""" 武侠三部曲 GraphRAG — FastAPI 后端 端点: GET /api/health — 健康检查(含 Neo4j 连通性) GET /api/stats — 图谱节点/关系统计 POST /api/import — 触发数据导入(一次性操作) POST /api/chat — 知识问答(Text-to-Cypher + LLM 回答) """ from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel from graph_query import get_driver, get_graph_stats from graph_builder import build_all_graphs from llm_router import answer_question import uvicorn app = FastAPI(title="武侠三部曲 GraphRAG API", version="1.1.0") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── Models ──────────────────────────────────────────────── class ChatRequest(BaseModel): question: str class ImportRequest(BaseModel): clear: bool = False # True = 先清空图谱再重新导入 novels: list[str] | None = None # 默认导入 dtslz/ldj/tlbb # ── Endpoints ───────────────────────────────────────────── @app.get("/api/health") def health(): driver = get_driver() try: driver.verify_connectivity() return {"status": "ok", "neo4j": "connected"} except Exception as e: raise HTTPException(status_code=503, detail=f"Neo4j 连接失败: {e}") @app.get("/api/stats") def stats(): try: return get_graph_stats() except Exception as e: raise HTTPException(status_code=503, detail=str(e)) @app.post("/api/import") def import_data(req: ImportRequest = ImportRequest()): """导入小说数据到 Neo4j(耗时约 1-3 分钟,请勿重复调用)""" driver = get_driver() try: build_all_graphs(driver, novels=req.novels, clear=req.clear) stats = get_graph_stats() return {"status": "ok", "stats": stats} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/api/chat") def chat(req: ChatRequest): if not req.question.strip(): raise HTTPException(status_code=400, detail="问题不能为空") return answer_question(req.question) if __name__ == "__main__": uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)