Chapter 03

搜索 & 反思家族:探索多条路 / 从失败中学

上一章的四种范式(ReAct → Plan-and-Execute → ReWOO → LLMCompiler),无论多适应、计划定得多死,都只产出一条计划/轨迹。这一章处理两种"一条路不够"的情形:探索多条候选路径再选最好的一条(搜索,家族②),以及整次失败后把教训学到手再重来(反思,家族③)。

本章你将建立的 schema

  • 多计划选择不是新的"计划格式",而是三个动作的合成:生成多个候选 + 评估每个 + 用搜索算法挑
  • ToT 的"评估函数"其实是让 LLM 给自己的状态打分(sure/likely/impossible),不是一个训练出来的 critic
  • Reflexion 用一段自然语言"梯度"(写成教训)代替权重更新——零微调,靠改 prompt 重塑行为
  • 反思发生在 episode 之间(整次重试),ReAct 当场纠错发生在 step 之间(同一次执行内)——回扣第 1 章那条区分
  • LATS 用 MCTS 把搜索(②)、反思(③)、ReAct 的行动-观察(①)缝成一个算法——三大家族可以组合

3.1从「一条路」到「多条路」

上一章所有范式都押注一条轨迹;有些任务的最优解藏在岔路里,押单条路会走进死胡同——这时需要同时探索几条、再比较。

为什么需要它

第 2 章的四种范式,差别只在"那条计划定得多死、能不能中途重排",但它们始终只维护一条路。这对"路径基本确定、只是步骤多"的任务(订差旅、多跳问答)足够。可一旦任务有多条看似可行的解题路径、且贪心地顺着一条走容易走进死胡同(数字谜题、需要试探性假设的推理、代码里有多种实现思路),单条轨迹就力不从心——错一步,整条轨迹作废,而 ReAct 式的当场纠错只能"改下一步",没法"退回岔路口换一条"。

底层机制(比文档深一层):单条轨迹的范式,本质是在解空间里走一条不回头的路径——即便 ReAct 能每步纠偏,它纠的也只是"当前这条路的下一步往哪拐",从不同时持有两个互斥的候选未来。家族②把规划重新表述成在一棵树(或图)上做搜索:每个节点是一个"想到这里的状态",从一个节点可以展开出多个候选下一步,于是天然出现分叉。一旦有了分叉,就必须回答两个新问题——哪条岔路更有希望(评估)、按什么顺序展开、什么时候砍掉没希望的岔路(搜索策略)。这两个问题,就是 §3.2 ToT 的全部内容。

类比 · 带边界声明

单条轨迹像走迷宫时一直摸右墙——能走,但撞墙了只能原地换个方向,没法瞬移回上一个路口。多路径搜索像在路口插旗、记下每条岔路、择优探索、走不通就回到插旗处换一条。类比失效在"回退成本":真人回到路口几乎免费,而 agent 的每个节点都是一次(或多次)LLM 调用——分叉越多、探得越深,账单越线性膨胀。这就是搜索家族的核心代价,§3.2 末尾会算这笔账。

想一想

"把 24 这个数用 4、9、10、13 四个数和加减乘除算出来"——这种题,为什么 ReAct 的"每步看真实结果再纠下一步"帮不上忙?它缺的到底是哪种能力?

展开答案(先停 10 秒)

这类题没有可供"接地"的外部观察——没有工具会告诉你"(13−9)=4 这一步是对是错",对错只有等你凑完四个数才知道。ReAct 的纠错依赖"看到真实 Observation 再调整",可这里每一步的"真实结果"要到终局才显现。它缺的不是"接地的纠错",而是"在到达终局前,并行持有多个候选算式、给每个估个前景分、砍掉明显凑不出 24 的分支"——这正是搜索(家族②)才有的能力。Game-of-24 也确实是 ToT 论文的主战场。

3.2Tree of Thoughts(ToT):生成 → 评估 → 搜索

ToT = 把"一连串想法"升级成"一棵想法树":每步生成 k 个候选想法,让 LLM 给每个状态打分,再用 BFS/DFS 择优展开、砍掉没希望的分支。

