Skip to content

feat(agent): LangGraph PoC —— /agent/v2/* 并行老 /agent/chat(不合 main) - #58

Open
Color2333 wants to merge 5 commits into
mainfrom
feat/langgraph-poc
Open

feat(agent): LangGraph PoC —— /agent/v2/* 并行老 /agent/chat(不合 main)#58
Color2333 wants to merge 5 commits into
mainfrom
feat/langgraph-poc

Conversation

@Color2333

Copy link
Copy Markdown
Owner

PoC 目标

验证 LangGraph 能复刻现有 agent 能力(27 工具 + confirm 流 + SSE 9 事件协议),与自研 StreamingAgentLoop 并行。不合 main,部署实测后由你拍板是否整体替换。

新增 packages/langgraph_agent/

文件 作用
chat_model.py PaperMindChatModel(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.py CONFIRM_NAMESTOOL_REGISTRY 派生;run_tool 复用 execute_tool_stream + get_stream_writer 发 tool_start/progress/result。
state.py + graph.py StateGraph ReAct(agent → should_continue → tools → agent),recursion_limit=24 替代 max_rounds;confirm 工具 interrupt() 暂停,Command(resume={"confirmed":bool}) 恢复。
checkpointer.py PostgresSaver 单例(psycopg v3,setup() 自建表),SQLite 回退 MemorySaver;thread_id = conversation_id
sse_adapter.py stream_mode=["messages","custom","updates"] → 现有 9 种 SSE;__interrupt__ → action_confirm;GraphRecursionError → text_delta 提示 + done(修⑩同形)。
entry.py stream_chat_v2 / confirm_v2 / reject_v2 三个入口。

新增路由 apps/api/routers/agent_v2.py

  • POST /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 新增 langgraph extra(不进核心 dependencies):langgraph>=1.2.9 + langgraph-checkpoint-postgres>=2.0 + psycopg[binary]>=3.2 + langchain-core>=0.3Dockerfile.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

关键技术点(踩坑后修复)

  1. tool_call_chunks[].args 必须是 JSON 字符串(非 dict),符合 langchain 流式协议。
  2. AIMessage.tool_calls 是顶层字段(langchain 1.x),不是 additional_kwargs
  3. langchain invoke 优先走 _stream 而非 _generate(见 _generate_with_cache),两者都需正确处理。
  4. get_stream_writer() 在同步节点可用stream_mode="custom" 能消费。
  5. interrupt() 检测在 updates 模式的 __interrupt__Command(resume=...) 用相同 thread_id 恢复。

风险

  • 未装 langgraph extra 时,/agent/v2/* 不可用但核心不受影响(懒挂载已验证)。
  • Anthropic tool 流式仍不支持(继承自 chat_stream 现状),PoC 仅支持 openai 兼容 provider。
  • checkpoint 表由 PostgresSaver.setup() 自建,不进 alembic chain。

部署后实测计划

  • curl /agent/v2/chat 跑一轮 search_papers,确认 SSE 9 事件对齐。
  • 跑一轮 skim_paper(confirm),确认 action_confirm → /agent/v2/confirm → action_result 全链路。
  • 对比 /agent/chat(老)与 /agent/v2/chat(新)同一问题的输出。
  • 验证 checkpoint 表在 PG 里生成且 resume 正确。

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。
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


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 后已清理
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


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. 工具往返延迟两后端都在毫秒级,工具本身不是瓶颈。
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


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 含原始数据。
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


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多轮稳定性待查
结论:工程优势明显,建议生产小范围验证后再决定是否整体替换。
@github-actions

Copy link
Copy Markdown

🔍 OpenCode PR Review Required

这是一个受保护的分支,merge 前需要进行 code review。

请运行以下命令进行 OpenCode review:

/oc review https://github.com/Color2333/PaperMind/pull/$PR_NUM

或者在 PR 页面评论 /oc 来触发 OpenCode review。


This is an automated reminder from PR Review Gate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant