|
|
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 |
|
|
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("✓ Cleaned up previous temp audio files") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠ Could not clean temp directory: {e}") |
|
|
|
|
|
|
|
|
cleanup_temp_files() |
|
|
|
|
|
|
|
|
@atexit.register |
|
|
def cleanup_on_exit(): |
|
|
temp_dir = Path("temp_audio") |
|
|
if temp_dir.exists(): |
|
|
try: |
|
|
shutil.rmtree(temp_dir) |
|
|
except: |
|
|
pass |
|
|
|
|
|
|
|
|
try: |
|
|
from services.stance_model_manager import stance_model_manager |
|
|
from services.label_model_manager import kpa_model_manager |
|
|
logger.info("✓ Model managers imported") |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ Could not import model managers: {e}") |
|
|
stance_model_manager = None |
|
|
kpa_model_manager = None |
|
|
|
|
|
|
|
|
@asynccontextmanager |
|
|
async def lifespan(app: FastAPI): |
|
|
logger.info("="*60) |
|
|
logger.info("🚀 API STARTUP - Loading models and checking APIs...") |
|
|
logger.info("="*60) |
|
|
|
|
|
|
|
|
if not GROQ_API_KEY: |
|
|
logger.warning("⚠ GROQ_API_KEY is not set. STT/TTS features may not work.") |
|
|
else: |
|
|
logger.info("✓ GROQ_API_KEY is configured") |
|
|
|
|
|
if not HUGGINGFACE_API_KEY: |
|
|
logger.warning("⚠ HUGGINGFACE_API_KEY is not set. Local models may not work.") |
|
|
else: |
|
|
logger.info("✓ HUGGINGFACE_API_KEY is configured") |
|
|
|
|
|
|
|
|
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("✓ Stance model loaded successfully") |
|
|
except Exception as e: |
|
|
logger.error(f"✗ Failed loading stance model: {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("✓ KPA model loaded successfully") |
|
|
except Exception as e: |
|
|
logger.error(f"✗ Failed loading KPA model: {e}") |
|
|
|
|
|
logger.info("="*60) |
|
|
logger.info("✓ Startup complete. API ready to receive requests.") |
|
|
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("="*60) |
|
|
|
|
|
yield |
|
|
|
|
|
logger.info("🛑 Shutting down 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("✓ STT route loaded (Groq Whisper)") |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ STT route not found: {e}") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠ Failed loading STT route: {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("✓ TTS route loaded (Groq PlayAI TTS)") |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ TTS route not found: {e}") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠ Failed loading TTS route: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
from routes import api_router |
|
|
app.include_router(api_router) |
|
|
logger.info("✓ Main API routes loaded") |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ Main API routes not found: {e}") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠ Failed loading main API routes: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
from routes.voice_chat_routes import router as voice_chat_router |
|
|
app.include_router(voice_chat_router, tags=["Voice Chat"]) |
|
|
logger.info("✓ Voice Chat route loaded") |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ Voice Chat route not found: {e}") |
|
|
except Exception as e: |
|
|
logger.warning(f"⚠ Failed loading Voice Chat route: {e}") |
|
|
|
|
|
|
|
|
try: |
|
|
from services.mcp_service import init_mcp_server |
|
|
from routes.mcp_routes import router as mcp_router |
|
|
MCP_ENABLED = True |
|
|
except ImportError as e: |
|
|
logger.warning(f"⚠ MCP not available: {e}") |
|
|
MCP_ENABLED = False |
|
|
|
|
|
|
|
|
if MCP_ENABLED: |
|
|
try: |
|
|
init_mcp_server(app) |
|
|
logger.info("✓ MCP Server initialized") |
|
|
except Exception as e: |
|
|
logger.error(f"✗ MCP initialization failed: {e}") |
|
|
|
|
|
|
|
|
if MCP_ENABLED: |
|
|
app.include_router(mcp_router) |
|
|
logger.info("✓ MCP routes loaded") |
|
|
|
|
|
|
|
|
@app.get("/health", tags=["Health"]) |
|
|
async def health(): |
|
|
"""Health check endpoint""" |
|
|
health_status = { |
|
|
"status": "healthy", |
|
|
"service": "NLP Debater + Groq Voice", |
|
|
"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 stance_model_manager.model is not None) else "not loaded", |
|
|
"kpa_model": "loaded" if (kpa_model_manager and kpa_model_manager.model is not None) else "not loaded" |
|
|
} |
|
|
} |
|
|
return health_status |
|
|
|
|
|
@app.get("/", tags=["Root"]) |
|
|
async def root(): |
|
|
"""Root endpoint with API information""" |
|
|
return { |
|
|
"message": "NLP Debater API with Groq Voice Support", |
|
|
"version": API_VERSION, |
|
|
"endpoints": { |
|
|
"docs": "/docs", |
|
|
"redoc": "/redoc", |
|
|
"health": "/health", |
|
|
"stt": "/api/v1/stt/", |
|
|
"tts": "/api/v1/tts/" |
|
|
}, |
|
|
"models": { |
|
|
"stt": GROQ_STT_MODEL, |
|
|
"tts": GROQ_TTS_MODEL, |
|
|
"chat": GROQ_CHAT_MODEL |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@app.exception_handler(404) |
|
|
async def not_found_handler(request, exc): |
|
|
return { |
|
|
"error": "Not Found", |
|
|
"message": f"The requested URL {request.url} was not found", |
|
|
"available_endpoints": { |
|
|
"GET /": "API information", |
|
|
"GET /health": "Health check", |
|
|
"POST /api/v1/stt/": "Speech to text", |
|
|
"POST /api/v1/tts/": "Text to speech" |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
logger.info("="*60) |
|
|
logger.info(f"Starting server on {HOST}:{PORT}") |
|
|
logger.info(f"Reload mode: {RELOAD}") |
|
|
logger.info("="*60) |
|
|
|
|
|
uvicorn.run( |
|
|
"main:app", |
|
|
host=HOST, |
|
|
port=PORT, |
|
|
reload=RELOAD, |
|
|
log_level="info" |
|
|
) |