为什么需要它

纯思维链(CoT)是一条不回头的想法链——一步推错,整条链跟着错下去(第 1 章已埋)。Tree of Thoughts(Yao et al. 2023)把这条链展开成树:在每个"想到一半的状态"处生成多个候选下一步,并引入一个评估来比较它们、一个搜索来决定先探哪条、砍哪条。它给 LLM 装上了人类解题时天然会做却被 CoT 抹掉的两件事:试探不同思路和走不通就回头。

底层机制(比文档深一层):ToT 把规划拆成可独立替换的三个部件——

  • 状态(state):一个节点是 s = [x, z₁…zᵢ],即"原始输入 x + 到目前为止的若干步想法 z"。注意节点存的是累积的部分解,不是单个想法。
  • 生成器(generator):从当前状态产出 k 个候选下一步想法——要么用一个 CoT prompt 独立同分布地采样 k 次(想法空间大、彼此差异大时),要么用一个"propose" prompt 一次性顺序列出 k 个不重复候选(想法空间窄时)。
  • 状态评估器(state evaluator):让 LLM 给每个状态打分。两种打法——赋值:对单个状态做一小段前瞻推理后贴标签(sure / likely / impossible);投票:把多个状态摆在一起,让 LLM 投票选最有希望的那个。
  • 搜索算法(search):BFS——每一层只保留评估最高的 b 个状态(beam),逐层向下;DFS——沿一条路深入,一旦某状态的评估值低于阈值就剪枝并回溯到上一个岔口换分支。

这里有个该被讲明白的意外:那个"评估器"不是一个训练出来或外挂的 critic——它就是同一个 LLM 在给自己生成的状态打分(贴 sure/likely/impossible,或自己跟自己投票)。换句话说,ToT 的"智能"全部来自"让模型多生成几个候选、再让它自己评判哪个更靠谱"这套提示工程,没有任何额外的模型或参数。就这么朴素的一招,在 Game-of-24 上把准确率从 CoT 的 4% 抬到 74%。

根状态 s [x] 初始输入 生成 k 个候选 候选 A sure 候选 B likely 候选 C impossible ✗ 剪枝 A1 · 解 sure A2 ✗ 剪枝 B1 likely ★ 最佳路径:根 → A → A1
图 3.1ToT 把规划变成树上搜索:每层生成 k 个候选,评估器(同一个 LLM)给每个状态贴 sure/likely/impossible,BFS 留下高分的、DFS 把低于阈值的分支剪掉并回溯,最终沿高亮路径抵达解。 注意:图中 灰色虚线的分支(C、A2)被评估器判为没希望而剪掉——剪枝是 ToT 省算力的关键,否则节点数会随深度指数爆炸。

成本怎么算:一次 ToT 大约要 b × 深度 × (生成 + 评估) 次 LLM 调用——Game-of-24 这种约需 100+ 次调用,单题成本约 $0.74,而朴素 CoT 单次约 $0.47。表面看贵了一半,但下面这点才反直觉。

第二个意外 · 值得记住

ToT 并不比"无脑多采样 CoT"贵多少。它在 Game-of-24 上用约 5.5k token 拿到 74%;而盲目跑 100 次独立 CoT(指望蒙对一次)大约要 6.7k token——量级相当,准确率却天差地别。结论是反直觉的:把同样的 token 预算花在"有评估、有剪枝的结构化搜索"上,比花在"碰运气重采样"上,既更准、又不更贵。ToT 的贵是"比单次 CoT 贵",不是"比同等算力的暴力采样贵"——这两件事常被混为一谈。

想一想

把 ToT 的搜索从 DFS 换成 BFS,对"什么时候发现走错了路"这件事有什么影响?哪一种更省 token?

展开答案(先停 10 秒)

DFS 沿一条路一插到底,靠"评估值低于阈值就回溯"来及时止损——发现错路早,但若评估不准,可能在一条死路上深挖几步才掉头。BFS 逐层保留 top-b,每层都横向比较 b 个状态,更稳健但要同时维护 b 条路、每层都评估,调用数通常更多。一般规律:解的深度浅、分支因子大、想尽早砍掉烂分支,用 DFS 更省;需要全局横向比较、怕过早剪错好分支,用 BFS。Game-of-24(深度固定为 3 步)用的是 BFS(b=5)。

