Reflection
03-ReAct 和 04-Plan-and-Solve 都是任务做完就结束。但它们生成的初始答案——无论是行动轨迹还是最终结果——都可能有误或不够好。Reflection 引入的是一个事后(post-hoc)的自我校正循环:审视自己的工作,发现不足,迭代优化。
先分清机制与具体实现
这一篇要同时交代两个层次,混着读会失真——和 04-Plan-and-Solve 里那个「提示技巧 vs 工程实现」的两层区分是同一类问题:
| 层次 | 指什么 | 边界 |
|---|---|---|
| Reflection(机制) | 一类做法:让模型回看自己的输出并据此修改,可以是同一次对话里的自我批评,也可以是跨轮次的记忆化复盘 | 是方法论类别,有多种实现 |
| Reflexion(具体实现) | Shinn et al., NeurIPS 2023 提出的具体方案——用语言强化 + 情景记忆把复盘持久化 | 是一个具体实现,也是这一类里最有名的一个 |
本篇以 Reflexion 为主线(它给出了完整的组件划分、形式化与实验数据),但凡是属于机制共性的部分会标明——比如「反馈必须是独立信号」这条对任何 Reflection 实现都成立,不是 Reflexion 特有的。
为什么这条区分重要:说「我用了 Reflection」只说明你在做自我校正;说「我用了 Reflexion」则承诺了一套具体结构(Actor / Evaluator / 反思者 + 记忆)。面试或技术评审里这两句话的分量不一样。
分辨「机制」与「其中一个具体实现」是两个层次。
Reflection(机制 · 方法论类别)
定义:让模型回看自己的输出并据此修改
两种形态:同一次对话里的自我批评 / 跨轮次的记忆化复盘
有多种实现
│
▼ Reflexion 是其中最有名的一个
Reflexion(具体实现 · 方法)
用语言强化 + 情景记忆把复盘持久化
给定了完整结构:Actor / Evaluator / 反思者 + 记忆
配套形式化与实验数据
│
└─ 凡属机制共性的部分,对任何 Reflection 实现都成立,
不是 Reflexion 特有的(例:「反馈必须是独立信号」)
这两句话的分量不一样:
「我用了 Reflection」= 我在做自我校正
「我用了 Reflexion」= 我承诺了一套具体结构语言强化:为什么要用一段话代替一个分数
传统 RL 的反馈是一个标量奖励,Agent 需要反复试错、逐步调参数才能改进。问题是**「得分低」这件事本身不含方向信息**——它不告诉 Agent 错在哪、下次怎么办。
Reflexion 的做法是把环境反馈翻译成一段自然语言的自我反思:
环境反馈:代码编译失败,报错「变量未定义」
语言反思:我刚才写的代码报错了,因为我尝试使用一个还没声明的变量 x。
下次我应该在使用任何变量之前,先确保它已经被正确初始化。这被称为 Verbal Reinforcement(语言强化),那段话的作用相当于语义梯度(semantic gradient)——它明确指出了「下降」的方向。
代价对比一目了然:
| 传统 RL | Reflexion | |
|---|---|---|
| 反馈形态 | 一个数值(reward) | 一段语言(反思文本) |
| 学习方式 | 更新模型权重 | 追加到记忆 |
| 成本 | 高,需要大量样本与计算 | 低,无需微调,几次尝试即可 |
| 可解释性 | 差,黑箱 | 强,能读到 Agent 的「心路历程」 |
注意「不改参数」这条边界的双面性:反思可以跨任务保留,所以已经是持久改进;但被保存的是外部文字记忆,不是模型参数。把记忆一撤,能力就回到原点。
三个组件
| 组件 | 角色 | 实现 |
|---|---|---|
| Actor(行动者) | 干活。根据当前状态和记忆生成行动 | 本身就是一个 LLM,策略可以用 CoT 或 ReAct——Reflexion 是叠在它们之上的 |
| Evaluator(评估者) | 裁判。判断这次行动结果好坏 | 三种形态,见下 |
| Self-Reflection(反思者) | 灵魂导师。生成反思文本 | 一个 LLM,接收完整轨迹 + Evaluator 的反馈 |
三个组件构成一个闭环,Evaluator 决定了循环有没有意义。
任务描述
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
长期记忆 当前轨迹 三样一起进提示词
(过去几条反思) (短期记忆) │
│ │ ▼
└──────────────────┴──────────────▶ ┌──────────┐
│ Actor │
└────┬─────┘
│ 行动与输出
▼
┌──────────┐
│Evaluator │ 三种形态:
└────┬─────┘ 精确匹配 / 规则启发 /
│ LLM 自生成测试
│ 好坏 + 依据
▼
┌──────────────────┐
│ Self-Reflection │ 收完整轨迹 + Evaluator 反馈
└────────┬─────────┘
│ 一段自然语言反思
▼
┌──────────────────┐
│ Memorize │ 存长期记忆,只留最近 k 条
└────────┬─────────┘
│
└──▶ 回到 Actor第三种 Evaluator 最有迁移价值:在没有现成测试的场景里,用模型自己造出可执行的判据,把一个「主观评价」问题转成「跑不跑得过」的客观问题。这也是 Reflexion 在代码任务上效果最突出的原因。
Evaluator 的三种实现(这一层怎么选,是效果差异的主因)
| 形态 | 做法 | 适用 |
|---|---|---|
| 精确匹配 | 直接比对标准答案 | 有明确答案的任务(HotPotQA) |
| 规则启发 | 人工设定失败判据 | ALFWorld:在同一地点连续执行同样无效动作超过 3 次,或总步数超过 30 步(说明规划效率过低) |
| LLM 自生成测试 | 让模型根据函数说明自己写单元测试,再用它检验 Actor 的代码 | 编程任务——这是 Reflexion 在代码上效果最突出的关键 |
第三种是这套方法最有迁移价值的设计:在没有现成测试的场景里,用模型自己造出可执行的判据。 它把一个「主观评价」问题转成了「跑不跑得过」的客观问题。
流程与形式化
┌─ 一次反思迭代 ────────────────────────────────────────────────┐
│ │
│ Act(执行) │
│ 输入 = 任务描述 + 长期记忆(过去几条反思)+ 当前尝试的轨迹 │
│ │ │
│ ▼ │
│ Evaluate(评估) │
│ Evaluator 给出「这次结果好不好」以及依据 │
│ │ │
│ ▼ │
│ Self-Reflect(生成语言化反思) │
│ 反思者拿到完整轨迹 + Evaluator 的反馈,产出一段文字 │
│ │ │
│ ▼ │
│ Memorize(存入情景记忆) │
│ 反思文本进长期记忆库,只保留最近 k 条(k = 3) │
│ │ │
│ ▼ │
│ Retry(带着反思重试)──▶ 回到 Act │
│ │
└───────────────────────────────────────────────────────────────┘
循环持续到任务成功或达到最大迭代次数。循环持续到任务成功或达到最大迭代次数。
形式化(
记忆分两层
| 层 | 存什么 | 作用 |
|---|---|---|
| 短期记忆 | 当前这次任务的完整轨迹(每步的思考、行动、观察) | 提供细粒度的、正在发生的事实 |
| 长期记忆 | 历史反思文本库 | 提供跨任务的「教训」 |
为了不让上下文窗口爆炸,长期记忆只保留最近的 k 条反思(该文取 k=3)。 这跟人一样——记住最重要的教训,淡忘久远的细节。
下一次行动时,Actor 的提示词里同时包含三样:任务描述 + 长期记忆(过去几条反思)+ 当前尝试的轨迹(短期记忆)。
这个机制在工程实现上就是一个显式的 Memory 模块:
class Memory:
def __init__(self):
self.records: List[Dict[str, Any]] = []
def add_record(self, record_type: str, content: str):
"""record_type: 'execution' 或 'reflection'"""
self.records.append({"type": record_type, "content": content})
def get_trajectory(self) -> str:
"""把全部记录序列化成文本,直接插进提示词"""
parts = []
for r in self.records:
if r['type'] == 'execution':
parts.append(f"--- 上一轮尝试 (代码) ---\n{r['content']}")
elif r['type'] == 'reflection':
parts.append(f"--- 评审员反馈 ---\n{r['content']}")
return "\n\n".join(parts)
def get_last_execution(self) -> Optional[str]:
for r in reversed(self.records):
if r['type'] == 'execution':
return r['content']
return None三个接口的分工:add_record 写入,get_trajectory 把轨迹序列化成可插入提示词的文本(供反思用全量上下文),get_last_execution 反向查找最近一次初稿(供优化阶段取「待改的那一版」)。
记忆不止能存文本——同一套结构允许反思和修正代码、图像等非文本输出,这是多模态智能体的基础。
实验数据
| 基准 | 此前最好 | Reflexion | 提升 |
|---|---|---|---|
| HumanEval (Python) | GPT-4 80.1% | 91.0% | +10.9 |
| HumanEval (Rust) | GPT-4 60.0% | 68.0% | +8.0 |
| MBPP (Python) | GPT-4 80.1% | 77.1% | -3.0 |
| MBPP (Rust) | GPT-4 70.9% | 75.4% | +4.5 |
| Leetcode Hard (Python) | GPT-4 7.5% | 15.0% | 翻倍 |
| ALFWorld(ReAct + Reflexion) | ReAct-only | 97%(134 个环境 12 次尝试内解决 130 个) | +22 个百分点 |
| HotPotQA | ReAct 32% | 53% | +21 |
| HotPotQA(给定标准答案上下文) | 61% | 75% | +14 |
三个值得注意的地方:
- HumanEval 91% 超过同期的 GPT-4(80.1%)——而 Reflexion 自己不改任何参数,只是给 GPT-4 加了一层反思循环。
- HotPotQA 的第二个数字更说明问题:把标准答案的上下文直接给它、只考纯推理,成功率仍能从 61% 提到 75%。信息都在手上,Agent 依然会因为错误的推理路径得出错误结论——反思修的是这一层。
- MBPP 上反而下降了(77.1 vs 80.1)。说明这不是普适增益。
同一批基准上,加一层反思循环带来的变化。
HumanEval(Python)
GPT-4 80.1% ← 此前最好
Reflexion 91.0% ████████████████ +10.9
└─ Reflexion 自己不改任何参数,只是给 GPT-4 加了一层反思循环
Leetcode Hard(Python)
GPT-4 7.5%
Reflexion 15.0% ← 翻倍
ALFWorld(ReAct + Reflexion)
ReAct-only ──▶ 97%(134 个环境,12 次尝试内解决 130 个)
+22 个百分点
HotPotQA
ReAct 32% ──▶ Reflexion 53% +21
给定标准答案上下文:61% ──▶ 75% +14
└─ 信息都在手上,Agent 依然会因为错误的推理路径得出错误结论 ——
反思修的就是这一层
MBPP(Python)
GPT-4 80.1%
Reflexion 77.1% ← 反而下降 3.0,说明这不是普适增益
消融实验(拆台做法):只保留自生成测试、但不做语言反思 ──▶ 性能几乎没有提升。
「光知道错了」不够,必须搞清楚「为什么错」和「下次怎么办」——
这条直接回答了为什么不用一个分数代替一段话。消融实验的一句关键结论
该文做了个「拆台」实验:只保留自生成测试,但不做语言反思——性能几乎没有提升。
光知道「错了」不够,必须通过语言反思搞清楚「为什么错」和「下次怎么办」。
这条直接回答了「为什么不用一个分数代替一段话」。
一个代码优化的实例
任务:写一个 Python 函数,找出 1 到 n 之间所有的素数。
| 轮次 | 发生什么 | 结果 |
|---|---|---|
| 初始 | 模型给出试除法实现 | 时间复杂度 O(n·√n),功能正确但慢 |
| 第 1 轮反思 | 指出每个数都要试除是瓶颈 | 建议改用埃拉托斯特尼筛法,O(n log log n) |
| 第 1 轮优化 | 按反馈改写 | 落地为埃氏筛 |
| 第 2 轮反思 | 认可现有复杂度,另提分段筛法、奇数筛法 | 结论:「一般情况下无需改进」 |
| 终止 | 反思无新问题 | 收敛 |
三个观察:
- 有效的批判是优化的前提。 第一轮反思之所以有价值,是因为提示词被设定成「极其严格」且「专注算法效率」——否则模型会满足于「功能正确的初版」。
O(n·√n)→O(n log log n)是算法层面的跃迁,不是代码风格微调。Reflection 能驱动的正是这种跃迁。- 收敛判断和优化同等重要。 第二轮反思在提到更高级的优化方向之后主动判定「无需改进」,这个判断触发了终止条件。没有它,迭代会一直烧钱。
成本收益:典型的以成本换质量
三项成本:
| 成本 | 量级 |
|---|---|
| 模型调用开销 | 每轮迭代至少多调两次 LLM(一次反思、一次优化),多轮则成倍增加 |
| 任务延迟 | 串行过程,每轮优化必须等上一轮反思完成,实时场景不可接受 |
| 提示工程复杂度 | 执行、反思、优化三套提示词都要单独设计调试 |
两项收益:
- 解决方案质量的跃迁——从「合格」到「优秀」,从功能正确到性能更优
- 鲁棒性提升——内部纠错能发现并修复逻辑漏洞、事实性错误、边界情况处理不当
适用判据:对结果质量、准确性、可靠性要求极高,且对实时性要求宽松。典型场景是生成关键业务代码或技术报告、科研中的复杂逻辑推演、需要深度分析与规划的决策支持系统。
反过来,需要快速响应、或「大致正确」就够的场景,用 ReAct 或 Plan-and-Solve 性价比更高。
三条局限
一条边界(后续分析也反复提到):
- 效果上限取决于反思者的能力。 如果 LLM 本身「固执」或推理能力不足,它生成的反思可能无效甚至误导——用同一把尺子量同一块布,很难量出自己的盲区。这也是 LLM Evaluation 与反馈闭环 里「同源盲区」要单独处理的原因。
- 可能陷入局部最优。 Agent 反思出一种「还行」的策略后就一直在这条路上小修小补,跳不出去探索全局更优解。在 WebShop 上就没有显著提升——那个任务需要更多样化、更有创造性的探索行为,而不是对既有策略的微调。
- 没有收敛保证。
这三条合起来给出一条判据:Reflection 擅长「把一个方向上的解做好」,不擅长「换一个方向」。
相关
- 03-ReAct、04-Plan-and-Solve —— Reflection 的「初稿」环节可以由任意一个承担;Reflexion 那篇里 Actor 用的就是这两者
- AI 生成代码的质量保障 —— 同一思路在整条工程链路上的位置
- LLM Evaluation 与反馈闭环 —— 「同源盲区」在这里是第一号局限
参考
- 《Hello-Agents》第四章 §4.4
- Shinn, N., Cassano, F., Yao, S., et al. Reflexion: Language Agents with Verbal Reinforcement Learning. arXiv:2303.11366, NeurIPS 2023.
- https://www.sohu.com/a/1078305796_122105141
- 同上
- https://news.qq.com/rain/a/20250929A07UAL00
YJ