| | import sys |
| | from pathlib import Path |
| | import logging |
| | from contextlib import asynccontextmanager |
| | import atexit |
| | import shutil |
| |
|
| | from fastapi import FastAPI |
| | from fastapi.middleware.cors import CORSMiddleware |
| | from fastapi.responses import RedirectResponse |
| | import uvicorn |
| |
|
| | |
| | logging.basicConfig( |
| | level=logging.INFO, |
| | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" |
| | ) |
| | logger = logging.getLogger(__name__) |
| |
|
| | |
| | app_dir = Path(__file__).parent |
| | sys.path.insert(0, str(app_dir)) |
| |
|
| | |
| | from config import ( |
| | API_TITLE, API_DESCRIPTION, API_VERSION, |
| | HUGGINGFACE_API_KEY, HUGGINGFACE_STANCE_MODEL_ID, HUGGINGFACE_LABEL_MODEL_ID, |
| | HOST, PORT, RELOAD, |
| | CORS_ORIGINS, CORS_METHODS, CORS_HEADERS, CORS_CREDENTIALS, |
| | PRELOAD_MODELS_ON_STARTUP, LOAD_STANCE_MODEL, LOAD_KPA_MODEL, |
| | GROQ_API_KEY, GROQ_STT_MODEL, GROQ_TTS_MODEL, GROQ_CHAT_MODEL |
| | ) |
| |
|
| | |
| | def cleanup_temp_files(): |
| | """Nettoyer les fichiers temporaires audio au démarrage""" |
| | temp_dir = Path("temp_audio") |
| | if temp_dir.exists(): |
| | try: |
| | shutil.rmtree(temp_dir) |
| | logger.info("✓ Fichiers temporaires audio nettoyés") |
| | except Exception as e: |
| | logger.warning(f"⚠ Impossible de nettoyer le répertoire temporaire: {e}") |
| |
|
| | |
| | cleanup_temp_files() |
| |
|
| | |
| | @atexit.register |
| | def cleanup_on_exit(): |
| | temp_dir = Path("temp_audio") |
| | if temp_dir.exists(): |
| | try: |
| | shutil.rmtree(temp_dir) |
| | logger.info("Nettoyage final des fichiers temporaires") |
| | except: |
| | logger.warning("Échec du nettoyage final") |
| |
|
| | |
| | stance_model_manager = None |
| | kpa_model_manager = None |
| | try: |
| | from services.stance_model_manager import stance_model_manager |
| | from services.label_model_manager import kpa_model_manager |
| | logger.info("✓ Gestionnaires de modèles importés") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Impossible d'importer les gestionnaires de modèles: {e}") |
| |
|
| | |
| | MCP_ENABLED = False |
| | try: |
| | from services.mcp_service import init_mcp_server |
| | MCP_ENABLED = True |
| | logger.info("✓ Modules MCP détectés") |
| | except ImportError as e: |
| | logger.warning(f"⚠ MCP non disponible: {e}") |
| |
|
| | |
| | @asynccontextmanager |
| | async def lifespan(app: FastAPI): |
| | logger.info("="*60) |
| | logger.info("🚀 DÉMARRAGE API - Chargement des modèles et vérification des APIs...") |
| | logger.info("="*60) |
| | |
| | |
| | if not GROQ_API_KEY: |
| | logger.warning("⚠ GROQ_API_KEY non configurée. Fonctions STT/TTS désactivées.") |
| | else: |
| | logger.info("✓ GROQ_API_KEY configurée") |
| | |
| | if not HUGGINGFACE_API_KEY: |
| | logger.warning("⚠ HUGGINGFACE_API_KEY non configurée. Modèles locaux désactivés.") |
| | else: |
| | logger.info("✓ HUGGINGFACE_API_KEY configurée") |
| |
|
| | |
| | if PRELOAD_MODELS_ON_STARTUP: |
| | |
| | |
| | if LOAD_STANCE_MODEL and stance_model_manager and HUGGINGFACE_STANCE_MODEL_ID: |
| | try: |
| | stance_model_manager.load_model(HUGGINGFACE_STANCE_MODEL_ID, HUGGINGFACE_API_KEY) |
| | logger.info("✓ Modèle de détection de stance chargé") |
| | except Exception as e: |
| | logger.error(f"✗ Échec chargement modèle stance: {e}") |
| |
|
| | |
| | if LOAD_KPA_MODEL and kpa_model_manager and HUGGINGFACE_LABEL_MODEL_ID: |
| | try: |
| | kpa_model_manager.load_model(HUGGINGFACE_LABEL_MODEL_ID, HUGGINGFACE_API_KEY) |
| | logger.info("✓ Modèle KPA chargé") |
| | except Exception as e: |
| | logger.error(f"✗ Échec chargement modèle KPA: {e}") |
| |
|
| | |
| | if MCP_ENABLED: |
| | try: |
| | init_mcp_server(app) |
| | logger.info("✓ Serveur MCP initialisé") |
| | except Exception as e: |
| | logger.error(f"✗ Échec initialisation MCP: {e}") |
| |
|
| | logger.info("="*60) |
| | logger.info("✓ Démarrage terminé. API prête à recevoir des requêtes.") |
| | logger.info(f" STT Model: {GROQ_STT_MODEL}") |
| | logger.info(f" TTS Model: {GROQ_TTS_MODEL}") |
| | logger.info(f" Chat Model: {GROQ_CHAT_MODEL}") |
| | logger.info(f" MCP: {'Activé' if MCP_ENABLED else 'Désactivé'}") |
| | logger.info("="*60) |
| | |
| | yield |
| | |
| | logger.info("🛑 Arrêt de l'API...") |
| | |
| | cleanup_on_exit() |
| |
|
| | |
| | app = FastAPI( |
| | title=API_TITLE, |
| | description=API_DESCRIPTION, |
| | version=API_VERSION, |
| | lifespan=lifespan, |
| | docs_url="/docs", |
| | redoc_url="/redoc", |
| | openapi_url="/openapi.json" |
| | ) |
| |
|
| | |
| | app.add_middleware( |
| | CORSMiddleware, |
| | allow_origins=CORS_ORIGINS, |
| | allow_credentials=CORS_CREDENTIALS, |
| | allow_methods=CORS_METHODS, |
| | allow_headers=CORS_HEADERS, |
| | ) |
| |
|
| | |
| | |
| | try: |
| | from routes.stt_routes import router as stt_router |
| | app.include_router(stt_router, prefix="/api/v1/stt", tags=["Speech To Text"]) |
| | logger.info("✓ Route STT chargée (Groq Whisper)") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Route STT non trouvée: {e}") |
| | except Exception as e: |
| | logger.warning(f"⚠ Échec chargement route STT: {e}") |
| |
|
| | |
| | try: |
| | from routes.tts_routes import router as tts_router |
| | app.include_router(tts_router, prefix="/api/v1/tts", tags=["Text To Speech"]) |
| | logger.info("✓ Route TTS chargée (Groq PlayAI TTS)") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Route TTS non trouvée: {e}") |
| | except Exception as e: |
| | logger.warning(f"⚠ Échec chargement route TTS: {e}") |
| |
|
| | |
| | try: |
| | from routes.voice_chat_routes import router as voice_chat_router |
| | app.include_router(voice_chat_router, tags=["Voice Chat"]) |
| | logger.info("✓ Route Voice Chat chargée") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Route Voice Chat non trouvée: {e}") |
| | except Exception as e: |
| | logger.warning(f"⚠ Échec chargement route Voice Chat: {e}") |
| |
|
| | |
| | try: |
| | from routes import api_router |
| | app.include_router(api_router, prefix="/api/v1") |
| | logger.info("✓ Routes API principales chargées") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Routes API principales non trouvées: {e}") |
| | |
| | try: |
| | from routes.label import router as kpa_router |
| | app.include_router(kpa_router, prefix="/api/v1/kpa", tags=["KPA"]) |
| | from routes.stance import router as stance_router |
| | app.include_router(stance_router, prefix="/api/v1/stance", tags=["Stance Detection"]) |
| | logger.info("✓ Routes KPA et Stance chargées en fallback") |
| | except ImportError: |
| | logger.warning("⚠ Fallback pour KPA/Stance échoué") |
| | except Exception as e: |
| | logger.warning(f"⚠ Échec chargement routes API principales: {e}") |
| |
|
| | |
| | if MCP_ENABLED: |
| | try: |
| | from routes.mcp_routes import router as mcp_router |
| | app.include_router(mcp_router) |
| | logger.info("✓ Routes MCP FastAPI chargées (visibles dans Swagger)") |
| | except ImportError as e: |
| | logger.warning(f"⚠ Routes MCP FastAPI non trouvées: {e}") |
| | except Exception as e: |
| | logger.warning(f"⚠ Échec chargement routes MCP FastAPI: {e}") |
| | |
| | logger.info("✓ MCP monté via lifespan (endpoints auto-gérés)") |
| | else: |
| | logger.warning("⚠ MCP désactivé") |
| |
|
| | |
| | @app.get("/health", tags=["Health"]) |
| | async def health(): |
| | health_status = { |
| | "status": "healthy", |
| | "service": "NLP Debater + Groq Voice", |
| | "version": API_VERSION, |
| | "features": { |
| | "stt": GROQ_STT_MODEL if GROQ_API_KEY else "disabled", |
| | "tts": GROQ_TTS_MODEL if GROQ_API_KEY else "disabled", |
| | "chat": GROQ_CHAT_MODEL if GROQ_API_KEY else "disabled", |
| | "stance_model": "loaded" if (stance_model_manager and hasattr(stance_model_manager, 'model_loaded') and stance_model_manager.model_loaded) else "not loaded", |
| | "kpa_model": "loaded" if (kpa_model_manager and hasattr(kpa_model_manager, 'model_loaded') and kpa_model_manager.model_loaded) else "not loaded", |
| | "mcp": "enabled" if MCP_ENABLED else "disabled" |
| | }, |
| | "endpoints": { |
| | "mcp": "/api/v1/mcp" if MCP_ENABLED else "disabled" |
| | } |
| | } |
| | return health_status |
| |
|
| | @app.get("/", tags=["Root"]) |
| | async def root(): |
| | return RedirectResponse(url="/docs") |
| |
|
| | |
| | @app.exception_handler(404) |
| | async def not_found_handler(request, exc): |
| | endpoints = { |
| | "GET /": "Redirige vers /docs (Swagger UI)", |
| | "GET /health": "Health check", |
| | "POST /api/v1/stt/": "Speech to text", |
| | "POST /api/v1/tts/": "Text to speech", |
| | "POST /voice-chat/voice": "Voice chat" |
| | } |
| | if MCP_ENABLED: |
| | endpoints.update({ |
| | "GET /api/v1/mcp/health": "Health check MCP", |
| | "GET /api/v1/mcp/tools": "Liste outils MCP", |
| | "POST /api/v1/mcp/tools/call": "Appel d'outil MCP" |
| | }) |
| | return { |
| | "error": "Not Found", |
| | "message": f"URL {request.url} non trouvée", |
| | "available_endpoints": endpoints |
| | } |
| |
|
| | |
| | if __name__ == "__main__": |
| | logger.info("="*60) |
| | logger.info(f"Démarrage du serveur sur {HOST}:{PORT}") |
| | logger.info(f"Mode reload: {RELOAD}") |
| | logger.info("="*60) |
| | |
| | uvicorn.run( |
| | "main:app", |
| | host=HOST, |
| | port=PORT, |
| | reload=RELOAD, |
| | log_level="info" |
| | ) |