feat(agent): LangGraph PoC —— /agent/v2/* 并行老 /agent/chat(不合 main) - #58
Open
Color2333 wants to merge 5 commits into
Open
feat(agent): LangGraph PoC —— /agent/v2/* 并行老 /agent/chat(不合 main)#58Color2333 wants to merge 5 commits into
Color2333 wants to merge 5 commits into
Conversation
PoC 目标:验证 LangGraph 能复刻现有 agent 能力(27 工具 + confirm 流 + SSE 协议),
不合 main,拍板后再整体替换自研 StreamingAgentLoop。
新增包 packages/langgraph_agent/:
- chat_model.py: PaperMindChatModel 包装现有 LLMClient.chat_stream 为
LangChain BaseChatModel,复用 provider 路由(xiaomi/zhipu/openai)。
bind_tools 直接透传现有 get_openai_tools() 的 OpenAI spec(不漂移描述)。
tool_call chunk → AIMessageChunk(tool_call_chunks=[...])(args 为 JSON str,
符合 langchain 流式协议)。
- tools_adapter.py: CONFIRM_NAMES 从 TOOL_REGISTRY 派生;
run_tool 复用 execute_tool_stream + get_stream_writer 发 tool_start/progress/result;
describe_action 复用老 ConfirmationMixin。
- state.py + graph.py: StateGraph ReAct(agent → should_continue → tools → agent),
recursion_limit=24 替代 max_rounds;confirm 工具调 interrupt() 暂停,
Command(resume={"confirmed":bool}) 恢复(顺带修⑧:多 confirm 逐个 interrupt)。
- checkpointer.py: PostgresSaver 单例(psycopg v3,setup() 自建表),
SQLite 回退 MemorySaver;thread_id = conversation_id。
- sse_adapter.py: stream_mode=["messages","custom","updates"] →
现有 9 种 SSE 事件(text_delta/tool_start/tool_progress/tool_result/
action_confirm/action_result/done/error);__interrupt__ → action_confirm;
GraphRecursionError → text_delta 提示 + done(修⑩同形)。
- entry.py: stream_chat_v2/confirm_v2/reject_v2 三个入口。
新增路由 apps/api/routers/agent_v2.py:
- /agent/v2/chat / /agent/v2/confirm/{id} / /agent/v2/reject/{id}
- 复用 agent.py 的持久化辅助,SSE 协议 + 持久化与 /agent/chat 完全一致。
- main.py 懒挂载(未装 langgraph extra 时跳过,核心不受影响)。
依赖:pyproject.toml 新增 langgraph extra(不进核心 dependencies)。
Dockerfile.backend 装依赖加 ,langgraph。
测试(12 个,全绿):test_langgraph_chat_model + test_langgraph_agent。
全量 tests/ 81 passed(69 老 + 12 新), 2 skipped。
🔍 OpenCode PR Review Required这是一个受保护的分支,merge 前需要进行 code review。 请运行以下命令进行 OpenCode review: 或者在 PR 页面评论 This is an automated reminder from PR Review Gate. |
本地实测发现两个 PoC bug:
1. action_id 随机导致 confirm/resume 不一致:
_make_action_id 用 uuid4() 随机生成,LangGraph resume 会重新执行
call_tools 节点(interrupt 此时立即返回 resume 值),重新走 _make_action_id
生成新 id,与首次 interrupt 时的 action_id 不一致,路由层
_resolve_conversation_id_from_action 反查失败。
修:改用 sha256(thread_id:tool_call_id) 确定性派生,interrupt 和 resume
产出的 action_id 一致。实测 confirm 后 action_result.id 与 action_confirm.id 匹配。
2. LangGraph interrupt 不写 AgentPendingAction(checkpoint 已存状态),
但 /agent/v2/confirm 路由需从 action_id 反查 conversation_id:
- agent_v2.stream_with_save 加 action_confirm 事件处理:写一行
AgentPendingAction(conversation_id 存,conversation_state 留空)。
- confirm/reject 路由调 _delete_pending_action 提前删(LangGraph resume
靠 checkpoint 不靠 pending action)。
本地实测全链路通过:
- /agent/v2/chat → skim_paper → action_confirm(action_id 确定)
- /agent/v2/confirm/{id} → tool_start/tool_result/action_result + LLM 继续
- /agent/v2/reject/{id} → action_result(success=false, "用户已取消") + LLM 替代
- pending action confirm/reject 后已清理
🔍 OpenCode PR Review Required这是一个受保护的分支,merge 前需要进行 code review。 请运行以下命令进行 OpenCode review: 或者在 PR 页面评论 This is an automated reminder from PR Review Gate. |
scripts/bench_agent_chat.py:5 场景 × 5 次交替跑 v1/v2,测 4 指标 (端到端 SSE / TTFT / 工具往返 / token),结果输出到 stdout 表格 + JSON。 设计要点: - 交替跑(v1, v2, v1, v2...)平衡 LLM 冷热/网络抖动 - confirm 场景重试触发(LLM 非确定性可能不调 confirm 工具) - token 从 prompt_traces 表按时间窗口统计 bench_results.json:本地 uvicorn + xiaomi LLM + SQLite/MemorySaver 实测。 结果摘要(mean,5 runs): - 普通对话:v1 TTFT 9.8s / v2 14.1s(+43.8%)—— v2 graph 编译开销明显 - list_topics:v1 15.4s / v2 12.7s(-17.6%) - get_batch_job_status:v1 8.8s / v2 7.3s(-17.0%) - get_citation_tree:v1 18.0s / v2 11.7s(-34.7%) - skim_paper confirm:v1 10.2s / v2 15.3s(+49.8%)—— v2 含 2 请求 关键发现: 1. xiaomi LLM 方差极大(同 prompt TTFT 1.6s~27s),5 次 mean 仍噪声大。 2. 无工具场景 v2 明显慢于 v1(+43.8% TTFT),符合预期:graph 编译 + LangChain 消息转换 + MemorySaver checkpoint 是纯额外开销。 3. 含工具场景 v2 反而更快,但 LLM 方差大不能下定论。 4. 工具往返延迟两后端都在毫秒级,工具本身不是瓶颈。
🔍 OpenCode PR Review Required这是一个受保护的分支,merge 前需要进行 code review。 请运行以下命令进行 OpenCode review: 或者在 PR 页面评论 This is an automated reminder from PR Review Gate. |
优化:编译后的 graph 单例复用,不每请求重建。 - get_compiled_graph(checkpointer) 返回缓存的编译 graph,thread_id 在 运行时从 get_config() 读,不通过闭包捕获,使 graph 可跨请求共享。 - entry.py 三个入口改用 get_compiled_graph。 优化前后对比(5 runs mean,本地 xiaomi LLM + MemorySaver): 场景 1 普通对话:TTFT +43.8% → +23.1%,E2E +38.8% → +13.8% 场景 2-4 含工具:v2 普遍更快或持平(-20.5%/-32.6%/+8.8%) 场景 5 confirm:v2 慢 +100.4%(2 请求架构导致,非框架开销) graph 编译缓存有效消除每请求 build_graph 开销。bench_results_optimized.json 含原始数据。
🔍 OpenCode PR Review Required这是一个受保护的分支,merge 前需要进行 code review。 请运行以下命令进行 OpenCode review: 或者在 PR 页面评论 This is an automated reminder from PR Review Gate. |
scripts/bench_multiturn.py:两场景(10轮纯对话增长 + 10轮含confirm)+ 真实多工具任务案例。 bench_multiturn.json:实测原始数据。 BENCHMARK_ANALYSIS.md:完整对比分析。 关键发现: 1. 性能:无工具 +13.8%、含工具持平/更快、confirm +100%(2请求)、多轮不劣化 2. 真实案例:两后端 LLM 决策路径完全一致(框架不影响工具选择) 3. 框架优势:checkpoint持久化/增量存储/interrupt原生/可观测性/少写边界条件/并发安全 4. 劣势:无工具延迟/confirm 2请求/依赖重/confirm多轮稳定性待查 结论:工程优势明显,建议生产小范围验证后再决定是否整体替换。
🔍 OpenCode PR Review Required这是一个受保护的分支,merge 前需要进行 code review。 请运行以下命令进行 OpenCode review: 或者在 PR 页面评论 This is an automated reminder from PR Review Gate. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PoC 目标
验证 LangGraph 能复刻现有 agent 能力(27 工具 + confirm 流 + SSE 9 事件协议),与自研
StreamingAgentLoop并行。不合 main,部署实测后由你拍板是否整体替换。新增
packages/langgraph_agent/chat_model.pyPaperMindChatModel(BaseChatModel)包装现有LLMClient.chat_stream,复用 provider 路由(xiaomi/zhipu/openai)。bind_tools直接透传get_openai_tools()的 OpenAI spec(不漂移描述)。tool_call →AIMessageChunk(tool_call_chunks=[...])(args 为 JSON str)。tools_adapter.pyCONFIRM_NAMES从TOOL_REGISTRY派生;run_tool复用execute_tool_stream+get_stream_writer发 tool_start/progress/result。state.py+graph.pyrecursion_limit=24替代 max_rounds;confirm 工具interrupt()暂停,Command(resume={"confirmed":bool})恢复。checkpointer.pyPostgresSaver单例(psycopg v3,setup()自建表),SQLite 回退 MemorySaver;thread_id = conversation_id。sse_adapter.pystream_mode=["messages","custom","updates"]→ 现有 9 种 SSE;__interrupt__→ action_confirm;GraphRecursionError→ text_delta 提示 + done(修⑩同形)。entry.pystream_chat_v2/confirm_v2/reject_v2三个入口。新增路由
apps/api/routers/agent_v2.pyPOST /agent/v2/chat、/agent/v2/confirm/{id}、/agent/v2/reject/{id}agent.py的持久化辅助(_db_messages_to_openai/_stream_with_save_for_action/_resolve_conversation_id_from_action/_parse_sse_events),SSE 协议 + 持久化与/agent/chat完全一致。main.py懒挂载(未装 langgraph extra 时跳过,核心不受影响)。依赖
pyproject.toml新增langgraphextra(不进核心 dependencies):langgraph>=1.2.9+langgraph-checkpoint-postgres>=2.0+psycopg[binary]>=3.2+langchain-core>=0.3。Dockerfile.backend装依赖加,langgraph。测试(12 个,全绿)
test_langgraph_chat_model.py(6):chunk 形状 / bind_tools 透传 / usage 回调 / 消息转换test_langgraph_agent.py(6):auto 工具全流程 / confirm interrupt+resume / reject / SSE 协议对齐 / recursion 耗尽提示 / entry 入口tests/81 passed(69 老 + 12 新),2 skipped关键技术点(踩坑后修复)
tool_call_chunks[].args必须是 JSON 字符串(非 dict),符合 langchain 流式协议。AIMessage.tool_calls是顶层字段(langchain 1.x),不是additional_kwargs。invoke优先走_stream而非_generate(见_generate_with_cache),两者都需正确处理。get_stream_writer()在同步节点可用,stream_mode="custom"能消费。interrupt()检测在updates模式的__interrupt__键,Command(resume=...)用相同 thread_id 恢复。风险
/agent/v2/*不可用但核心不受影响(懒挂载已验证)。chat_stream现状),PoC 仅支持 openai 兼容 provider。PostgresSaver.setup()自建,不进 alembic chain。部署后实测计划
curl /agent/v2/chat跑一轮 search_papers,确认 SSE 9 事件对齐。/agent/chat(老)与/agent/v2/chat(新)同一问题的输出。