3.3Reflexion:把失败写成下一次的提示

Reflexion = 跑完一次失败的轨迹后,让 LLM 把"哪里错了、下次怎么改"写成一段自然语言教训,存进记忆,拼到下一次同一任务的 prompt 开头——靠改提示而非改权重来变强。

为什么需要它

搜索(家族②)解决"一次执行内探索多条路"。但还有一类改进发生在整次执行之间:agent 把一个任务整体做砸了,按理该"吸取教训、下次别再这么干"。强化学习的标准答案是用奖励信号去更新权重——但对一个动辄千亿参数的 LLM,每失败一次就微调一轮,代价高到不可行。Reflexion(Shinn et al. 2023)给出一条绕过权重的路:把"教训"写成自然语言,存进记忆,下次直接喂回模型。

底层机制(比文档深一层):Reflexion 由三个角色构成——

  • Actor(执行者):一个跑轨迹的策略,本身就可以是第 2 章的 ReAct,或一个 CoT 策略——注意这里把上一章的 ReAct 当成了一个可插拔的组件复用。它产出一条完整轨迹(短期记忆)。
  • Evaluator(评估者):把这条轨迹的结果转成一个稀疏奖励——可以是一个启发式判断,更关键的是可以是外部信号,比如单元测试通过/失败、游戏赢/输。
  • Self-Reflection(自我反思 LLM):拿到"奖励 + 那条失败轨迹",生成一段自然语言教训("我假设了文件已存在,但其实没有——下次应先检查"),存进长期记忆。

关键在记忆怎么用:轨迹是短期记忆(只在本次有效),反思则被拼接到下一次同一任务的 prompt 开头,像一道"语义梯度"——它不更新任何一个权重,却通过改写输入上下文重塑了模型这一次的行为。零微调、零权重更新;记忆通常只保留最近 1–3 条反思(防止上下文膨胀)。这就是"用自然语言代替梯度下降"的字面含义。

第 N 次尝试(一次执行内) Actor 跑轨迹·可用ReAct Evaluator → 稀疏奖励 Self-Reflect 生成语言教训 长期记忆 存最近 1–3 条 Actor 第 N+1 次 下一次尝试(同一任务) 教训 prepend 到 prompt 开头
图 3.2Reflexion 的闭环:Actor 跑 → Evaluator 给奖励 → Self-Reflection 写教训 → 存进记忆 → 把教训拼到下一次 Actor 的 prompt 开头。 注意:两个灰色虚线框是两次不同的尝试——这条改进的回边跨越了 episode 边界(不是同一次执行内),这正是它和 ReAct 当场纠错在时间尺度上的根本区别。

成本与前提:Reflexion 不是免费的——它需要两个硬前提。其一,任务可重复尝试(同一道题能再做一遍);其二,存在可用的奖励信号(能判出这次到底成没成)。少了任一个,Reflexion 就无米下锅:无法重试,写了教训也没机会用;分不清成败,反思就只能瞎猜"哪里错了"。它最闪光的场景,恰恰是有现成外部判分器的地方——代码任务有单元测试(HumanEval pass@1 达 91%,对比 GPT-4 的 80%),具身决策有环境给的成败信号(ALFWorld 完成 130/134)。

类比 · 带边界声明

反思像考完试写错题本——把这次错的题、错因、下次对策写下来,下次开考前先翻一遍。类比失效在两处:① 错题本是你主动选择翻不翻,而 Reflexion 的教训是每次都被强制拼进 prompt;② 真人的错题本可以无限积累,Reflexion 的记忆窗口很小(1–3 条)——塞太多反而挤占上下文、稀释重点。它是"带遗忘的错题本"。

回扣第 1 章 · 把这个区分钉死

