"""services/copilot/api.py — FastAPI router for the RAG copilot. Mounted at /api/v1/copilot in app/main.py. Follows the same auth pattern as the rest of this backend (require_api_key — see app/security.py) since there's no per-user auth yet. """ from __future__ import annotations import logging from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.config import Settings, get_settings from app.database import get_db from app.security import rate_limit, require_api_key from app.services.copilot import db as copilot_db from app.services.copilot.engine import answer_chat from app.services.copilot.models import ChatRequest, ChatResponse logger = logging.getLogger(__name__) router = APIRouter(prefix="/copilot", tags=["Copilot"]) @router.post( "/chat", response_model=ChatResponse, dependencies=[Depends(require_api_key), Depends(rate_limit(limit=20, window_seconds=60))], summary="Ask Shield Copilot a question, grounded in the app's knowledge base", ) async def chat( request: ChatRequest, session: AsyncSession = Depends(get_db), settings: Settings = Depends(get_settings), ) -> ChatResponse: try: return await answer_chat(session, request, settings) except Exception as exc: # copilot_chunks / pgvector may be entirely unavailable in this # deployment (see database.py::_create_copilot_table_if_possible) — # surface a clear 503 rather than a generic 500. logger.exception("Copilot chat failed: %s", exc) raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Copilot is temporarily unavailable. Please try again shortly.", ) from exc @router.get("/health", summary="Copilot subsystem status (chunk count)") async def copilot_health(session: AsyncSession = Depends(get_db)) -> dict: try: count = await copilot_db.chunk_count(session) return {"status": "ready" if count > 0 else "empty", "indexed_chunks": count} except Exception as exc: return {"status": "unavailable", "detail": str(exc)}