import logging import sys from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, HTTPException from pydantic import BaseModel sys.path.insert(0, str(Path(__file__).resolve().parent)) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) PROJECT_ROOT = Path(__file__).resolve().parent.parent GRAPHRAG_ROOT = PROJECT_ROOT / "graphrag_input" @asynccontextmanager async def lifespan(app: FastAPI): logger.info(f"Loading GraphRAG engine from {GRAPHRAG_ROOT} ...") try: from query_engine import init_engine init_engine(str(GRAPHRAG_ROOT)) logger.info("GraphRAG engine ready ✅") except Exception as e: logger.error(f"Engine load failed (queries will return 503): {e}") yield logger.info("Shutdown") app = FastAPI( title="K-Drama GraphRAG API", description="Persistent in-memory GraphRAG over 1637 K-dramas", version="2.0.0", lifespan=lifespan, ) class QueryRequest(BaseModel): question: str method: str = "global" class QueryResponse(BaseModel): question: str method: str answer: str @app.get("/") def root(): try: from query_engine import get_engine loaded = get_engine()._loaded except Exception: loaded = False return {"status": "running", "engine_loaded": loaded} @app.get("/health") def health(): return {"status": "ok"} @app.post("/query", response_model=QueryResponse) def query(request: QueryRequest): if request.method not in ["global", "local"]: raise HTTPException(400, "method must be 'global' or 'local'") if not request.question.strip(): raise HTTPException(400, "question cannot be empty") try: from query_engine import get_engine answer = get_engine().query_sync(request.question, request.method) return QueryResponse( question=request.question, method=request.method, answer=answer, ) except RuntimeError as e: raise HTTPException(503, f"Engine not ready: {e}") except Exception as e: logger.exception("Query failed") raise HTTPException(500, str(e))