Reflexion 和 ReAct 都说"从失败中学",但时间尺度完全不同,别混。ReAct 学在 step 之间:看到这一步的真实 Observation,立刻调整下一步,全程在同一次执行内。Reflexion 学在 episode 之间:整条轨迹跑完、判定失败后,才把教训写进记忆,作用于下一次整体重试。同样四个字"从失败中学"——一个是当场微调走向,一个是赛后复盘重赛。第 1 章 §1.4 的预测题埋的就是这个,这里把它焊死:step 级 ≠ episode 级。

3.4LATS:把搜索、反思、ReAct 缝在一起

LATS = 以蒙特卡洛树搜索(MCTS)为骨架,把 ReAct 的行动-观察、ToT 的分支搜索、Reflexion 的自我反思统一进一个算法——本章三大能力的合体。

为什么需要它

到这里,本章两条线(搜索②、反思③)加上第 2 章的行动-观察(①)各自独立。LATS(Language Agent Tree Search, Zhou et al. 2023)回答最后一个问题:三者能不能合成一个算法?能——用 MCTS 当骨架,让一个 agent 同时具备"探多条路、从失败学、与环境接地"三种能力。它是本章最强、也最贵的范式。

底层机制(比文档深一层):MCTS 的四步循环(选择 → 扩展 → 评估/模拟 → 回传)天然就是一个"边探索边记账"的搜索框架,LATS 把三家能力各塞进一步——

  • 扩展时用 ReAct 式的行动-观察:每个新节点是一次真实的"行动 + 环境观察",让搜索接地到外部世界(这是纯 ToT 没有的)。
  • 分支与回溯继承 ToT:一个节点展开多个候选、用价值评估择优、走不通就回到树上别的节点。
  • 回传失败信号时引入 Reflexion:一条路径失败后,生成自然语言反思,注入后续的搜索决策,让"学到的教训"指导下一轮选择。

代价也最直白:MCTS 要反复"模拟—回传",节点数和 LLM 调用量远超前面任何范式。所以 LATS 不是默认选项,只保留给高可靠性、且状态可验证的领域——谜题、博弈、带测试的代码生成。能拿来当裁判的外部信号越硬,LATS 越划算;信号一软,它高昂的搜索成本就换不来对应收益。

LATS MCTS 骨架 ReAct 行动-观察·接地 Tree of Thoughts 分支搜索·剪枝 Reflexion 自我反思·语言梯度 三家能力汇入同一棵搜索树:扩展用①,分支用②,失败回传用③
图 3.3LATS 是一次合成:MCTS 居中当骨架,三家能力各司其职汇入——ReAct 负责"行动-观察"让搜索接地,ToT 负责"分支搜索",Reflexion 负责把失败转成"语言梯度"反哺后续选择。 注意:这张图回答了第 1 章 §1.4 的承诺——三大家族正交、可组合,LATS 就是把①②③拼进一个算法的活证据;代价是它也继承了三者全部的调用开销。

3.5跨概念综合 + 备选方案对比

把四章范式放回那条轴边上,选型问题收敛成两问。第一问:单条轨迹(第 2 章)何时就够?当任务的解题路径基本唯一、步骤间依赖在执行前大体可知(订差旅、多跳问答、跑一段确定流程),单条轨迹既省又快,多开几条路只是徒增成本;只有当"贪心顺着一条路走容易走进死胡同、最优解藏在岔路里"时,才值得上 ToT 的树搜索。第二问:反思何时强过单纯重试?如果失败是随机噪声(偶发超时、采样抖动),直接重试(甚至 CoT 自洽采样)更省——反思那一步是白花的 token;只有当失败有可复盘的系统性原因、且存在外部判分器能确认成败时,把教训写下来喂回去,下一次才真的会更好。一句话收口:多路径买的是"广度",反思买的是"跨次的记性",两者都不免费,按任务缺的是哪种能力来取。

