[!ReAct 的术语黑称]

  • thought: 模型自言自语写出的 reasoning trace, 不出现在环境里
  • action: 模型对外发出的工具调用 / 环境指令, 会被执行
  • observation: 工具或环境返回给模型的下一段输入
  • trajectory: thought \rightarrow action \rightarrow observation \rightarrow thought \rightarrow … 的整条交替序列
  • 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 改成

questionrationaleanswer\text{question} \rightarrow \text{rationale} \rightarrow \text{answer}

形式上让模型先写一段推理再给答案, 已经在 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
2
3
4
Action 1: search[Apple Remote]
Obs 1: The Apple Remote was originally designed to interact with the Front Row media center...
Action 2: search[Front Row]
Obs 2: ...

这种交互的问题在于:

  • 模型对为什么要发这个 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 试图回答:

  1. 能不能让 reasoning trace 和 tool action 共享同一个生成过程, 让 thought 来规划下一个 action, 让 observation 来修正后续 thought?
  2. 在只改 prompt, 不改模型的前提下, 这种交替结构能不能在 knowledge-intensive QA 和 sequential decision-making 两类任务上同时拿到收益?
  3. 写出来的 thought 本身能不能作为 interpretability 和 human-in-the-loop 修正的入口?

核心机制: thought \rightarrow action \rightarrow observation 的交替

ReAct 把 trajectory 定义为如下序列:

τ:=(t1,a1,o1,t2,a2,o2,,tn,an)\tau := (t_1, a_1, o_1, t_2, a_2, o_2, \dots, t_n, a_n)

  • tit_i : 第 ii 步的 thought, 是模型自己生成的自然语言, 不发给环境, 只是写进 context 给后续 token 看;
  • aia_i : 第 ii 步的 action, 在固定的 action space A\mathcal{A} 里取值, 会被 parser 抽出来发给环境;
  • oio_i : 环境对 aia_i 的返回, append 回 context;

形式上, ReAct 等价于把"reasoning trace"也写进了 action 历史, 但只标记一类不可观测的 action, 这类 action 不改变环境状态, 只改变 LLM 自己的 context; 这是它和经典 RL agent 最大的差异: action space 被人为扩张到了 AL\mathcal{A} \cup \mathcal{L} , 其中 L\mathcal{L} 是自然语言"内心独白"空间;

一个 HotpotQA 上的具体 trajectory 大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
Question: Aside from the Apple Remote, what other device can control the program Apple Remote was originally designed to interact with?
Thought 1: I need to search Apple Remote and find what program it was originally designed to interact with.
Action 1: Search[Apple Remote]
Observation 1: The Apple Remote is a remote control... originally designed to control the Front Row media center program...
Thought 2: Apple Remote was originally designed to control Front Row. I need to search Front Row and find what other devices can control it.
Action 2: Search[Front Row]
Observation 2: Could not find [Front Row]. Similar: ['Front Row Seat to Earth', ...]
Thought 3: Front Row is not found. I need to search Front Row (software).
Action 3: Search[Front Row (software)]
Observation 3: Front Row is a discontinued media center software... controlled by Apple Remote or the keyboard function keys.
Thought 4: So besides Apple Remote, keyboard function keys can also control Front Row. Answer is keyboard function keys.
Action 4: Finish[keyboard function keys]

这个例子里至少有两件事是 CoT 和 act-only 各自做不到的: thought 2 把 observation 1 的关键事实抽取出来并形成下一步 query (这是 act-only 缺的); thought 3 在 observation 2 失败时主动改写 query (这是 CoT-only 缺的, 因为它根本不会发请求);

Action Space 的两种设计

ReAct 在两类任务上跑实验, 对应两套 A\mathcal{A} 设计:

任务类 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 时被给到:

prompt=task instruction1-2 句    k 条完整 trajectory 示例通常 k=6    当前 question需要解答\text{prompt} = \underbrace{\text{task instruction}}_{\text{1-2 句}} \; \| \; \underbrace{k \text{ 条完整 trajectory 示例}}_{\text{通常 } k=6} \; \| \; \underbrace{\text{当前 question}}_{\text{需要解答}}

模型逐 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 kk 把 observation k1k-1 关键事实抽错了, 之后所有 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 期望长度大致是

LReActn(lt+la+lo)L_{\text{ReAct}} \approx n \cdot (l_t + l_a + l_o)

其中 nn 是 step 数, lt,la,lol_t, l_a, l_o 分别是 thought / action / observation 的平均 token 数, 而 lol_o 通常远大于 lt,lal_t, l_a (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 kk 错了, 后续步骤继承错误前提; 这是 ToT, LATS 这些后续工作的存在动机;
  • thought 的可读性是 ReAct 一个被低估的好处: 它让 agent 调试从"看一串黑箱 action" 变成"读一段日记", 这件事在 production 调试 agent 时极其重要;
📝 论文主流观点总结

如果按论文主线浓缩, ReAct 的主流观点可以总结为:

  1. CoT 提供 reasoning, 但没有 grounding, 容易 hallucinate; act-only agent 有 grounding, 但没有 working memory, 容易循环;
  2. 把 thought / action / observation 三类 token 放进同一个生成序列, 让 LLM 自己决定每一步是想还是做, 是最朴素的统一办法;
  3. 这套协议只改 prompt, 不改模型, 任何 frozen LLM 都能用, few-shot in-context 就够;
  4. 在 knowledge-intensive QA (HotpotQA, FEVER) 和 sequential decision-making (ALFWorld, WebShop) 两类任务上, ReAct 同时拿到收益, 尤其在后者优势更大;
  5. ReAct 单独跑不一定打过 CoT-SC, 但和 CoT-SC 组成 hybrid (一边失败回退另一边) 是一致最优的;
  6. ReAct 是后续所有 agent 工作 (Reflexion / ToT / LATS / Toolformer / SWE-agent) 的协议级 baseline, 看后续论文时把它当作"在 ReAct 骨架上加了什么"来读最高效;