| """FastAPI application — the main REST entry point.""" |
|
|
| import time |
| import uuid |
| from pathlib import Path |
| from contextlib import asynccontextmanager |
|
|
| from fastapi import FastAPI, Request |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import FileResponse, JSONResponse |
| from fastapi.staticfiles import StaticFiles |
|
|
| from agentic_rag.config.settings import get_settings |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| """Application lifespan — startup and shutdown.""" |
| settings = get_settings() |
| print(f"[{settings.app_name}] Starting on {settings.api.host}:{settings.api.port}") |
|
|
| |
| try: |
| from agentic_rag.data.db import init_db |
| init_db(settings.db_path) |
| print(f"[{settings.app_name}] Database ready ({settings.db_path})") |
| except Exception as e: |
| print(f"[{settings.app_name}] ⚠ Database unavailable: {e}") |
|
|
| |
| mcp_servers = settings.mcp_servers |
| if mcp_servers: |
| from agentic_rag.core.mcp.client import MCPClient |
| from agentic_rag.orchestration.l1_tools.registry import get_tool_registry |
| mcp_client = MCPClient() |
| registry = get_tool_registry() |
| for name, config in mcp_servers.items(): |
| try: |
| cmd = config.get("command", "") |
| args_raw = config.get("args", "") |
| |
| if isinstance(args_raw, list): |
| arg_list = args_raw |
| else: |
| arg_list = args_raw.split() if args_raw else [] |
| |
| import os as _os |
| env = dict(_os.environ) |
| nested_env = config.get("env", {}) |
| if isinstance(nested_env, dict): |
| env.update({str(k).upper(): str(v) for k, v in nested_env.items() if v}) |
| tools = await mcp_client.connect_stdio( |
| server_name=name, command=cmd, |
| args=arg_list, env=env if env else None, |
| ) |
| for tool in tools: |
| registry.register_mcp(tool, name) |
| print(f"[{settings.app_name}] MCP/{name} connected — {len(tools)} tools " |
| f"({cmd} {' '.join(arg_list[:2])}...)") |
| except Exception as e: |
| print(f"[{settings.app_name}] ⚠ MCP/{name} failed: {e}") |
|
|
| |
| try: |
| from agentic_rag.services.knowledge.pipeline import init_knowledge_pipeline |
| init_knowledge_pipeline() |
| print(f"[{settings.app_name}] Knowledge pipeline ready " |
| f"(embedding={settings.embedding.model}, dim={settings.embedding.dim})") |
| except Exception as e: |
| print(f"[{settings.app_name}] ⚠ Knowledge pipeline unavailable: {e}") |
|
|
| |
| if settings.gateway.qqbot.enabled: |
| from agentic_rag.entrypoints.gateway.qqbot import start_qqbot |
| await start_qqbot() |
|
|
| yield |
|
|
| |
| if settings.gateway.qqbot.enabled: |
| from agentic_rag.entrypoints.gateway.qqbot import stop_qqbot |
| await stop_qqbot() |
|
|
| try: |
| from agentic_rag.data.db import _db |
| if _db: |
| _db.close() |
| except Exception: |
| pass |
| print(f"[{settings.app_name}] Shutting down") |
|
|
|
|
| def create_app() -> FastAPI: |
| """Create and configure the FastAPI application.""" |
| settings = get_settings() |
|
|
| app = FastAPI( |
| title=settings.app_name, |
| version="0.1.0", |
| description="Agentic RAG — Multi-modal, ReAct-powered, MCP-enabled RAG System", |
| lifespan=lifespan, |
| ) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=settings.api.cors_origins, |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| |
| @app.middleware("http") |
| async def add_request_id(request: Request, call_next): |
| request_id = request.headers.get("X-Request-ID", uuid.uuid4().hex[:12]) |
| request.state.request_id = request_id |
| response = await call_next(request) |
| response.headers["X-Request-ID"] = request_id |
| return response |
|
|
| |
| from agentic_rag.entrypoints.rest.routes import chat, health, mcp, rag, session, settings |
| app.include_router(health.router, tags=["Health"]) |
| app.include_router(chat.router, prefix="/api/v1", tags=["Chat"]) |
| app.include_router(rag.router, prefix="/api/v1", tags=["RAG"]) |
| app.include_router(session.router, prefix="/api/v1", tags=["Session"]) |
| app.include_router(mcp.router, prefix="/api/v1", tags=["MCP"]) |
| app.include_router(settings.router, prefix="/api/v1", tags=["Settings"]) |
|
|
| |
| if get_settings().gateway.enabled: |
| from agentic_rag.entrypoints.gateway.router import get_gateway_router |
| gateway_router = get_gateway_router() |
| if gateway_router.routes: |
| app.include_router(gateway_router) |
|
|
| |
| try: |
| from agentic_rag.entrypoints.websocket.handler import router as ws_router |
| app.include_router(ws_router, tags=["WebSocket"]) |
| except ImportError: |
| pass |
|
|
| |
| static_dir = Path(__file__).resolve().parent.parent.parent.parent / "static" |
| if static_dir.exists(): |
| app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") |
|
|
| @app.get("/", include_in_schema=False) |
| async def spa_root(): |
| return FileResponse(str(static_dir / "index.html")) |
|
|
| |
| @app.exception_handler(Exception) |
| async def global_exception_handler(request: Request, exc: Exception): |
| return JSONResponse( |
| status_code=500, |
| content={ |
| "error": str(exc), |
| "type": type(exc).__name__, |
| "request_id": getattr(request.state, "request_id", "unknown"), |
| "timestamp": time.time(), |
| }, |
| ) |
|
|
| return app |
|
|
|
|
| app = create_app() |
|
|