Agent 的 ReAct 循环:让模型在「思考」与「行动」之间闭环
从 ReAct 论文出发讲清 Agent 的核心骨架:Thought → Action → Observation 的循环结构、为什么它能减少幻觉,以及工程落地的关键注意事项。
摘要:ReAct(Reasoning + Acting)把「推理轨迹」与「工具调用」交织成一个循环:Thought → Action → Observation,然后重复,直到模型给出最终答案。本文从论文动机讲到循环结构,给出可直接落地的伪代码,并梳理工程落地的取舍。
从两个失败的极端说起
在 ReAct 之前,「让语言模型完成复杂任务」有两条路线,各有明显缺陷:
- 纯推理(Chain-of-Thought):模型只能「想」,无法访问外部信息或执行工具。答案完全依赖训练数据里的记忆,遇到需要实时数据或精确计算的场景就会出错——而且错得很有自信。
- 纯行动(Act-only,如 WebGPT 的早期形态):模型会调用搜索、计算器等工具,但没有推理轨迹引导。它容易「走一步看一步」:一次失败的行动会直接污染后续所有步骤,错误像滚雪球一样累积。
ReAct 论文(Yao et al., 2022,标题 ReAct: Synergizing Reasoning and Acting in Language Models)的主张很简单:把两者交替起来。推理轨迹(Thought)引导下一步行动,而工具返回的观察(Observation)反过来修正推理。两者互相校准,形成闭环。
循环结构:Thought → Action → Observation
一次典型的 ReAct 轨迹长这样:
Thought: 我需要查一下天气 API 的返回格式,再决定如何回答。
Action: search["weather api docs"]
Observation: {"city": "Shanghai", "temp_c": 28, "forecast": {"tomorrow": {"high": 32}}}
Thought: 用户问的是「明天会不会热」,我需要拿到明天的预报。
Action: search["Shanghai tomorrow weather"]
Observation: {"date": "tomorrow", "high": 32, "condition": "sunny"}
Thought: 明天 32°C 且晴天,可以给出结论了。
Action: finish["明天上海 32°C,晴天,比较热,建议穿短袖。"]
关键在于:模型的每一步都有依据。它先说明「为什么这样做」(Thought),再执行(Action),最后根据真实结果(Observation)决定下一步。整个循环受两个约束保护——工具调用失败不致命(失败会作为 Observation 返回,模型可以自己纠错),推理跑偏也能被观察结果拉回来。
可落地的伪代码
把上面的循环翻译成代码,核心不超过 20 行:
async function runReAct(goal: string, tools: Tool[], maxIterations = 25): Promise<string> {
const messages: Message[] = [{ role: "user", content: goal }];
for (let i = 0; i < maxIterations; i++) {
// 1. 模型输出:可能包含 Thought + Action,或直接给出最终答案
const output = await llm.complete(messages);
if (output.type === "final") {
return output.answer; // 模型认为任务完成
}
// 2. 解析出结构化的工具调用
const action = parseToolCall(output.text);
// 3. 执行工具,拿到 Observation
const result = await executeTool(action.name, action.args);
// 4. 把这次交互追加回上下文,进入下一轮
messages.push(
{ role: "assistant", content: output.text },
{ role: "tool", content: result, toolCallId: action.id },
);
}
throw new Error(`达到最大迭代次数 ${maxIterations},任务未完成`);
}
这段代码就是一个最小可用的 Agent 骨架。你正在使用的很多 coding agent(包括本站作者日常使用的工具)本质上都是这个循环的工程化版本:模型在循环里读文件、执行命令、观察结果,直到任务完成或触达迭代上限。
为什么 ReAct 有效
| 收益 | 机制 |
|---|---|
| 减少幻觉 | 关键事实来自工具的真实观测,而不是模型的记忆 |
| 错误可恢复 | 一次工具失败只是一条 Observation,模型能据此换策略 |
| 可观测、可调试 | Thought / Action / Observation 全程留痕,失败可以精确定位到某一轮 |
| 复杂任务可分解 | 推理轨迹天然把大目标拆成一连串可验证的小步骤 |
工程落地的五个要点
- 工具协议要结构化:用 JSON schema 声明工具参数,比让模型自由写 shell 命令可靠得多;解析失败要有重试与报错路径。
- 循环上限必须有:
maxIterations同时是成本上限和死循环保险,没有它的一次幻觉可能让 token 账单失控。 - 错误当作 Observation 返回:工具抛异常时,把错误信息作为正常的 Observation 交给模型,让它自行纠错,而不是中断整个循环。
- 流式推送轨迹:把每一轮的 Thought 实时流给 UI,用户能「看着它思考」,体验和信任感都大幅提升。
- 持久化 messages:循环状态就是消息数组,落盘后天然支持恢复、回放和审计。
局限与取舍
ReAct 不是银弹,有三个代价要认清:
- 成本线性增长:每一轮都是一次完整 LLM 调用,token 消耗随迭代次数直线上升。
- 错误会累积:一旦某条 Observation 被污染(比如工具返回了误导性数据),后续推理都会建立在错误之上。
- 简单任务不该用:一句话能回答的问题,跑 5 轮循环纯属浪费。这也是很多 Agent 系统引入「路由」的原因——简单任务直接单次回答,困难任务才进入循环。
小结
ReAct 之所以成为 Agent 的主流骨架,是因为它把语言模型最强的部分(推理)和最弱的部分(精确执行、实时信息)用循环补全了:思考引导行动,行动反馈思考。理解了这一个循环,你就理解了 Agent 的「心脏」——剩下的多智能体协作、规划器-执行器、记忆系统,都是在这个循环之上生长的。
下一篇可以聊聊:当单循环不够用时,如何在 ReAct 之上叠加「规划」层,让 Agent 处理更长时间跨度的任务。