延伸阅读:
- ReAct 原始论文 —— Yao et al., 2022。(arXiv)
- Anthropic:Building Effective AI Agents —— 很适合理解 ReAct 这类“简单但有效”的 Agent 模式。(Anthropic)
- LangChain Agents 文档 —— 说明现代 Agent 运行本质上就是工具循环。(LangChain 文档)
ReAct 模式
flowchart LR
A["ReAct 模式"]
A --> B["分类:智能体 (Agents)"]
A --> C["关键词:Agent"]
A --> D["关键词:ReAct"]
A --> E["关键词:Reasoning"]
A --> F["关键词:Acting"]
上一篇讲 Agent 系统组成时提到,很多 Agent 的核心不是“先想完再做”,而是“边做边想、边看结果边调整”。ReAct 就是这种思路最经典、也最有代表性的形式。
它的重要性不在于名字本身,而在于它回答了一个非常实际的问题:
当任务的路径并不确定时,Agent 应该先做完整规划,再执行;还是应该先迈出一步,根据反馈再决定下一步?
ReAct 给出的答案是后者。 它把 Reasoning(推理)和 Acting(行动)交织起来,让系统不必在一开始就知道完整路径,而是在每一轮通过“思考 → 行动 → 观察”逐步逼近目标。原始论文把这种模式用于知识密集型推理与决策任务,作者报告说它相较于只做推理或只做行动的方式,在多类任务上都更有效,同时也更易解释。(arXiv)
这也是为什么 ReAct 会成为 Agent 讨论中的“基础模式”: 它不是最复杂的架构,却非常贴近现实任务的运行方式。
ReAct 是什么
先看定义:ReAct 是一种把推理与行动放进同一循环中的 Agent 模式,系统每走一步,都会根据最新观察结果决定下一步做什么。
更具体一点说,ReAct 的核心不是“让模型边想边说”,而是让模型在执行中持续接收外部反馈:
- 先根据当前信息做判断;
- 决定是否调用某个工具;
- 读取工具返回结果;
- 再基于新结果继续推理;
- 如此循环,直到能够回答或完成任务。
原始论文题目就叫 ReAct: Synergizing Reasoning and Acting in Language Models,其中的重点不是简单并列,而是 synergizing:推理和行动不是两个平行模块,而是相互增强。(arXiv)
这件事看似直观,实际上非常关键。 因为很多任务的难点并不在“模型知不知道答案”,而在于:
- 它当前掌握的信息不够;
- 它需要通过检索、读取、计算或执行动作获取新信息;
- 而获取什么信息,又取决于前一步推理的方向是否对。
所以,ReAct 适合解决的,不只是“复杂推理”,而是需要在环境交互中逐步推进的推理。
ReAct 的最小循环
ReAct 最经典的形式,通常写成:
Thought -> Action -> Observation -> Thought -> ...
也可以翻译成:
思考 -> 行动 -> 观察 -> 再思考
| 阶段 | 发生什么 |
|---|---|
| Thought | 模型基于当前状态判断下一步 |
| Action | 系统调用工具、执行操作或发起查询 |
| Observation | 工具返回结果,成为新的输入 |
| 循环 | 根据最新 Observation 再决定下一步 |
| Finish | 任务完成,输出最终答案或结果 |
先看定义:ReAct 不是“推理一次,执行一次”,而是让推理和执行在循环里不断互相修正。
这里最值得注意的一点是: Observation 不是装饰,它是 ReAct 成立的前提。 没有 Observation,所谓 ReAct 很容易退化成“模型自言自语地分步写过程”;只有当每一步都真的拿到外部反馈,ReAct 才是在和环境交互,而不是在 prompt 里演戏。
为什么 ReAct 有效:它解决的是“信息不够时怎么继续”的问题
要理解 ReAct 的价值,最好的方式不是背定义,而是看它替代了什么。
纯推理的局限
纯 Chain-of-Thought 的优点是: 模型会把中间推理写出来,看起来更有条理。
但它有一个根本限制: 它只能基于当前上下文和参数知识推理,不能主动去获取新信息。
这意味着一旦问题依赖:
- 实时事实;
- 长尾知识;
- 外部数据;
- 跨文档比对;
- 精确计算;
- 文件或代码库内容;
纯推理就很容易走向“有逻辑地胡说”。
纯行动的局限
另一种极端是: 让系统频繁调用工具,但不给清晰推理过程。
这类系统的常见问题是:
- 它会查很多东西,但不知道为什么查;
- 它能调用工具,但不会构造有效查询;
- 它能得到结果,但不会判断结果是否足够;
- 它可能连续做动作,却始终没形成清晰解题路径。
ReAct 的关键改进
ReAct 的有效性恰恰来自把两者接上:
- 推理给行动提供方向
- 行动给推理提供事实
- 事实再反过来修正推理
原始论文也是沿着这个逻辑展开:单纯推理缺少与环境交互,单纯行动缺少显式推理,而两者结合后,在知识密集型问答和决策任务中表现更好。(arXiv)
先看定义:推理解决“下一步为什么要这么做”,行动解决“现在还缺什么信息”。
一个比定义更重要的理解:ReAct 本质上是局部决策
ReAct 很容易让人误以为它是一种“更聪明的完整规划方法”。 其实恰恰相反,它更像一种局部最优推进策略。
这意味着,ReAct 通常不是:
- 先生成完整任务树;
- 再按树稳定执行。
而是:
- 先看当前状态;
- 选一个当前最合理的下一步;
- 等拿到反馈后再重新判断。
这带来两个直接后果:
好处:适应性强
如果环境是开放的、信息是不完整的、工具返回值会改变后续路径,那么局部决策往往比预先写死整条路线更实用。
例如:
- 搜索结果会告诉你原先的假设不对;
- 文件内容会暴露一个新线索;
- API 返回错误会迫使你换方案;
- 用户中途补充约束会打断旧计划。
ReAct 对这些变化天然更敏感。
代价:缺少全局视角
它也因此容易出现“走一步看一步”的问题:
- 局部上每一步都合理,但整体路线并不经济;
- 明明该先做高层规划,却一直在局部搜索;
- 任务已经偏航,系统还在继续补局部信息;
- 本来三步能完成,它绕了十步。
这也是为什么 ReAct 很强,但并不等于它适合所有 Agent 任务。Anthropic 关于 Agent 的实践文章就明确强调,最成功的系统通常使用简单、可组合的模式;但“简单有效”不等于“适用于所有问题”。(Anthropic)
一个研究型 Agent 的 ReAct 示例
假设用户问:
“比较 BERT 和 GPT 在文本分类任务上的表现,并各举一篇代表性论文。”
一个更接近 ReAct 的运行轨迹,通常不是一开始就写出一整段对比,而更像这样:
Thought: 我需要先确认 BERT 在文本分类中的代表性论文和常见表现。
Action: search("BERT text classification representative paper")
Observation: 返回 BERT 原论文及若干下游分类任务相关结果
Thought: 还需要 GPT 在文本分类中的代表性工作,继续搜索 GPT 相关结果。
Action: search("GPT text classification representative paper")
Observation: 返回 GPT 系列与若干文本分类应用结果
Thought: 仅有代表性论文还不够,需要找到更直接的比较或综述。
Action: search("BERT vs GPT text classification comparison")
Observation: 返回综述、博客、论文比较材料
Thought: 信息已足够形成结构化回答,可以总结两者在文本分类中的差异,并各给出代表论文。
Action: finish
这里最关键的不是格式,而是行为特征:
- 它没有假设自己一开始就知道全部答案;
- 它先获取信息,再决定是否还缺什么;
- 它的下一步由 Observation 驱动,而不是由预设脚本写死。
这就是 ReAct 的核心价值: 它把“我还不知道,但我可以先去查一下再决定”变成了系统行为。
ReAct 和工具调用的关系:不是所有工具调用都是 ReAct
一个常见误区是: 只要模型会调用工具,就是 ReAct。
这并不准确。
因为很多工具调用系统其实只有一轮:
- 模型判断要不要调工具;
- 调一次;
- 返回结果;
- 结束。
这更像“带工具的单轮推理”,而不是 ReAct。
ReAct 的关键不只是“用了工具”,而是:
- 工具调用出现在循环中;
- Observation 会改变下一步推理;
- 模型可能进行多轮行动,而不是只执行一次工具调用。
所以,判断一个系统是否真的使用 ReAct,可以看三个问题:
- 工具调用后,系统会不会把结果重新纳入推理?
- 它会不会根据新结果改写下一步动作?
- 它是否允许多轮“思考—行动—观察”持续推进?
只有答案大致都是“会”,它才更接近 ReAct。
ReAct 的实现:真正难的不是 prompt,而是循环运行时
很多教程喜欢把 ReAct 讲成一种 prompt 模板,这当然没错,但只讲 prompt 很容易让人低估实现难度。
Prompt 是入口,但不是全部
一个最基本的 ReAct prompt,大致会要求模型按某种格式输出:
- Thought
- Action
- Observation(由系统填回)
- 最终 Finish
例如:
You can use the following tools:
- search(query: str)
- read_file(path: str)
For each step, think about the next best action.
Use the format:
Thought: ...
Action: tool_name(arguments)
When you have enough information, output:
Thought: ...
Action: finish
这类 prompt 的作用很明确: 让模型显式地把“当前判断”和“下一步动作”表达出来。
但工程上更重要的是后半段:
- 解析 Action;
- 校验参数;
- 执行工具;
- 把结果格式化成 Observation;
- 写回上下文;
- 进入下一轮。
这意味着,ReAct 的可靠性不只是 prompt 问题,而是 prompt + parser + tool runtime + state update 的组合问题。
解析层为什么经常是事故点
原文已经提到输出解析需要鲁棒,这一点非常重要,而且通常比想象中更重要。
因为 LLM 的输出可能出现各种现实问题:
- 工具名拼错;
- 括号不闭合;
- 参数不是合法 JSON;
- Thought 和 Action 混在一起;
- 输出多余解释;
- 模型“自作主张”写了一个不存在的工具。
如果你的 ReAct 实现把模型输出当成绝对可信的程序输入,系统很容易在第三步就崩。
所以更稳妥的做法通常是:
- 使用结构化输出约束;
- 对工具名和参数做 schema 校验;
- 解析失败时,不要静默失败,而是把错误作为 Observation 回灌给模型;
- 对高风险工具额外加非模型层检查。
先看定义:ReAct 看起来像“模型在循环”,但真正把循环撑住的是运行时控制层。
Structured ReAct:为什么很多工程实现不再执着于自然语言格式
ReAct 的经典写法来自论文里的 Thought / Action / Observation 文本格式。
它非常适合展示思路,也便于人类阅读。
但到了工程实践里,越来越多系统会把 Action 做成更结构化的形式,例如:
- JSON
- XML
- 函数调用 schema
- 明确的 tool call 对象
原因很现实:
- 更容易解析;
- 更容易校验;
- 更适合和工具调用 API 对接;
- 更容易做日志、追踪和回放。
LlamaIndex 当前文档里就把 ReAct agent 描述为一种会提示模型“调用工具 / 函数,或者返回最终响应”的工作流;它的示例也越来越强调 stateful workflow 和 memory,而不只是老式文本解析。(LlamaIndex OSS Documentation)
这说明一件事: ReAct 作为行为模式依然重要,但它的工程承载形式正在越来越结构化。
ReAct 的优势
1. 自然适合开放式任务
如果任务路径本来就不确定,比如调研、故障排查、跨文件分析、网页搜索、代码定位,ReAct 往往比硬编码流程更顺手。
因为它允许系统根据中间发现改变路径,而不是强行沿着一条最初写下来的路线走到底。
2. 可解释性相对更好
原始论文也强调了这一点:相较于没有显式推理和动作分解的方法,ReAct 更容易让人理解系统是如何一步步走到结论的。(arXiv)
当然,这里的“可解释”要克制理解。 它不是说 Thought 一定是真实内部思维,而是说:
- 你可以看到系统每一步做了什么;
- 你能回看工具调用和中间状态;
- 你更容易定位到底是哪一步走偏了。
这对调试尤其重要。
3. 实现门槛不高
和先做 Planner 再做 Executor 的复杂分层相比,ReAct 的最小闭环相对简单:
- 一个模型;
- 一组工具;
- 一个循环;
- 一个停止条件。
这也是为什么很多框架和教程都把它当作 Agent 的起点。
4. 与现实工具环境兼容性强
现代 Agent 框架本质上都在实现“工具循环”。LangChain 当前官方文档就直接把 Agent 定义为:模型在循环中运行工具,以达到目标,直到输出最终结果或达到迭代上限。(LangChain 文档)
从这个角度看,ReAct 不是一个“过时论文模式”,而是很多实际 Agent runtime 的思想底层。
ReAct 的弱点,不是“不够聪明”,而是“太容易局部合理”
1. 容易陷入循环
这是最经典的问题。
系统可能出现:
- 重复搜索同一类问题;
- 连续读取价值不高的文件;
- 一直不愿结束;
- 工具失败后不断重试同一路径;
- 因为 Observation 不够有用,始终在原地打转。
因此,ReAct 实现几乎都需要显式终止条件:
- 最大步数;
- 总超时;
- 相似 Action 重复检测;
- 工具失败计数;
- 预算上限;
- 人工中断。
2. 成本会随步数线性甚至超线性上升
每一轮 ReAct 通常都意味着:
- 一次模型调用;
- 可能的一次工具调用;
- 历史上下文变长;
- 观测结果被再次送回模型。
如果不做上下文压缩和状态裁剪,任务一长,成本和延迟都会快速上升。
所以,ReAct 的问题从来不是“能不能做”,而是“值不值得这样做”。 对一个两步就能完成的固定任务,ReAct 可能只是昂贵的过度设计。
3. 错误会在循环中放大
如果某一步 Thought 已经误解了问题,后面所有 Action 都可能围绕错误假设展开。 而且由于每一步都有 Observation,这种错误经常会被包装得更“像是真的”。
这意味着,ReAct 的循环能力会增强系统,也会增强偏差的持续性。
4. 缺少全局规划
这点尤其适合和下一篇做衔接。
ReAct 很适合:
- 边查边做;
- 根据反馈调整;
- 在不确定环境里探索。
但如果任务本身有明显的全局结构,例如:
- 明确的多阶段流程;
- 强顺序依赖;
- 长链条任务;
- 需要提前资源分配或阶段划分;
那“走一步看一步”就不总是最优。 这也是为什么后续会出现 Plan & Execute、Reflection、Router 等更复杂模式。
何时适合用 ReAct
| 场景 | ReAct 是否合适 | 原因 |
|---|---|---|
| 开放式信息检索 | 很合适 | 需要根据检索结果动态调整路径 |
| 故障排查 | 很合适 | 每一步新证据都会改变后续判断 |
| 代码库探索 | 很合适 | 读到哪个文件、发现哪个调用链,会影响下一步 |
| 简单固定流程 | 未必合适 | 预定义 workflow 可能更便宜、更稳 |
| 长程复杂任务 | 部分合适 | 可以作为执行内核,但往往需要更高层规划 |
| 高风险动作自动化 | 需要谨慎 | 必须叠加确认、审计和执行约束 |
一个简单判断标准是:
如果你无法在任务开始时准确写出完整路径,但又能通过中间观察逐步收敛,ReAct 往往是合适的。
ReAct 在框架里的现实位置:思想仍在,接口在变化
原文提到 LangChain 和 LlamaIndex 的实现,这里需要稍微更新一下表述。
LangChain
LangChain 当前官方文档强调,Agent 的本质是“模型在循环中运行工具直到满足停止条件”,并把 create_agent 作为 LangChain 1.0 的标准构建方式。官方迁移文档同时说明,过去常用的 create_react_agent 作为 prebuilt 形态,已被更统一的 create_agent 路径取代。(LangChain 文档)
这意味着: ReAct 仍然是重要思想,但在 LangChain 的新 API 里,你不一定总是直接写“ReAct agent”这个名字。
LlamaIndex
LlamaIndex 仍然提供明确的 ReActAgent 和从零构建 ReAct workflow 的示例,官方文档也强调这种 agent 本质上是:模型在状态化工作流中决定调用工具或返回最终响应。(LlamaIndex OSS Documentation)
这说明另一件事:
ReAct 作为实现范式仍然活跃,但各家框架越来越把它纳入更通用的 agent runtime 和 workflow 体系里。
实践中的实现要点
1. 让 Observation 尽量“可用”,而不是尽量“原始”
工具返回值不应该无脑整段塞回模型。 否则常见问题是:
- 返回信息太长;
- 格式太乱;
- 关键字段不突出;
- 无关内容淹没重要线索。
更好的做法通常是:
- 保留原始结果供日志追踪;
- 给模型返回结构化或摘要化后的 Observation;
- 明确错误类型和状态码;
- 对超长返回做裁剪或摘要。
2. Thought 不必无限展开
ReAct 的价值不是让模型写长篇思维独白。 过长的 Thought 常见问题包括:
- token 成本上升;
- 重复表达;
- 把局部不确定性说得像确定推理;
- 增加解析和日志噪声。
工程上更好的目标通常是: 让 Thought 足够表达“为什么下一步这么做”,而不是追求“推理过程越长越好”。
3. 从一开始就设计停止逻辑
一个可用的 ReAct agent,至少应明确:
- 最大步数;
- 单步和总时长上限;
- 重复动作检测;
- 工具错误上限;
- finish 的判定条件;
- 什么情况下必须交还给用户或人工。
很多所谓“ReAct 不稳定”的问题,其实不是模式本身的问题,而是系统根本没有终止设计。
4. 保持工具集合小而清晰
工具太多、描述太像、参数太复杂,都会让 ReAct 的决策变差。 如果一个 Agent 的工具集已经足够让人类程序员翻文档半天,模型通常也不会用得很好。
5. 记录每一步,但不要过度相信每一步 Thought
ReAct 很适合 tracing。 你应该记录:
- 每轮输入状态;
- Thought;
- Action;
- Observation;
- 工具耗时;
- 是否触发降级或错误。
但这些记录更适合拿来做调试和行为分析,而不是直接当作“系统真实内部认知”的严格证据。
ReAct 的几种常见扩展
ReAct + Reflection
在若干轮之后插入“自检”步骤,让模型判断:
- 当前路径是否有效;
- 是否陷入重复;
- 是否缺少关键信息;
- 是否应该换策略。
这能缓解 ReAct 的局部贪心问题,但也会增加成本。
ReAct + 粗规划
先做一个轻量级计划,再在每个子阶段内部使用 ReAct。 这通常比纯 ReAct 更适合中长任务,也比重型 Planner 更容易起步。
Structured ReAct
用结构化 tool call、JSON 或函数调用替代自由文本 Action。 这不是背离 ReAct,而是把 ReAct 的思想工程化。
核心概念速查
| 概念 | 一句话 |
|---|---|
| ReAct | 把推理与行动交织进同一循环的 Agent 模式 |
| Thought | 当前状态下对下一步行动的判断 |
| Action | 实际调用工具或执行动作 |
| Observation | 工具返回的外部反馈,用来修正下一步 |
| Finish | 任务结束信号,不再继续循环 |
| 优势 | 灵活、适应性强、便于调试、适合开放环境 |
| 局限 | 容易循环、成本上升、偏差放大、缺少全局规划 |
小结
ReAct 的价值,不在于它把“思考”和“行动”这两个词拼在一起,而在于它抓住了很多真实任务的本质: 你往往不可能在一开始就知道完整路径,必须先走一步、看一眼结果、再决定下一步。
这使它特别适合开放式检索、故障排查、代码探索、研究辅助等任务,也让它成为很多 Agent 系统最自然的起点。它的强项是适应性和可调试性,弱项则是容易陷入局部合理、全局低效的循环。因此,ReAct 更像 Agent 的“基础运动方式”,而不是所有复杂任务的终局架构。
下一篇要讲的 Plan & Execute,正好就是对 ReAct 的一个重要补充: 当“边走边看”不够时,什么时候该先站高一点看全局,再开始执行?