feat: add POST /ai/session-to-flow endpoint (Task 21)
Converts a completed session into a reusable procedural flow using AI. Includes quota checking, usage recording, and proper error handling. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
POST /ai/branch-detail — Stage 3: AI generates detail for one branch
|
||||
POST /ai/assemble — Stage 4: assemble branches into tree (no AI)
|
||||
GET /ai/quota — quota status
|
||||
|
||||
Session conversion:
|
||||
POST /ai/session-to-flow — Convert a completed session into a procedural flow
|
||||
"""
|
||||
import logging
|
||||
from typing import Annotated
|
||||
@@ -40,6 +43,8 @@ from app.schemas.ai_builder import (
|
||||
AIAssembleResponse,
|
||||
AIQuotaStatusResponse,
|
||||
)
|
||||
from app.schemas.session_to_flow import SessionToFlowRequest, SessionToFlowResponse
|
||||
from app.services.session_to_flow_service import generate_flow_from_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -437,3 +442,97 @@ async def assemble(
|
||||
summary=stats,
|
||||
status="completed",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/session-to-flow", response_model=SessionToFlowResponse)
|
||||
@limiter.limit("5/minute")
|
||||
async def session_to_flow(
|
||||
request: Request,
|
||||
data: SessionToFlowRequest,
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
_: None = Depends(require_engineer_or_admin),
|
||||
):
|
||||
"""Convert a completed troubleshooting session into a reusable procedural flow."""
|
||||
_require_ai_enabled()
|
||||
|
||||
# Check AI quota
|
||||
allowed, quota_status = await check_ai_quota(
|
||||
user_id=current_user.id,
|
||||
account_id=current_user.account_id,
|
||||
db=db,
|
||||
billing_anchor=current_user.ai_billing_cycle_anchor_at,
|
||||
is_super_admin=current_user.is_super_admin,
|
||||
)
|
||||
if not allowed:
|
||||
reset_key = (
|
||||
"daily_reset_at"
|
||||
if quota_status.get("deny_reason") == "daily"
|
||||
else "monthly_reset_at"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail={
|
||||
"message": f"AI build limit exceeded ({quota_status['deny_reason']})",
|
||||
"reset_at": quota_status.get(reset_key),
|
||||
"quota": quota_status,
|
||||
},
|
||||
)
|
||||
|
||||
plan = await get_user_plan(current_user.account_id, db)
|
||||
|
||||
try:
|
||||
result = await generate_flow_from_session(
|
||||
session_id=data.session_id,
|
||||
user_id=current_user.id,
|
||||
account_id=current_user.account_id,
|
||||
db=db,
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.warning("session_to_flow validation error: %s", e)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("session_to_flow failed: %s", e)
|
||||
await record_ai_usage(
|
||||
user_id=current_user.id,
|
||||
account_id=current_user.account_id,
|
||||
conversation_id=None,
|
||||
generation_type="session_to_flow",
|
||||
tier=plan,
|
||||
input_tokens=0,
|
||||
output_tokens=0,
|
||||
estimated_cost=0,
|
||||
succeeded=False,
|
||||
counts_toward_quota=False,
|
||||
error_code=type(e).__name__,
|
||||
extra_data={"session_id": data.session_id, "error": str(e)},
|
||||
db=db,
|
||||
)
|
||||
await db.commit()
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to generate flow: {type(e).__name__}. Please try again.",
|
||||
)
|
||||
|
||||
# Record successful quota-consuming usage
|
||||
await record_ai_usage(
|
||||
user_id=current_user.id,
|
||||
account_id=current_user.account_id,
|
||||
conversation_id=None,
|
||||
generation_type="session_to_flow",
|
||||
tier=plan,
|
||||
input_tokens=0,
|
||||
output_tokens=0,
|
||||
estimated_cost=0,
|
||||
succeeded=True,
|
||||
counts_toward_quota=True,
|
||||
error_code=None,
|
||||
extra_data={"session_id": data.session_id},
|
||||
db=db,
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return SessionToFlowResponse(**result)
|
||||
|
||||
Reference in New Issue
Block a user