表 3.1 · 搜索 / 反思家族 vs 第 2 章单条轨迹(按"学习的时间尺度"对齐)
方法核心动作何时用主要代价学习的时间尺度
单条轨迹
ReAct / Plan-and-Execute(ch02)
沿一条路径走到底(ReAct 每步可纠下一步) 解题路径基本唯一、依赖大体可预知 错一步整条作废;无法退回岔口换路 step 级(仅 ReAct,同一次执行内)
Tree of Thoughts 每步生成 k 个候选 + LLM 自评 + BFS/DFS 搜索剪枝 多条候选路径、贪心易走死胡同(谜题、试探性推理) ~b×深度×(生成+评估) 次调用,比单次 CoT 贵 无跨次学习(探索在一次执行内)
Reflexion 失败轨迹 → 自然语言教训 → prepend 到下次 prompt 可重复尝试 + 有外部判分器(代码测试、环境信号) 需可重试 + 可用奖励;记忆窗口仅 1–3 条 episode 级(跨尝试,整体重试)
LATS MCTS 骨架统一 ReAct + ToT + Reflexion 高可靠性 + 状态可验证(谜题、博弈、带测试的代码) 最贵:MCTS 反复模拟-回传,调用量最大 step + episode 兼有(搜索内接地 + 反思跨次)

§本章 self-check

先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节再读一遍。

  1. ToT 的"状态评估器"是一个单独训练出来 / 外挂的 critic 吗?它具体怎么给状态打分?
  2. Reflexion 说它"用自然语言梯度代替权重更新"——这句话里"零权重更新"具体指什么?教训最后是怎么影响下一次行为的?
  3. ReAct 的当场纠错和 Reflexion 的反思都"从失败中学",但发生在不同的时间尺度。分别是哪个尺度?用一个词区分这两个尺度。
  4. LATS 把哪三种范式缝进了一个算法?它的骨架是什么?为什么它被限制在"状态可验证"的领域?
答案(先做完再展开)
  1. 不是。评估器就是同一个 LLM 在给自己生成的状态打分——要么对单个状态做一小段前瞻后贴 sure/likely/impossible 标签(赋值),要么把多个状态摆一起让它投票选最优(投票)。没有任何额外训练的模型或参数;它的"评判力"全来自提示工程。
  2. "零权重更新"指不做任何微调——模型参数一个都不动。教训的影响路径是:失败轨迹 + 奖励 → Self-Reflection 写成自然语言教训 → 存进长期记忆 → 下一次同一任务时拼接到 prompt 开头,靠改写输入上下文(而非改权重)来重塑这一次的行为。记忆通常只留最近 1–3 条。
  3. ReAct 在 step 之间(同一次执行内,看到 Observation 立刻调整下一步);Reflexion 在 episode 之间(整次失败后写教训、下一次整体重试)。一词区分:step 级 vs episode 级(或"当场微调 vs 赛后重赛")。
  4. 缝进了 ReAct(行动-观察接地)+ Tree of Thoughts(分支搜索)+ Reflexion(自我反思)。骨架是 MCTS(蒙特卡洛树搜索)。限制在状态可验证领域,是因为 MCTS 的反复模拟-回传调用量极大,只有当存在硬外部判分器(测试、博弈胜负)时,这笔搜索成本才换得回可靠性收益。
进阶挑战 · 刚好够不着

给一个"会自我修复的代码 agent"挑范式

需求:「给定一道编程题 + 一套隐藏单元测试,agent 要写出能全部通过的代码」。可以反复提交、每次提交都拿到测试的通过/失败结果。在本章四种方法里,写下你的首选和次选,并说清:单纯让它"失败就重写一遍"为什么往往不够?如果改用 ToT 单打,缺了什么?

提示(卡住再展开)

先核对两个 Reflexion 前提:能反复提交(✓ 可重试)、有单元测试(✓ 硬奖励信号)——这正是 Reflexion 的主场,也是它 HumanEval 91% 的来源,首选它。"失败就盲目重写"为什么不够:模型很可能重复同一个错误,因为它没把"上次为什么挂"显式带进下一次的上下文;Reflexion 的那段自然语言教训正是补上这块。ToT 单打缺什么:它能在一次提交内探索多条实现思路,但不携带跨提交的记忆——这次学到的教训,下次提交时丢得一干二净。想两者兼得(一次内搜索 + 跨次反思 + 测试接地),答案就滑向 LATS 那一格——但要先掂量它的调用成本是否撑得起。