← 博客

经典 ReAct Agent Loop 改造

记录日期:2026-04-02

1. 背景

旧行为:工具执行完后直接到 response 透传,工具结果(如 list_plans 返回的原始 JSON)直接展示给用户,无 LLM 再加工。

问题:用户看到的是 [工具成功] list_plans\n{...} 而非自然语言总结;与经典 Agent Loop 语义不符(工具结果应回到 LLM 生成最终回答)。

目标:改为经典 ReAct 模式——工具执行结果追加进 messages 后,必须再经 intent_recognition 调用 LLM,由 LLM 决定继续调工具、结束对话或进入「待确认写库」路径。

2. 图结构(与 agent/graph.py 一致)

3. 执行流程(简图)

                    ┌─ chat / error ──────────────────────► response ─► END
                    │
intent_recognition ─┤─ execute_confirmed(App 已 confirm)──► execute_tools
                    │         │
                    │         └─►(完毕)route_after_execute_tools
                    │                   ├─ 有 staged_mutating_calls ─► stage_mutating_pending ─► persistence ─► response ─► END
                    │                   └─ 否则 ─────────────────────► intent_recognition(再调 LLM,形成闭环)
                    │
                    ├─ tool_call_staged_reads ─► execute_tools ──(同上)
                    │
                    ├─ tool_call / mixed:需确认且 pending 非空 ─► persistence ─► response ─► END
                    │                      (本轮结束;等用户 confirm 下一次请求)
                    │
                    └─ tool_call / mixed:只读或已带 confirmed ─► execute_tools ──(同上)

说明

4. 关键路由逻辑

4.1 route_after_intentintent_recognition 出发)

| 条件 | 目标 | 说明 |

| _loop_after_toolspending_tool_calls 非空 | execute_tools | 刚从工具侧返回且仍有待执行调用(如 confirm 注入;以合并后状态为准) | | _loop_after_toolspending 为空 | response | 工具链在本轮收束,直接收束输出 | | intent == "execute_confirmed" | execute_tools | App 确认后必须执行 | | intent == "tool_call_staged_reads" | execute_tools | 先执行只读批次 | | intent in ("chat","error") 等 | response | 纯对话或错误展示 | | needs_confirmationpending_tool_calls 非空 | persistence | mutating 需落库待确认 | | intenttool_call / mixed / client_tool 且可自动跑服务端工具 | execute_tools | 只读白名单或已 confirmedpending 对齐 |

intent_recognition_node 入口会 pop_loop_after_tools主循环通常是:execute_toolsintent_recognition(LLM)→ 按 intent / needs_confirmation 再路由。

4.2 route_after_execute_toolsexecute_tools 出发)

| 条件 | 目标 |

| staged_mutating_calls 非空 | stage_mutating_pending | | 否则 | intent_recognition(经典 ReAct:带着 ToolMessage 再问过 LLM) |

5. 状态标记 _loop_after_tools

execute_tools 在返回里置 _loop_after_tools=True,供 route_after_intent 识别「刚从工具节点出来」的分支;intent_recognition 开头会清除该标记,避免与下一轮混淆。与 仅以 intent 字段路由 的组合以线上 graph.py + 日志 为准。

6. 工具执行后的状态清理

execute_tools 在一轮成功路径末尾会 pending_tool_calls / confirmed_tool_calls 等置空并清除 needs_confirmation(具体见 execute_tools_node),避免死循环;待确认会话的权威来源是 NoSQL persistence 写入,不在此重复携带 pending 进下一轮 SSE。

与 App 契约persistence_node 合并回图状态时应保留或设置 needs_confirmation: true(仅清空内存侧 pending_tool_calls),以便 STATE_SNAPSHOT 触发确认 UI(详见《Agent状态持久化 2.9 / 2.10》)。

7. 确认机制与 ReAct 的衔接

不在 intent_recognition 内根据 waiting_confirmation 自动当「已确认」执行写工具,避免与确认 UI 脱节。

8. 实际踩坑记录(格式见 docs/开发记录博客/agents.md

以下为 2025–2026 年联调/真机路径上真实出现的问题,按仓库约定的博客结构(问题背景 → 问题列表 → 根因链 → 方案 → 采纳 → 用例 → 小结)整理,便于与《Agent状态持久化》§2.10 / §2.11 交叉索引。

8.1 问题背景

用户表述类似「给我创建一个计划,晨跑」时,观测到:

8.2 问题列表

8.2.1 未确认即执行写库(同轮偷跑 create_plan

根因(证据级)

方案(每题不超过两个)

优点:改动集中、与集成测「提案轮不执行写工具」一致。缺点:强依赖 App 确认请求继续携带 confirm + 约定 messages。

优点:可读、可审计。缺点:要梳理所有进入 persistence 的场景。

采纳方案

补充用例

8.2.2 App 不展示「确认执行」(needsConfirmation 恒 false)

根因(证据级)

方案

优点:与现有 App 协议对齐。缺点:checkpoint 中该位需由 confirm/执行完成流正确清掉(见 execute_tools_node)。

优点:契约稳定。缺点:要动 SDK 与客户端解析。

采纳方案

补充用例

8.2.3 计划标题整句入库(非短标题「晨跑」)

根因(证据级)

方案

优点:与 App new-plan.tsx 人工建计划的短标题体验一致。缺点:依赖中文话术启发式,需随反馈加规则或用例。

优点:少魔术字符串。缺点:成本与延迟。

采纳方案

补充用例

8.3 小结(本组踩坑)

9. 小结(改造目标回顾)

参考shenmiren-agent/agent/graph.py、《开发记录博客/Agent状态持久化(中断需用户确认场景).md》(§2.10 / §2.11)、docs/开发记录博客/agents.md(博客结构模板)。