02 · 工作原理与设计权衡

为什么语言能代替梯度

上一章建立了 Reflexion 的词汇表——Actor、Evaluator、Self-Reflection 三组件,trajectory 是短期记忆,episodic memory 是跨 trial 的长期记忆。这一章拆开循环的内核:标量 reward 为什么不够用、语言反思凭什么补上、"更新"到底发生在哪里。读完不再停在"会调这个循环",而是能解释它为何成立、代价是什么、什么时候该换别的范式。

本章你将建立的 schema

  • 标量 reward 只携带"成功/失败"一个比特,做不了 credit assignment
  • 语言反思是一个高维、带定位的纠错信号:它能指出"action ai 该改成 a'i"
  • "更新"通过 in-context conditioning 发生,不是 backprop;policy 就是记忆里那段文本
  • reward 来源按任务分三类:精确匹配 / 启发式或 LLM 分类 / 自生成测试
  • 反思 ≠ 重试:加反思比只塞历史轨迹多 +8% 绝对值,盲目重试反而更差
  • 为什么没选 ReAct / CoT / Self-Refine / ToT / CRITIC / RLHF——每条分界轴在哪

2.1标量 reward 的信息瓶颈

强化学习的经典回路里,环境回给 agent 一个数:r。这个数可以是 0/1,可以是连续分值,但无论多精确,它只回答一个问题——这次整体做得多好。它不回答"哪一步错了""错在什么地方""下次该改成什么"。在监督式或基于梯度的方法里,这个缺口由 backprop 填补:误差信号沿着计算图反传,把"责任"按权重分摊到每一个参数上。这就是 credit assignment(信用分配)——把一个总体结果拆解成对各个决策的功过评判。

LLM agent 的回路里,这条路断了。权重冻结,没有梯度回传;一次 trajectory 可能有十几步 action,Evaluator 只回来一个 fail。Actor 拿到这个 fail,无从知道是第 2 步取错了元素、还是第 7 步漏判了边界条件。标量 reward 在多步决策上是一个信息瓶颈:结果维度坍缩到一维,定位信息全部丢失。

深一层 · 为什么向量 reward 也不够

把 reward 从标量升级成向量(给每一步打分),似乎能恢复定位。但向量回答的仍是"第 i 步好不好",不回答"第 i 步应该怎么做"。诊断与处方是两件事。Reflexion 论文把改进信号交给一个 LLM 用自然语言表达,正是因为自然语言能同时携带"哪一步""为什么错""改成什么"——这是任何固定维度的数值向量都编码不下的结构。

语言反思如何完成 credit assignment

Self-Reflection 模型(Msr)接收三样东西:任务描述、完整 trajectory、Evaluator 的判定。它产出一段自然语言 reflective text。这段文本做的恰恰是标量 reward 做不到的事——它把整体失败归因到具体某步,并给出修正方向。论文里这段文本是第一人称写的,例如一条编程任务的反思:

reflective text 示例(论文设计为第一人称)

"我只取了排序后中间的单个元素,没有处理列表长度为偶数的情况;下次遇到偶数长度,应该取中间两个数的平均。"

对照看:标量信号是 AssertionError 这一个 fail;语言信号则定位到"取中间单个元素"这一步(credit assignment 的定位),并给出"取两数平均"这一处方(credit assignment 的方向)。这就是一个高维、带定位的纠错信号:它的"维度"是自然语言的全部表达力,它的"定位"是对具体 action 的指认。下一个 trial 开始时,Actor 把这段文本拼进 prompt——所谓"学习",是 Actor 在新的上下文条件下重新生成,即 in-context conditioning,而不是参数被梯度推动。policy 在数学上被参数化为"记忆编码 + 冻结的 LLM 参数",trial 之间唯一变化的就是上下文里那段文本。这正是 "verbal reinforcement learning(语言强化学习)" 为何是字面意义:用语言而非梯度去强化。

