Spaces:
Running
Running
| """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<max) ───┐ | |
| │ │ | |
| └──── (relevant/irrelevant/max) ┘ | |
| │ | |
| ▼ | |
| END | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver | |
| from langgraph.graph import END, START, StateGraph | |
| from app.agents.nodes import ( | |
| evaluate_node, | |
| rerank_node, | |
| retrieve_node, | |
| route_node, | |
| ) | |
| from app.agents.state import AgentState | |
| from app.config import settings | |
| logger = logging.getLogger(__name__) | |
| # ========== 边决策 ========== | |
| def _after_route(state: AgentState) -> str: | |
| decision = state.get("route_decision", "retrieve") | |
| if decision == "direct": | |
| return END | |
| return "retrieve" | |
| def _after_evaluate(state: AgentState) -> str: | |
| """evaluate 后: needs_more + iter<max -> 回 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 | |