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 从标量升级成向量(给每一步打分),似乎能恢复定位。但向量回答的仍是"第 i 步好不好",不回答"第 i 步应该怎么做"。诊断与处方是两件事。Reflexion 论文把改进信号交给一个 LLM 用自然语言表达,正是因为自然语言能同时携带"哪一步""为什么错""改成什么"——这是任何固定维度的数值向量都编码不下的结构。
语言反思如何完成 credit assignment
Self-Reflection 模型(Msr)接收三样东西:任务描述、完整 trajectory、Evaluator 的判定。它产出一段自然语言 reflective text。这段文本做的恰恰是标量 reward 做不到的事——它把整体失败归因到具体某步,并给出修正方向。论文里这段文本是第一人称写的,例如一条编程任务的反思:
"我只取了排序后中间的单个元素,没有处理列表长度为偶数的情况;下次遇到偶数长度,应该取中间两个数的平均。"
对照看:标量信号是 AssertionError 这一个 fail;语言信号则定位到"取中间单个元素"这一步(credit assignment 的定位),并给出"取两数平均"这一处方(credit assignment 的方向)。这就是一个高维、带定位的纠错信号:它的"维度"是自然语言的全部表达力,它的"定位"是对具体 action 的指认。下一个 trial 开始时,Actor 把这段文本拼进 prompt——所谓"学习",是 Actor 在新的上下文条件下重新生成,即 in-context conditioning,而不是参数被梯度推动。policy 在数学上被参数化为"记忆编码 + 冻结的 LLM 参数",trial 之间唯一变化的就是上下文里那段文本。这正是 "verbal reinforcement learning(语言强化学习)" 为何是字面意义:用语言而非梯度去强化。
备选信号对比
| 信号形态 | 能否定位是哪一步 | 能否给出修正方向 | 更新机制 |
|---|---|---|---|
| 标量 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 实现 | 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 的选择,直接决定整个循环的可靠性上限。
编程任务里 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 看到的是被提炼过的教训,不是原始的失败轨迹。
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。
def add(self, sr: str) -> None:
self.reflections.append(sr)
self.reflections = self.reflections[-self.capacity:] # 只保留最近 capacity 条
Ω 调小:省 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 的分界轴 | 什么时候选它而非 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 和 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。
本章自测
- 用一句话说清:为什么 LLM 权重冻结时,标量 reward 无法驱动改进?(提示:credit assignment 与 backprop)
- reflective text 比标量 reward 多携带了哪两类信息?分别对应 credit assignment 的哪个动作?
- 所谓"更新"在 Reflexion 里通过什么机制发生?policy 在数学上被参数化成了什么?
- 论文三类任务的 Evaluator 各是什么?哪一类把"判据"也交给了 LLM,因此最不可信?
- Ω 滑动窗口为什么是"容量上限"而不是 vector DB?调大和调小各有什么代价?
- "盲目重试 52% < 不重试 60%"说明了什么?它如何反驳"Reflexion 收益只是多试几次"?
- 跨机制综合:一个任务初始正确率已有 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
- Shinn et al., 2023 · Reflexion: Language Agents with Verbal Reinforcement Learning(算法、消融、三类 reward 来源、MBPP 失利分析)
- Prompt Engineering Guide · Reflexion(机制速览与对比)
- Madaan et al., 2023 · Self-Refine(最易与 Reflexion 混淆的范式,对照 2.5)
- Gou et al., 2023 · CRITIC(工具 grounding 的就地纠正,对照 2.5)
- 兄弟教程 · agent 规划与反思(自我改进谱系中 RLHF / Self-Refine 的定位)