标量 reward 与语言反思的 credit assignment 对比 同一条失败 trajectory a₁ sort a₂ 取中间 a₃ return Evaluator: fail 标量 reward r = 0 「失败了」— 一个比特,无定位 Actor 收到 r=0 后只能问: 是 a₁ 排序错了? 还是 a₂ 取错了元素? 还是 a₃ 返回类型不对? 无从定位 → 只能盲猜 语言反思 「a₂ 只取了中间单个元素, 偶数长度应取两数平均」 定位到 a₂ 下一个 trial,Actor 据此改写: 定位(哪一步): a₂ 归因(为什么): 漏判偶数长度 处方(改成什么): a'₂ = 取两数平均 in-context conditioning,非 backprop
图 2-1同一条失败 trajectory,标量 reward 只回传"失败了"一个比特、无法定位是哪一步;语言反思则定位到 a₂、归因并给出处方 a'₂,是一个高维带定位的纠错信号。注意:右侧那条指回 a₂ 的箭头,就是 credit assignment——标量信号永远画不出这条线。

备选信号对比

纠错信号的三种形态
信号形态能否定位是哪一步能否给出修正方向更新机制
标量 reward否(整体一维)否需 backprop 才能分摊;LLM 权重冻结时无路可走
向量 / 逐步 reward部分(知道哪步差)否(不说怎么改)仍需梯度;诊断 ≠ 处方
语言 reflective text是(指认具体 action)是(给出 a'i)in-context conditioning,零梯度
带来的代价

语言信号的表达力是有代价的:它无法被验证为真。梯度由损失函数机械导出,不会"编造";reflective text 是 LLM 生成的,可能把失败归因到错误的 action,甚至凭空发明一条不存在的教训(reflection hallucination,见 04 章 #reflection-hallucination)。高维表达力换来的,是把一部分信任交给了一个会出错的生成器。这也是为什么 Reflexion 强依赖一个能独立判对错的 Evaluator——反思可以错,但"是否通过"这个 ground truth 不能错。

预测一下

如果把 Self-Reflection 模型换成一个只会输出 "请再试一次,这次更仔细" 这类无定位套话的模型,Reflexion 的收益会怎样?它和标量 reward 有本质区别吗?

展开答案

收益会塌回接近"盲目重试"的水平。无定位的套话虽然是自然语言,但它不携带 credit assignment——不指认哪步、不给处方,信息量和"失败了"这个比特几乎一样。本质上它退化成了一个被包装成句子的标量信号。这恰好印证 2.4 节的消融:起作用的不是"有没有文字反馈",而是反馈里有没有完成定位 + 处方。

2.2reward 从哪里来:按任务分三类

2.1 节反复强调"必须有一个能判对错的 Evaluator"。但 Evaluator 不是一个固定实现——论文里它随任务类型变化,而且大多数情况下不用 LLM 当裁判。这一点常被误解为"Reflexion 就是让 LLM 自己评自己",实际并非如此。下表来自论文的三类基准。

Evaluator 的三种实现(对应论文三类任务)
任务类型Evaluator 实现reward 性质基准与增益
推理
(HotPotQA)
exact-match(EM):把答案与 ground-truth 精确比对 客观、外部、可信 HotPotQA +20%
决策
(AlfWorld)
手写启发式(同一动作重复 >3 次,或动作数 >30 判失败)或 LLM 二分类 启发式客观 / LLM 判定带噪 130/134(较 ReAct +22%)
编程
(HumanEval)
自生成单元测试(≤6 条,用 AST 过滤掉语法无效的) 半客观——测试本身可能有错 pass@1 = 91%(GPT-4 基准 80%)

三类的可信度递减:EM 比对 ground-truth,几乎不会错;手写启发式规则明确;自生成测试则把"判据"也交给了 LLM 生成——这埋下了 Reflexion 唯一一次失利的根因(见下方代价段)。Evaluator 的选择,直接决定整个循环的可靠性上限。

reward 来源按任务分三类 trajectory τ (待评估) exact-match 对 ground-truth 推理 · HotPotQA · 几乎不会错 手写启发式 / LLM 二分类 决策 · AlfWorld · 规则明确 自生成单元测试 (AST 过滤) 编程 · HumanEval · 判据也由 LLM 生成 reward: pass / fail 可信度随任务递减 ↓
图 2-2同一条 trajectory,三类任务用三种 Evaluator 打分:推理用客观 exact-match、决策用启发式或 LLM 分类、编程用 LLM 自生成的单元测试。注意:只有编程这一路(红色)把"判据"本身也交给了 LLM,可信度最低——这正是 Reflexion 唯一失利案例的源头。
带来的代价 · 自生成测试的假阳性

编程任务里 Evaluator 用 LLM 自己写的单元测试。这条路在 HumanEval 上很成功(假阳性仅 1.4%),却在 MBPP-Python 上翻车:Reflexion 77.1% < GPT-4 的 80.1%,是论文里唯一一次失利。根因不是反思错了,而是自生成测试有 16.3% 假阳性——错代码被一个同样有缺陷的测试误判通过,循环于是提前提交了错误答案。失败是非对称的:假阴性可容忍(还能继续反思),假阳性致命(提前提交、不可恢复)。选 Evaluator 时,宁可让它偏向严格。详见 04 章 #evaluator-dependency。

2.3反思如何拼回下一轮上下文

反思产出之后,要真正影响行为,必须进入下一个 trial 的 prompt。这一步是机械的字符串拼接,但其中两个设计决定了 Reflexion 能否在长任务上撑住:反思怎么拼、留几条。

每个 trial 结束,Self-Reflection 产出的 srt 被追加进 episodic memory。下一个 trial 开始,Actor 把 memory 里的反思文本格式化成一个"过往教训"区块,拼进任务描述之后、生成指令之前。注意:拼进去的是反思(长期记忆),而不是完整的历史 trajectory——trajectory 是短期记忆,每个 trial 内重置。这正是 2.4 节消融的物理基础:Actor 看到的是被提炼过的教训,不是原始的失败轨迹。

memory 注入(对应共享骨架 Actor.act)python
def act(self, task: Task, reflections: list[str]) -> str:
    memory_block = "\n".join(f"- {r}" for r in reflections)   # 长期记忆 → 文本
    prompt = (
        f"实现函数 {task.entry_point}。\n任务:{task.spec}\n"
        + (f"\n过去尝试的教训(避免重犯):\n{memory_block}\n" if reflections else "")
        + "\n只输出函数定义代码。"
    )
    return self.llm.complete(prompt)

第二个决定是容量。episodic memory 不是无限增长的——它是一个大小为 Ω 的滑动窗口,只保留最近 Ω 条反思(论文 Ω=1–3;AlfWorld 用 3,programming 用 1)。截断的原因是上下文长度:trial 3 时,如果把全部历史都塞进去,prompt = 任务 + 3 次完整尝试 + 3 条反思,长任务会直接溢出窗口(见 04 章 #memory-bloat)。Ω 是为适配上下文限制而设的容量上限,论文明确指出它不是 vector DB——向量库 / SQL 检索被列为 future work。

Ω 滑动窗口(对应共享骨架 EpisodicMemory.add)python
def add(self, sr: str) -> None:
    self.reflections.append(sr)
    self.reflections = self.reflections[-self.capacity:]   # 只保留最近 capacity 条
反思注入下一轮 context 的流程 trial t(失败) trajectory τₜ Evaluator: fail Self-Reflection 产出 srₜ episodic memory · Ω 滑动窗(保留最近 Ω 条) sr₀ sr₁ srₜ ↑ 超窗丢弃 append trial t+1 的 prompt 任务描述 task.spec 过去尝试的教训: - sr₁ - srₜ(最近 Ω 条) 「只输出函数定义代码」 注:拼的是反思,非完整 τ read Actor 据新 prompt 生成 τₜ₊₁ → 回到 trial 顶部
图 2-3失败 trial 的反思被 append 进 Ω 滑动窗;下一轮 prompt 只读取最近 Ω 条反思(不是完整 trajectory),拼在任务描述与生成指令之间。注意:窗口左侧 sr₀ 超出 Ω 后被丢弃——这是为适配上下文长度的容量上限,不是检索式向量库。
带来的代价 · Ω 的两难

Ω 调小:省 token、抗溢出,但跨越多个 trial 的教训会被滑出窗口而遗忘。Ω 调大:记得多,但长任务下 prompt 迅速膨胀,既贵又可能触上下文上限,还稀释了模型对最新教训的注意力。论文用 Ω=1–3 是工程折中,不是理论最优——它把"记忆"压在了一个很窄的窗里,这也是 Reflexion 难以处理需要长期积累的任务的结构性原因。

2.4反思 ≠ 重试:消融证据

一个自然的怀疑:Reflexion 的收益,会不会只是"多试了几次"?毕竟给模型第二次、第三次机会,本身就可能撞对。论文用消融实验正面回应了这个问题,结论很硬:起作用的是"反思"这一步,不是"重试"本身。

消融:把"反思"从循环里拿掉会怎样(论文)
配置Actor 下一轮看到什么相对效果
不重试(单次)—基线 60%
blind retry(盲目重试)把上次 trajectory 原样塞回52%,反而低于不重试
episodic-memory-only只给历史轨迹,无反思有限提升
反思引导的 refinement提炼过的 reflective text(定位+处方)较 memory-only 再 +8% 绝对值

两个数字钉死结论。其一,盲目重试 52% < 不重试 60%:把原始失败轨迹塞回上下文,不仅没帮助,还把模型往错误方向引(它倾向于复刻上次的错误路径)。其二,反思比只给历史轨迹再多 +8% 绝对值:同样是"看过去",看"提炼后的教训"显著优于看"原始轨迹"。差距就出在 2.1 节那一步——credit assignment。反思把"发生了什么"压缩成"哪步错了、该怎么改",而原始轨迹把这个推断的负担又甩回给 Actor。

别记成"多试几次就有用"

"我读得很顺,反思不就是 retry 吗"——如果脑子里冒出这句,正是该停下的信号。Reflexion 与 retry 的分界,不在"试了几次",而在"两次之间发生了什么处理"。把这一步去掉,52% 这个数字会提醒:盲目重试比单次更差。下一章动手时,留白 B(怎么写反思 prompt)考的就是这一点。

带来的代价 · 涌现性与成本

反思能完成 credit assignment,前提是模型本身足够强——自我纠错是更强模型的涌现能力。弱模型(starchat-beta)上 Reflexion 0.26 = baseline 0.26,反思形同虚设。此外,一轮 Reflexion 要 10–30s(单次推理仅约 0.8s);在相同采样预算下,多次尝试常常打不过 self-consistency。这些都在 04 章 #cost 展开。

2.5为什么没选别的:六种范式的分界轴

Reflexion 不是凭空出现的,它在一族"让 agent 做得更好"的方法里有明确的生态位。理解它的关键,是理解它不是什么——以及在什么条件下,别的范式才是对的选择。下表给出六个最常被拿来比较的方法,选中行是 Reflexion 的适用区间。

Reflexion vs 五种相邻范式(分界轴)
范式一句话本质与 Reflexion 的分界轴什么时候选它而非 Reflexion
ReAct 单条轨迹里交错 reason + act + observe 单轨迹、不跨尝试学习;Reflexion 把 ReAct 当 Actor,外面套评估→反思→重试 任务一遍能解、无需从失败中改进时
CoT 单遍生成中间推理步 无评估、无记忆、无第二次尝试 没有可验证信号、也不打算重试时
Self-Refine 同一 session 内自评 → 自改一个输出 无外部 reward、不跨 trial、不持久化记忆——最易与 Reflexion 混淆 没有外部 verifier、只想润色单个输出时
Tree of Thoughts 在推理分支树上搜索(BFS/DFS) 探索"宽度"(一次尝试内并行铺开);Reflexion 探索"时间"(跨 trial 串行改进) 解空间需要广度搜索、单步可枚举候选时
CRITIC 调外部工具验证并就地改正 工具 grounding、任务内即时纠正;Reflexion 是 reward 驱动的跨 trial 记忆 有现成工具能即时校验、无需跨尝试积累时
RLHF / 微调 梯度更新权重 改权重、全局持久、跨任务;Reflexion 是 in-context、临时、per-task、零梯度 要把能力永久固化进模型、且有数据和算力时
Reflexion 把失败的标量信号翻译成语言教训、写进记忆、下轮读回 —(跨 trial · reward 驱动 · 语言记忆 · 零梯度) 能重试 + 有可靠 Evaluator + 初始正确率不高时

把分界轴归纳成两条问题,选型就清楚了:

  • 有没有可靠的外部对错信号? 没有 → CoT / Self-Refine(它们不依赖 verifier);有 → Reflexion / CRITIC / RLHF 才成立。
  • 改进要不要跨越多次尝试、并被记住? 任务内即时纠正 → CRITIC;一次尝试内铺开搜索 → ToT;要永久写进权重 → RLHF;跨 trial、用语言记忆、不动权重 → Reflexion。
深一层 · 最危险的混淆是 Self-Refine

Self-Refine 和 Reflexion 都"自评 + 自改",代码骨架看起来几乎一样。但 Self-Refine 的"评"是模型自己的主观判断、没有外部 ground truth,且不跨 trial、不持久化——它是一个 session 内对单个输出的润色。Reflexion 的"评"来自独立的 Evaluator(exact-match / 测试 / 环境 reward),改进被写进 episodic memory 跨尝试累积。一句话:Self-Refine 是"自我审稿",Reflexion 是"在客观判分下跨回合学习"。把 Reflexion 误用成 Self-Refine(去掉外部 reward),恰恰落进 Huang et al. ICLR 2024 证伪的那个区间——无 grounding 的内在自我纠错会净降准确率。RLHF/Self-Refine 这类"自我改进谱系"的整体定位,可参 agent 规划与反思教程。

还要补一句生态现实:Reflexion 的 Actor 内部通常就是一个 ReAct 或 CoT 智能体(它复用、而非取代它们),这也是为什么 Reflexion 坐落在这族方法之上而非与之并列。关于 ReAct / CoT 本身的机制,见 agent 推理模式教程。

跨概念综合题

场景:某团队让 LLM 玩 WebShop(在线购物,需要大量探索不同商品组合),用 Reflexion 反复跑了 4 个 trial,发现成绩到 trial 4 就平台期、且始终不超过纯 ReAct。请结合 2.3(Ω 滑动窗)、2.4(反思的机制)、2.5(分界轴)三节,解释为什么 Reflexion 在这个任务上不灵,并指出更该选哪种范式。

展开答案

三层原因叠加。(1) 任务-范式错配(2.5):WebShop 的瓶颈是"探索宽度"——需要尝试大量不同的商品/查询组合,这是 Tree of Thoughts 那种"一次尝试内铺开搜索"擅长的维度。Reflexion 探索的是"时间"(跨 trial 串行改进同一条思路),它不会主动拓宽探索面。(2) 反思的机制天花板(2.4):credit assignment 擅长修"哪一步逻辑错了",但 WebShop 的失败往往不是"某步错了",而是"压根没探到对的商品"——没有明确的错步可定位,反思无从发力,容易反复在同一片狭窄区域打转(local minima)。(3) Ω 滑动窗的健忘(2.3):需要长期积累探索经验的任务,Ω=1–3 的窗口记不住跨越多轮的探索历史,进一步压低了上限。结论:这类高探索任务应转向 ToT(广度搜索)或带显式探索策略的方法,而不是 Reflexion。这也呼应论文 Fig 6 与 04 章 #convergence。

本章自测

  1. 用一句话说清:为什么 LLM 权重冻结时,标量 reward 无法驱动改进?(提示:credit assignment 与 backprop)
  2. reflective text 比标量 reward 多携带了哪两类信息?分别对应 credit assignment 的哪个动作?
  3. 所谓"更新"在 Reflexion 里通过什么机制发生?policy 在数学上被参数化成了什么?
  4. 论文三类任务的 Evaluator 各是什么?哪一类把"判据"也交给了 LLM,因此最不可信?
  5. Ω 滑动窗口为什么是"容量上限"而不是 vector DB?调大和调小各有什么代价?
  6. "盲目重试 52% < 不重试 60%"说明了什么?它如何反驳"Reflexion 收益只是多试几次"?
  7. 跨机制综合:一个任务初始正确率已有 85%、且没有可靠 Evaluator。结合 2.1(信号)、2.2(reward 源)、2.5(分界轴),论证为什么对它用 Reflexion 很可能有害,并给出更合适的范式。
展开参考答案

1. 标量 reward 只携带"整体成败"一个比特、不能定位是哪一步错;而把这个责任分摊到决策上(credit assignment)在数值方法里靠 backprop 完成,权重冻结时没有梯度回传这条路,标量信号于是无法转化为可执行的改进。

2. 多携带了定位(指认具体 action ai,对应 credit assignment 的"分配责任")和处方(给出 a'i 该改成什么,对应"指明修正方向")。这两类信息任何固定维度的数值都编码不下。

3. 通过 in-context conditioning——下一轮 Actor 把反思文本读进 prompt、在新上下文下重新生成,不是 backprop。policy 被参数化为"agent 的记忆编码 + 冻结的 LLM 参数",trial 之间唯一变化的是上下文里的那段文本。

4. 推理=exact-match 对 ground-truth;决策=手写启发式或 LLM 二分类;编程=自生成单元测试。编程这一类把判据(测试)也交给 LLM 生成,最不可信——MBPP 上 16.3% 假阳性导致了论文唯一一次失利。

5. 它只保留最近 Ω(=1–3)条反思,目的是适配上下文长度限制、防止 prompt 溢出,并非按相似度检索的存储(论文把向量库列为 future work)。调小:省 token、抗溢出,但会遗忘跨多轮的教训;调大:记得多但 prompt 膨胀、变贵、稀释对最新教训的注意力。

6. 说明把原始失败轨迹塞回去不仅无益、反而有害(模型倾向复刻错误路径),起作用的是"反思"这步做的 credit assignment 而非"重试"本身。配合"反思比 memory-only 再 +8%",直接反驳"收益只是多试几次"——多试反而更差。

7. 两个条件同时踩雷。没有可靠 Evaluator(2.2):Reflexion 的反思可以错,但靠一个可信的对错信号兜底;缺了它,循环退化成无 grounding 的内在自我纠错,正是 Huang et al. 证伪的区间。初始正确率高(2.5/RA 阈值):反思只在初始正确率低(约 <20–30%)时净获益,对本就高置信的答案反而把对的改错(correct→incorrect 翻转)。这两点叠加,Reflexion 很可能净降准确率。更合适的选择:若只想润色单个输出,用普通生成 / 谨慎的 Self-Refine;若要保住已有的高正确率,干脆不加自我纠错循环。详见 04 章 #task-fit。

刚好够不着的挑战

给"语言信号"设计一个反例

2.1 节论证了语言信号优于标量。但语言信号的代价是"无法被验证为真"。请设计一个具体任务场景,使得语言反思的高表达力反而成为缺点——即反思越"自信、具体",对结果越有害。提示:把"自生成测试假阳性(2.2)"与"reflection hallucination"结合,想一个 Evaluator 和 Self-Reflection同时出错、且错误方向一致的情形。把它写成 3–4 句,作为进入 04 章前的诊断练习。

Further reading