"""LangGraph StateGraph 装配 + 编译. 设计: 图只负责"检索循环" (route → retrieve → rerank → evaluate). answer 流式生成由 chat 端点直接驱动 (answer_node_stream + asyncio.Queue), 这样 SSE token 流不依赖 astream_events 的复杂性. ⚡ A 改良版: route + query_rewrite 已合并为单次 LLM 调用 (route_node), 旧版两次串行 RTT → 一次, 省 0.5-1.5s. 流程图: START │ ▼ route ─── direct ───────────────────────────────► END │ ▼ retrieve ──► rerank ──► evaluate │ ┌──── (needs_more, iter str: decision = state.get("route_decision", "retrieve") if decision == "direct": return END return "retrieve" def _after_evaluate(state: AgentState) -> str: """evaluate 后: needs_more + iter 回 retrieve; 否则 END.""" if state.get("needs_more_retrieval", False): return "retrieve" return END # ========== 编译 ========== def _build_graph() -> StateGraph: g = StateGraph(AgentState) g.add_node("route", route_node) g.add_node("retrieve", retrieve_node) g.add_node("rerank", rerank_node) g.add_node("evaluate", evaluate_node) g.add_edge(START, "route") g.add_conditional_edges( "route", _after_route, {END: END, "retrieve": "retrieve"}, ) g.add_edge("retrieve", "rerank") g.add_edge("rerank", "evaluate") g.add_conditional_edges( "evaluate", _after_evaluate, {END: END, "retrieve": "retrieve"}, ) return g # ========== 编译产物 (带 checkpointer) ========== _compiled = None _saver_cm = None _compiled_loop = None async def get_compiled_graph(): """懒加载 + 单例 (按 event loop 隔离).""" global _compiled, _saver_cm, _compiled_loop import asyncio current_loop = asyncio.get_running_loop() if _compiled is not None and _compiled_loop is current_loop: return _compiled if _compiled is not None: await close_checkpointer() g = _build_graph() _saver_cm = AsyncSqliteSaver.from_conn_string(str(settings.langgraph_db_path)) saver = await _saver_cm.__aenter__() _compiled = g.compile(checkpointer=saver) _compiled_loop = current_loop logger.info( "LangGraph compiled with AsyncSqliteSaver at %s", settings.langgraph_db_path, ) return _compiled async def close_checkpointer() -> None: global _compiled, _saver_cm, _compiled_loop _compiled = None _compiled_loop = None if _saver_cm is not None: try: await _saver_cm.__aexit__(None, None, None) except Exception as e: # noqa: BLE001 logger.warning("AsyncSqliteSaver close failed: %s", e) _saver_cm = None