Files
2026-02-26 17:25:38 -05:00

79 lines
2.4 KiB
Python

"""AI auto-fix endpoint for tree validation errors.
POST /ai/fix-tree — accepts a tree with validation errors and returns
AI-generated fix proposals for each error.
"""
import logging
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Request, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db, require_engineer_or_admin
from app.core.config import settings
from app.core.rate_limit import limiter
from app.core.ai_fix_service import generate_fixes
from app.models.user import User
from app.schemas.ai_fix import (
AIFixTreeRequest,
AIFixTreeResponse,
AIFixProposal,
AIFixTokenUsage,
)
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/ai", tags=["ai-fix"])
def _require_ai_enabled() -> None:
"""Raise 503 if AI is not configured."""
if not settings.ai_enabled:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="AI fix is not configured. Set GOOGLE_AI_API_KEY or ANTHROPIC_API_KEY.",
)
@router.post("/fix-tree", response_model=AIFixTreeResponse)
@limiter.limit("10/minute")
async def fix_tree(
request: Request,
body: AIFixTreeRequest,
user: Annotated[User, Depends(require_engineer_or_admin)],
db: Annotated[AsyncSession, Depends(get_db)],
):
"""Generate AI-powered fixes for tree validation errors."""
_require_ai_enabled()
validation_errors = [
{"node_id": e.node_id, "message": e.message}
for e in body.validation_errors
]
try:
fixes, input_tokens, output_tokens = await generate_fixes(
tree_structure=body.tree_structure,
tree_name=body.tree_name,
tree_type=body.tree_type,
validation_errors=validation_errors,
)
except RuntimeError as exc:
logger.error("AI provider not available: %s", exc)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=str(exc),
)
except Exception as exc:
logger.exception("Unexpected error in AI fix service")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="An unexpected error occurred while generating fixes.",
)
return AIFixTreeResponse(
fixes=[AIFixProposal(**f) for f in fixes],
tokens_used=AIFixTokenUsage(input=input_tokens, output=output_tokens),
)