"""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), )