ReAct 模式

Reasoning + Acting 如何交织、ReAct 循环的运作方式、优势与局限,以及实践中的实现要点

16 min read Part of Agent · Ch. 3

延伸阅读

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

这里最关键的不是格式,而是行为特征:

  1. 它没有假设自己一开始就知道全部答案;
  2. 它先获取信息,再决定是否还缺什么;
  3. 它的下一步由 Observation 驱动,而不是由预设脚本写死。

这就是 ReAct 的核心价值: 它把“我还不知道,但我可以先去查一下再决定”变成了系统行为。

ReAct 和工具调用的关系:不是所有工具调用都是 ReAct

一个常见误区是: 只要模型会调用工具,就是 ReAct。

这并不准确。

因为很多工具调用系统其实只有一轮:

  1. 模型判断要不要调工具;
  2. 调一次;
  3. 返回结果;
  4. 结束。

这更像“带工具的单轮推理”,而不是 ReAct。

ReAct 的关键不只是“用了工具”,而是:

  • 工具调用出现在循环中;
  • Observation 会改变下一步推理;
  • 模型可能进行多轮行动,而不是只执行一次工具调用。

所以,判断一个系统是否真的使用 ReAct,可以看三个问题:

  1. 工具调用后,系统会不会把结果重新纳入推理?
  2. 它会不会根据新结果改写下一步动作?
  3. 它是否允许多轮“思考—行动—观察”持续推进?

只有答案大致都是“会”,它才更接近 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 的作用很明确: 让模型显式地把“当前判断”和“下一步动作”表达出来。

但工程上更重要的是后半段:

  1. 解析 Action;
  2. 校验参数;
  3. 执行工具;
  4. 把结果格式化成 Observation;
  5. 写回上下文;
  6. 进入下一轮。

这意味着,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 对象

原因很现实:

  1. 更容易解析;
  2. 更容易校验;
  3. 更适合和工具调用 API 对接;
  4. 更容易做日志、追踪和回放。

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 的一个重要补充: 当“边走边看”不够时,什么时候该先站高一点看全局,再开始执行?