2026-02-24 05:19:38 +00:00
|
|
|
"""
|
|
|
|
|
Main FastAPI application for AI Service.
|
|
|
|
|
[AC-AISVC-01] Entry point with middleware and exception handlers.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
|
|
|
|
from fastapi import FastAPI, Request, status
|
|
|
|
|
from fastapi.exceptions import HTTPException, RequestValidationError
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
from fastapi.responses import JSONResponse
|
|
|
|
|
|
|
|
|
|
from app.api import chat_router, health_router
|
2026-02-24 11:52:52 +00:00
|
|
|
from app.api.admin import dashboard_router, kb_router, rag_router, sessions_router
|
2026-02-24 05:19:38 +00:00
|
|
|
from app.core.config import get_settings
|
|
|
|
|
from app.core.database import close_db, init_db
|
|
|
|
|
from app.core.exceptions import (
|
|
|
|
|
AIServiceException,
|
|
|
|
|
ErrorCode,
|
|
|
|
|
ErrorResponse,
|
|
|
|
|
ai_service_exception_handler,
|
|
|
|
|
generic_exception_handler,
|
|
|
|
|
http_exception_handler,
|
|
|
|
|
)
|
|
|
|
|
from app.core.middleware import TenantContextMiddleware
|
|
|
|
|
from app.core.qdrant_client import close_qdrant_client
|
|
|
|
|
|
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(
|
|
|
|
|
level=getattr(logging, settings.log_level.upper()),
|
|
|
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-01, AC-AISVC-11] Application lifespan manager.
|
|
|
|
|
Handles startup and shutdown of database and external connections.
|
|
|
|
|
"""
|
|
|
|
|
logger.info(f"[AC-AISVC-01] Starting {settings.app_name} v{settings.app_version}")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await init_db()
|
|
|
|
|
logger.info("[AC-AISVC-11] Database initialized successfully")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning(f"[AC-AISVC-11] Database initialization skipped: {e}")
|
|
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
await close_db()
|
|
|
|
|
await close_qdrant_client()
|
|
|
|
|
logger.info(f"Shutting down {settings.app_name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(
|
|
|
|
|
title=settings.app_name,
|
|
|
|
|
version=settings.app_version,
|
|
|
|
|
description="""
|
|
|
|
|
Python AI Service for intelligent chat with RAG support.
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
- Multi-tenant isolation via X-Tenant-Id header
|
|
|
|
|
- SSE streaming support via Accept: text/event-stream
|
|
|
|
|
- RAG-powered responses with confidence scoring
|
|
|
|
|
|
|
|
|
|
## Response Modes
|
|
|
|
|
- **JSON**: Default response mode (Accept: application/json or no Accept header)
|
|
|
|
|
- **SSE Streaming**: Set Accept: text/event-stream for streaming responses
|
|
|
|
|
""",
|
|
|
|
|
docs_url="/docs",
|
|
|
|
|
redoc_url="/redoc",
|
|
|
|
|
lifespan=lifespan,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
allow_origins=["*"],
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
app.add_middleware(TenantContextMiddleware)
|
|
|
|
|
|
|
|
|
|
app.add_exception_handler(AIServiceException, ai_service_exception_handler)
|
|
|
|
|
app.add_exception_handler(HTTPException, http_exception_handler)
|
|
|
|
|
app.add_exception_handler(Exception, generic_exception_handler)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
|
|
|
async def validation_exception_handler(request: Request, exc: RequestValidationError):
|
|
|
|
|
"""
|
|
|
|
|
[AC-AISVC-03] Handle request validation errors with structured response.
|
|
|
|
|
"""
|
|
|
|
|
logger.warning(f"[AC-AISVC-03] Request validation error: {exc.errors()}")
|
|
|
|
|
error_response = ErrorResponse(
|
|
|
|
|
code=ErrorCode.INVALID_REQUEST.value,
|
|
|
|
|
message="Request validation failed",
|
|
|
|
|
details=[{"loc": list(err["loc"]), "msg": err["msg"], "type": err["type"]} for err in exc.errors()],
|
|
|
|
|
)
|
|
|
|
|
return JSONResponse(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
content=error_response.model_dump(exclude_none=True),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.include_router(health_router)
|
|
|
|
|
app.include_router(chat_router)
|
|
|
|
|
|
2026-02-24 11:52:52 +00:00
|
|
|
app.include_router(dashboard_router)
|
2026-02-24 08:10:27 +00:00
|
|
|
app.include_router(kb_router)
|
|
|
|
|
app.include_router(rag_router)
|
|
|
|
|
app.include_router(sessions_router)
|
|
|
|
|
|
2026-02-24 05:19:38 +00:00
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
|
uvicorn.run(
|
|
|
|
|
"app.main:app",
|
|
|
|
|
host=settings.host,
|
|
|
|
|
port=settings.port,
|
|
|
|
|
reload=settings.debug,
|
|
|
|
|
)
|