ReAct - Synergizing Reasoning and Acting in Language Models
[!ReAct 的术语黑称]
- thought: 模型自言自语写出的 reasoning trace, 不出现在环境里
- action: 模型对外发出的工具调用 / 环境指令, 会被执行
- observation: 工具或环境返回给模型的下一段输入
- trajectory: thought action observation thought … 的整条交替序列
- act-only: 没有 thought, 只对外发 action 的 baseline (WebGPT, SayCan 那一派)
- CoT-only: 全程在脑子里推, 不调外部工具的 baseline (Wei et al. 2022)
为什么要把 reasoning 和 acting 放一起讲
ReAct 之前, LLM 的"推理"和"动作"基本是两个互不相交的研究分支:
- reasoning 分支 以 Chain-of-Thought (CoT) 为代表, 让模型在回答之前先输出一段 “Let’s think step by step” 的中间过程; 但所有 token 都是模型自己生成的, 没有任何外部世界的输入, 一旦中间一步算错或者记错事实, 后面就一路错下去, 没有 grounding;
- acting 分支 以 WebGPT, SayCan, Inner Monologue 这些工作为代表, 让模型直接对一个外部环境 (browser, robot, text game) 输出 action, 由环境给出 observation, 然后下一步继续输出 action; 但这一派几乎不让模型显式地写 reasoning, 模型从 observation 到下一个 action 之间是黑箱;
ReAct 的核心动机就一句话: 这两件事本来就该一起做; 人类自己做复杂任务的时候, 既会动手 (acting) 也会在脑子里规划 / 反思 / 修正 (reasoning), 把这两类 token 混在同一条 trajectory 里, 让 LLM 自己决定下一个 token 是 thought 还是 action, 是这个工作最朴素的 insight;
前置基础: Chain-of-Thought 的局限
CoT 把 prompt 改成
形式上让模型先写一段推理再给答案, 已经在 GSM8K 这种纯算术 / 纯符号任务上有巨大提升; 但 CoT 有两个根本痛点:
- hallucination: 模型没有外部知识源, 只能凭参数里记得的事实"假装查到", 一旦记错就是错; 在 HotpotQA, FEVER 这种 knowledge-intensive QA 上经常会非常自信地编造一个看起来合理的实体或日期;
- error propagation: CoT 是一次性 sampling, 中间任何一步出错都不可逆, 后续步骤会继续在错误前提上推理;
CoT-SC (self-consistency) 用 sample 多条 rationale 再投票的方式部分缓解了第二个问题, 但仍然解决不了第一个问题, 因为所有 sample 共享同一份过时的内部知识;
前置基础: act-only agent 的局限
另一边, 一个纯 action-only 的 agent 拿到 observation 之后只能输出下一个 action, 比如:
1 | Action 1: search[Apple Remote] |
这种交互的问题在于:
- 模型对为什么要发这个 action 没有显式表示, 调试者只能从 action 序列倒推意图;
- 当 observation 很长或者跑题, 模型没有"我要做的事是 X, 当前还差 Y"这种 working memory 形式的痕迹, 很容易陷入循环 (反复 search 同一个词) 或者忘记原始问题;
- 任务一旦需要多跳推理 (multi-hop QA, compositional question), act-only 完全没有办法在 observation 之间做组合;
论文定位
ReAct 不是一个新模型, 也不需要额外训练 (主要实验是 prompt-only, few-shot in-context), 它是一种输出协议: 让 LLM 在解决任务时生成一种交替结构的 trajectory, 把"想"和"做"显式地交叉起来; 通过这个协议, 同一个 frozen LLM 既能利用外部工具拿到 grounding, 又能保留 CoT 那种透明的 reasoning trace;
论文解决的核心问题
现有方案的不足:
- CoT-only: 没有外部 grounding, 容易 hallucinate, 错了不可逆
- act-only: 没有 working memory, 没有计划性, 调试不可读
- CoT + retrieval (一次性 RAG): retrieval 只发生在 forward 之前, 中途无法根据 reasoning 改变检索意图
ReAct 试图回答:
- 能不能让 reasoning trace 和 tool action 共享同一个生成过程, 让 thought 来规划下一个 action, 让 observation 来修正后续 thought?
- 在只改 prompt, 不改模型的前提下, 这种交替结构能不能在 knowledge-intensive QA 和 sequential decision-making 两类任务上同时拿到收益?
- 写出来的 thought 本身能不能作为 interpretability 和 human-in-the-loop 修正的入口?
核心机制: thought action observation 的交替
ReAct 把 trajectory 定义为如下序列:
- : 第 步的 thought, 是模型自己生成的自然语言, 不发给环境, 只是写进 context 给后续 token 看;
- : 第 步的 action, 在固定的 action space 里取值, 会被 parser 抽出来发给环境;
- : 环境对 的返回, append 回 context;
形式上, ReAct 等价于把"reasoning trace"也写进了 action 历史, 但只标记一类不可观测的 action, 这类 action 不改变环境状态, 只改变 LLM 自己的 context; 这是它和经典 RL agent 最大的差异: action space 被人为扩张到了 , 其中 是自然语言"内心独白"空间;
一个 HotpotQA 上的具体 trajectory 大概长这样:
1 | Question: Aside from the Apple Remote, what other device can control the program Apple Remote was originally designed to interact with? |
这个例子里至少有两件事是 CoT 和 act-only 各自做不到的: thought 2 把 observation 1 的关键事实抽取出来并形成下一步 query (这是 act-only 缺的); thought 3 在 observation 2 失败时主动改写 query (这是 CoT-only 缺的, 因为它根本不会发请求);
Action Space 的两种设计
ReAct 在两类任务上跑实验, 对应两套 设计:
| 任务类 | action space | 备注 |
|---|---|---|
| Knowledge QA (HotpotQA, FEVER) | Search[entity], Lookup[string], Finish[answer] — 三个动作, 后端是 Wikipedia API |
action 空间极小, 关键是让 thought 决定 search 什么 |
| Decision-making (ALFWorld, WebShop) | 沿用环境本身的 action set (goto, take, open, put, click[button] 等) | action 空间是环境给的, ReAct 的贡献是注入 thought |
注意 Knowledge QA 这一组的 action 设计是有讲究的:
Search[entity]返回的是 Wikipedia 上实体页的前几句, 不是 search engine ranking 的 snippet — 这避免了 reasoning 被 search engine 的 ad 或者 SEO 干扰;Lookup[string]是在当前已经 search 过的页面内做 ctrl-F, 模拟人类"先看页面顶部, 再针对性查关键词"的两段式访问;Finish[answer]是一个 terminal action, 触发后 trajectory 终止并把 answer 提交给打分器;
这个 action 设计本身就是 ReAct 工作的一部分: 它把"信息获取"拆成 search (定位) + lookup (定点) 两步, 给 thought 留出干预的空间;
Prompt 是怎么搭的
ReAct 全程 prompt-only, 不微调; few-shot 示例由人工写好, 每个示例就是一条完整的 thought / action / observation 交替轨迹; LLM 在 inference 时被给到:
模型逐 token 解码, 解码到 Action N: 这一行时, 外部 parser 停止解码, 把方括号里的 action 抽出来送给环境, 环境返回 observation 后 append 到 context 后面继续解码; 这是一个非常工程化的 loop, 整体跟现在所有 ReAct-style agent runtime (LangChain, LlamaIndex agent, 我们自己手撕 60 行 ReAct loop) 的实现是一致的;
关键点是: 停止解码的 trigger 不是模型的特殊 token, 而是 parser 检测到 “Observation N:” 这一行尚未填入, 这样旧 LLM (没有 function calling 训练) 也能跑;
实验对比: 4 种策略
论文在 HotpotQA / FEVER 上比较了:
| 方法 | reasoning trace | tool call | 何时 stop | 备注 |
|---|---|---|---|---|
| Standard | 否 | 否 | 直接给答案 | 纯 in-context 回答 |
| CoT | 是 | 否 | 一次性输出 rationale + answer | 没有 grounding |
| CoT-SC | 是 × N | 否 | sample N 条后投票 | 缓解 error propagation, 没解决 hallucination |
| Act-only | 否 | 是 | 输出 Finish | 没有 thought, 容易循环 |
| ReAct | 是 (交替) | 是 | 输出 Finish | 同时拿到 grounding 和规划性 |
| ReAct→CoT-SC | 是 (优先 ReAct) | 是 | ReAct 失败时 fallback CoT-SC | 拿不到 evidence 时退回内部知识 |
| CoT-SC→ReAct | 是 (优先 CoT-SC) | 是 | CoT 自信度低时切 ReAct | 信号不够强时再去查证 |
主要结论:
- 在 HotpotQA 上, ReAct 单独跑略低于 CoT-SC (因为 search engine 偶尔检索失败会拖累整个 trajectory), 但 ReAct + CoT-SC 的组合显著超过任一单一方法;
- 在 FEVER (事实核查) 上, ReAct 单独就明显赢, 因为这类任务严重依赖外部证据;
- 在 ALFWorld (text-based household) 上, ReAct vs 模仿学习训练的 BUTLER, 在 prompt-only 设定下绝对成功率从 act-only 的 25% 飙升到 ReAct 的 71%; 关键是 thought 让模型能记住"我目前 sub-goal 是把刀放到 sink 里";
- 在 WebShop 上, ReAct 也明显赢 act-only, 帮模型在多个商品候选间做对比和取舍;
一句话总结: thought 在 sequential decision-making 上的收益比在 QA 上还要大, 因为 sub-goal 跟踪比 grounding 还更难离开自然语言;
ReAct 的失败模式
论文很坦诚地列了 ReAct 跑挂的几种典型情况:
- search failure: Wikipedia 拿不到精确实体, 模型陷入
Search[X] $\rightarrow$ not found $\rightarrow$ Search[X 改写] $\rightarrow$ not found的循环, 这种 case 需要外部判定来让它 fallback; - hallucinated thought after empty obs: observation 为空或没有有效信息时, 模型有概率在下一个 thought 里直接编造事实, 然后基于这个假事实输出 Finish;
- reasoning error propagation: thought 把 observation 关键事实抽错了, 之后所有 action 都建立在错误前提上;
这几个失败模式直接催生了后续整条 agent 路线: Reflexion (失败后写复盘 thought 重新跑), Self-Refine (生成后自检), Tree of Thoughts (在 thought 层面做 search), LATS (UCT-style 树搜索) — 全部都是在 ReAct 的 thought-action-observation 骨架上加层;
复杂度 / 成本侧
ReAct trajectory 的 token 数远高于 CoT, 因为每步都要写一段 thought + 一段 action + 把 observation append 回去; 单次 trajectory 期望长度大致是
其中 是 step 数, 分别是 thought / action / observation 的平均 token 数, 而 通常远大于 (Wikipedia 一段就上百 token); 这意味着同等 LLM 调用预算下 ReAct 的 trajectory 数量会比 CoT-SC 少, 这也是为什么实际部署常用 CoT-SC → ReAct fallback 这种 hybrid: 先尝试 cheap 的 CoT-SC, 不行再上 expensive 的 ReAct;
这里也埋下了一个工程问题, 后面的 LLM agent runtime 都要面对: observation 长度爆炸, 比如 search 返回一整篇文章; 实践中要么 truncate observation, 要么再加一层 summarizer (这就过渡到 MemGPT 那一套了);
和后续工作的关系
ReAct 是 LLM agent 这条研究线真正意义上的起点, 后面几乎所有 agent 论文都可以拆解成"在 ReAct 骨架上加什么":
| 后续工作 | 加在 ReAct 哪里 |
|---|---|
| Reflexion | trajectory 失败后追加一条 “verbal self-reflection” 写进下一次 prompt |
| Self-Refine | 在 thought 层加自检 / 重写循环 |
| Tree of Thoughts (ToT) | 把 thought 从单链改成树, 在 thought 节点做 BFS / DFS |
| LATS | 在 ToT 之上加 UCT 选择 + value model |
| Toolformer | 把 ReAct 的 action 协议编进模型权重, 不再需要 few-shot prompt |
| MemGPT | 给 ReAct trajectory 加 OS-style memory hierarchy |
| SWE-agent | 把 action space 从 Search 换成 file edit / shell, 在 SWE-bench 上跑 |
这些后续会单独开 note 讨论;
我对 ReAct 的几个判断
- ReAct 真正的贡献不在性能数字, 而在协议: thought / action / observation 这套交替结构是后续所有 agent runtime 的事实标准;
- “prompt-only, 不训练” 这件事是它能扩散得这么快的关键, 任何团队拿一个 frozen LLM 就能跑;
- 这套协议的根本短板是 trajectory 不可分支 — ReAct 是单链推理, 一旦 thought 错了, 后续步骤继承错误前提; 这是 ToT, LATS 这些后续工作的存在动机;
- thought 的可读性是 ReAct 一个被低估的好处: 它让 agent 调试从"看一串黑箱 action" 变成"读一段日记", 这件事在 production 调试 agent 时极其重要;
如果按论文主线浓缩, ReAct 的主流观点可以总结为:
- CoT 提供 reasoning, 但没有 grounding, 容易 hallucinate; act-only agent 有 grounding, 但没有 working memory, 容易循环;
- 把 thought / action / observation 三类 token 放进同一个生成序列, 让 LLM 自己决定每一步是想还是做, 是最朴素的统一办法;
- 这套协议只改 prompt, 不改模型, 任何 frozen LLM 都能用, few-shot in-context 就够;
- 在 knowledge-intensive QA (HotpotQA, FEVER) 和 sequential decision-making (ALFWorld, WebShop) 两类任务上, ReAct 同时拿到收益, 尤其在后者优势更大;
- ReAct 单独跑不一定打过 CoT-SC, 但和 CoT-SC 组成 hybrid (一边失败回退另一边) 是一致最优的;
- ReAct 是后续所有 agent 工作 (Reflexion / ToT / LATS / Toolformer / SWE-agent) 的协议级 baseline, 看后续论文时把它当作"在 ReAct 骨架上加了什么"来读最高效;
