Reflexion 反思式自我纠错
Reflexion 的核心是把失败信号转成一段自然语言经验,写进记忆,再喂给下一次尝试。它用「语言反馈」代替了「梯度更新」——模型权重不动,靠上下文里多出来的那段教训变强。
Reflexion:反思式自我纠错
阅读提示 面向已经写过「失败就重试」逻辑的开发者。 前置概念:Agent 循环、工具调用返回值(测试结果 / 报错信息)。 ⚠️ = 常见陷阱 🆚 = 对比说明 💡 = 选择建议
目录
核心概念 Reflexion 的核心是把失败信号转成一段自然语言经验,写进记忆,再喂给下一次尝试。它用「语言反馈」代替了「梯度更新」——模型权重不动,靠上下文里多出来的那段教训变强。
一、一句话定义
做错了以后,不只是重试,而是先总结自己为什么错,再带着这条教训重试。
二、最小示例:一次自我修复
任务:写代码解决一道算法题。
第一轮:
生成代码
↓
运行测试 → 失败:IndexError on empty input
↓
Reflection(模型自己写的):
"我假设输入数组至少有一个元素,没处理 nums 为空的情况。
下次先在函数开头加空数组的早返回。"
第二轮:
把上面那段 Reflection 拼进 prompt
↓
重新生成代码(这次带了 if not nums: return 0)
↓
测试通过 ✅抽象成循环:
Action
↓
Result(测试结果 / 报错 / 评分)
↓
Reflection(自然语言教训,存入记忆)
↓
Improved Action三、🆚 和普通重试的区别
这是整个概念的重点,两者代码看起来都是 for i in range(3),但中间那一步完全不同。
普通重试:
失败 → 再试一次(prompt 一个字都没变)Reflexion:
失败
↓
分析为什么失败 ← 多出来的关键一步
↓
记录经验(写入记忆)
↓
带着经验重新尝试 ← prompt 变了| 维度 | 普通重试 | Reflexion |
|---|---|---|
| 下一次的输入 | 与上次完全相同 | 多了一段失败教训 |
| 失败信息利用 | 丢弃 | 转成自然语言存下来 |
| 多次失败 | 大概率重复同一个错 | 教训累积,错误面收敛 |
| 额外成本 | 无 | 每轮多一次生成反思的调用 |
为什么普通重试大概率重复同一个错 LLM 采样虽有随机性,但在同一个 prompt 下,输出分布的众数是稳定的。同样的输入重采样三次,很可能三次都掉进同一个思维定式。Reflexion 之所以有效,是因为它改变了输入,从而改变了整个输出分布,而不是在原分布里碰运气。
四、机制:三个角色和一块记忆
一个完整实现通常包含:
| 角色 | 职责 | 实现方式 |
|---|---|---|
| Actor | 产出动作/代码/答案 | LLM 生成,输入 = 任务 + 短期轨迹 + 长期反思 |
| Evaluator | 评价这次做得怎么样,给出反馈 | 单元测试、编译器、外部打分器、启发式规则,或另一个 LLM |
| Self-Reflection | 把「轨迹 + 反馈」写成一段语言化的教训 | LLM 生成 |
| Memory | 短期存本次轨迹,长期存历次反思 | 见下方说明 |
⚠️ 把 Evaluator 和 Self-Reflection 当成同一个东西 很多二手资料(包括本笔记的旧版本)会写成「Evaluator 负责生成反思」。原论文里这是两个分开的组件,拆开才说得通:
- Evaluator 回答「做得好不好」——输出是奖励值、通过/失败、或一段错误信息。它可以完全不是 LLM(跑一遍 pytest 就是个 Evaluator)。
- Self-Reflection 回答「为什么不好、下次怎么改」——输入是「执行轨迹 + Evaluator 的反馈」,输出是一段自然语言教训。这一步必须是 LLM。
合成一个会导致一个很实际的后果:你以为「换成 pytest 当 Evaluator」就完事了,但 pytest 只会吐报错,没有人把报错翻译成教训——记忆里存的是原始 stack trace,下一轮 Actor 读到的还是同一段报错,等于退化成普通重试。
两块记忆,别只留一块
原论文里 Memory 分两层,实现时最常被漏掉的是第一层:
- 短期记忆 = 本次尝试的执行轨迹(做了哪些动作、每步的观察结果)。它是 Self-Reflection 的输入之一——没有轨迹,反思就只能对着最终结果猜「哪一步错了」。
- 长期记忆 = 历次反思的集合(跨 trial 累积),拼进下一轮 Actor 的 prompt。
只用一个 list[str] 存反思,等于只做了长期那一层。
最小骨架:
long_term = [] # 跨 trial 累积的反思
for attempt in range(MAX_ATTEMPTS):
# trajectory 是本次尝试的短期记忆:动作 + 每步观察
action, trajectory = actor(task, long_term)
feedback = evaluator(action, trajectory) # 只回答「好不好」
if feedback.passed:
return action
# 反思的输入必须包含轨迹,否则它不知道是哪一步出的问题
reflection = reflect(task, trajectory, feedback)
long_term = [*long_term, reflection] # 不可变追加
raise MaxAttemptsExceeded(long_term)Evaluator 的质量决定上限 反思的输入是评估结果。如果 Evaluator 只能说「错了」,反思就只能瞎猜;如果 Evaluator 能给出
IndexError: list index out of range, line 7,反思就能精确定位。优先用确定性的评估器(测试、编译器、schema 校验),而不是让 LLM 给自己打分。但也别走到另一个极端:Evaluator 的输出不必是二值的。原论文同时支持标量奖励、二值成败和自由文本反馈——在 AlfWorld 这类决策任务里用的就是启发式规则(比如「同一个动作重复了 N 次还没进展」判定失败),并不存在单元测试。信号要具体、要可归因,但不要求非黑即白。
五、⚠️ 核心陷阱:反思写成了废话
⚠️ 反思空洞化 错误操作: 反思 prompt 写成「请反思上一次的错误」,然后直接把输出拼进记忆。
实际结果: 记忆里堆的是这种句子:
"我应该更仔细一些。" "下次要考虑边界情况。" "需要更全面地理解题目。"三轮之后,prompt 变长了、成本涨了,但错误一模一样。
原因: 这些句子不含任何新增信息。它们对下一次生成没有约束力——模型读完「要考虑边界情况」,依然不知道是哪个边界。反思只有携带具体、可执行的修正指令时才改变输出分布。
正确做法: 在反思 prompt 里强制要求结构和具体性,例如:
用三句话回答,禁止使用"更仔细""更全面"这类词: 1. 具体哪一行 / 哪个假设错了? 2. 为什么这个假设不成立? 3. 下次要写的具体改动是什么?(给出代码片段或明确条件)好的反思长这样:「第 7 行
nums[0]假设数组非空;测试用例[]会触发 IndexError;下次在函数开头加if not nums: return 0。」
⚠️ 记忆无限增长 错误操作: 每轮反思都追加进 memory,永不清理。
实际结果: 第 10 轮时 prompt 里塞了 10 段教训,其中大半是早已修复的问题;模型注意力被稀释,甚至会「改回」之前已经修好的地方。
原因: 上下文里的每一条指令都在争夺注意力,过期教训和有效教训权重相同。
正确做法: 限制记忆窗口(常见做法是只保留最近 3 条),或在每轮让模型把历史反思合并压缩成一份「当前有效约束清单」。
六、什么时候用
用:
- Coding Agent:有单元测试当 Evaluator,信号最干净,收益最大
- 自动调试、编译错误修复
- 有明确成败判定的任务(游戏、benchmark、schema 校验)
不用:
- 完全拿不到可归因反馈的开放式任务(写文案、做总结)——注意判据是「反馈能不能指向具体哪里不行」,不是「有没有单元测试」。开放任务如果有明确评分维度(字数、必含要点、风格清单)或真人评语,Reflexion 一样能用;只有当反馈退化成一个说不清所以然的总分时,反思才变成自说自话。
- 一次就该做对的低延迟场景——Reflexion 至少让延迟和成本翻倍
七、复习重点
复习重点
- 定义:失败 → 生成自然语言教训 → 存入记忆 → 带教训重试。用语言反馈代替梯度更新。
- 和重试的本质差别:重试不改 prompt(同分布重采样,大概率同样出错),Reflexion 改 prompt(换了输出分布)。
- 三个角色分工别混:Actor 执行、Evaluator 评价并给反馈、Self-Reflection 把反馈写成教训。后两个是分开的组件——只换 Evaluator 不接 Self-Reflection,等于退化成普通重试。
- 两块记忆:短期 = 本次执行轨迹(Self-Reflection 的输入),长期 = 历次反思。只用一个
list[str]是漏了短期那块。- 最容易出错的地方:反思空洞化。必须强制输出「哪里错 / 为什么错 / 具体怎么改」,禁用「更仔细」类空话。
- 第二个坑:记忆无限增长会稀释注意力,要限窗口或做压缩合并。
- 前提条件:Evaluator 质量决定反思质量,优先用测试、编译器这类确定性信号。但反馈不必是二值的——标量奖励、自由文本、启发式规则都行,要求是「可归因」而非「非黑即白」。
- 它管哪一层:失败回路。和 Plan-and-Execute 的 Replan 天然互补——反思产出的教训正好是重新规划的输入。
相关笔记:Agent 推理范式总览|Plan-and-Execute 先规划再执行|Self-Ask 自问自答多跳推理|ToT 思维树