79 lines
2.4 KiB
Python
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),
|
|
)
|