feat: add POST /ai/fix-tree endpoint for AI-powered validation fixes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
78
backend/app/api/endpoints/ai_fix.py
Normal file
78
backend/app/api/endpoints/ai_fix.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""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),
|
||||
)
|
||||
Reference in New Issue
Block a user