File size: 5,497 Bytes
d28f1ed 247c14c 48d6c63 d28f1ed 48d6c63 d28f1ed 595f77d 48d6c63 d28f1ed 8f36e50 d28f1ed 8f36e50 d28f1ed 8f36e50 d28f1ed 48d6c63 d28f1ed 595f77d 48d6c63 a7400dd 48d6c63 d28f1ed a7400dd ce67322 a7400dd ce67322 d28f1ed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | """
CAPL Routeur IA API
Main FastAPI application with AI agent routing.
"""
from dotenv import load_dotenv
load_dotenv() # charge .env pour tout le process
from pathlib import Path
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse, FileResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.exceptions import RequestValidationError
from contextlib import asynccontextmanager
import logging
from config import settings
from api.routes import auth, completion, transcription, models, realtime
from api.routes import documents
from api.routes import voice
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Lifespan event handler for startup and shutdown."""
# Startup
logger.info(f"Starting {settings.api_title} v{settings.api_version}")
logger.info(f"Environment: {settings.environment}")
from services.voice.ice_servers import get_ice_servers
from pipecat.transports.smallwebrtc.request_handler import SmallWebRTCRequestHandler
raw_ice, rtc_ice = get_ice_servers()
app.state.voice_ice_servers = raw_ice
app.state.voice_handler = SmallWebRTCRequestHandler(ice_servers=rtc_ice)
logger.info("Voice WebRTC handler initialized with %d ICE server(s)", len(rtc_ice))
yield
# Shutdown
logger.info("Shutting down API")
if hasattr(app.state, "voice_handler") and app.state.voice_handler is not None:
await app.state.voice_handler.close()
# Create FastAPI app
app = FastAPI(
title=settings.api_title,
version=settings.api_version,
description="""
# CAPL Routeur IA API
API sécurisée pour l'interaction avec des agents IA basés sur LangGraph.
## Fonctionnalités principales:
- **Authentification JWT** pour sécuriser l'accès
- **Completion texte** avec support du streaming (SSE)
- **Multi-modèles**: OpenAI (GPT-4, GPT-3.5) et Mistral AI
- **Multi-agents**: Architecture extensible pour différents types d'agents
- **Transcription audio**: Conversion audio vers texte avec Whisper
- **Conversation vocale**: WebRTC via Pipecat (STT + LangGraph + TTS)
- **Temps réel**: Support WebSocket
## Authentification
1. Obtenez un token JWT via `POST /auth/token`
2. Incluez le token dans le header: `Authorization: Bearer <token>`
3. Utilisez le token pour toutes les requêtes protégées
## Architecture
- **Clean Architecture** avec séparation domain/services/api
- **SOLID principles** pour une extensibilité maximale
- **LangGraph** pour l'orchestration des agents IA
""",
lifespan=lifespan,
docs_url="/docs",
redoc_url="/redoc"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # À restreindre en production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Exception handlers
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Handle validation errors with detailed messages."""
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"error": "Validation Error",
"detail": exc.errors(),
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
"""Handle unexpected exceptions."""
logger.error(f"Unexpected error: {str(exc)}", exc_info=True)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"error": "Internal Server Error",
"detail": str(exc) if settings.environment == "development" else "An unexpected error occurred"
}
)
# Root endpoint
@app.get("/", tags=["Root"])
async def root():
"""Root endpoint with API information."""
return {
"name": settings.api_title,
"version": settings.api_version,
"status": "running",
"environment": settings.environment,
"docs": "/docs",
"health": "/health"
}
# Include routers
app.include_router(auth.router)
app.include_router(models.router)
app.include_router(completion.router)
app.include_router(transcription.router)
app.include_router(realtime.router)
app.include_router(documents.router)
app.include_router(voice.router)
STATIC_DIR = Path(__file__).resolve().parent / "static"
@app.get("/voice-test", tags=["Voice"])
async def serve_voice_page():
"""Serve the standalone WebRTC voice test page (SmallWebRTC)."""
return FileResponse(str(STATIC_DIR / "voice.html"))
@app.get("/voice-daily", tags=["Voice"])
async def serve_voice_daily_page():
"""Serve the Daily.co voice test page with Prebuilt UI (works on HF Spaces)."""
return FileResponse(str(STATIC_DIR / "voice_daily.html"))
@app.get("/voice-daily-minimal", tags=["Voice"])
async def serve_voice_daily_minimal_page():
"""Serve the Daily.co minimal page (no iframe, only bot audio in own UI)."""
return FileResponse(str(STATIC_DIR / "voice_daily_minimal.html"))
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"app:app",
host="0.0.0.0",
port=7860,
reload=True if settings.environment == "development" else False
)
|