Reflexion - Verbal Reinforcement Learning · essay
20. Reflexion:把失败后的反思写成语言记忆
论文:Reflexion: Language Agents with Verbal Reinforcement Learning
本地原文:
![[papers/20-Reflexion-2303.11366.pdf]]
论文定位与阅读路径
核心定位
Reflexion 的核心非常朴素:Agent 失败后,不马上训练模型,而是让它用自然语言总结失败原因,把这段反思放进记忆,下次尝试时读出来避免重复犯错。
概念解释:
不改模型参数,给 Agent 一本错题本。
原文结构导读
| 章节 | 内容 | 重点 |
|---|---|---|
| Abstract | 用语言反思增强 Agent | verbal reinforcement 是关键词 |
| 1 Introduction | 传统 RL 成本高,LLM Agent 需要快速试错 | 为什么不用梯度训练 |
| 2 Related Work | RL、推理、编程相关工作 | 可略读 |
| 3 Reflexion | 核心框架 | 重点 |
| 4 Experiments | 决策、编程、推理任务 | 看反思是否提升多轮表现 |
| 5 Limitations | 局限 | 反思质量依赖模型 |
| 6-8 | 影响、结论、复现 | 快速读 |
证据链阅读
| 证据点 | 读什么 | 工程判断 |
|---|---|---|
| 多轮尝试曲线 | 加入反思后是否随 attempt 变好 | 证明反思记忆能把失败转成下一轮策略 |
| 任务类型差异 | 决策、编程、推理任务效果是否一致 | 判断 Reflexion 适合哪类 Agent |
| Evaluator 可靠性 | 反馈信号是否客观、可检查 | 没有可靠反馈,反思可能只是自我合理化 |
| 反思文本质量 | 是否具体指出错误和下一步动作 | 工程上要约束反思格式,不能只写“更仔细” |
研究背景与问题定义
传统强化学习和 Reflexion 的区别
传统 RL 大致是:
代码块收起展开
行动 -> 奖励 -> 更新参数/策略Reflexion 是:
代码块收起展开
行动 -> 失败/反馈 -> 生成文字反思 -> 存入记忆 -> 下次 prompt 使用对比:
| 维度 | 传统 RL | Reflexion |
|---|---|---|
| 更新对象 | 模型参数/策略 | 语言记忆 |
| 成本 | 高 | 低 |
| 是否需要训练 | 通常需要 | 不需要微调 |
| 可解释性 | 较弱 | 强,反思是文本 |
| 稳定性 | 取决于训练 | 取决于反思质量 |
方法框架与核心机制
核心循环
这套循环非常适合编程 Agent:
代码块收起展开
写代码 -> 跑测试 -> 失败 -> 总结原因 -> 改代码 -> 再跑测试Reflexion 里的三个角色
| 角色 | 作用 | 编程类比 |
|---|---|---|
| Actor | 尝试完成任务 | 写代码的人 |
| Evaluator | 判断结果好坏 | 单元测试/评测器 |
| Self-Reflection | 总结经验教训 | 写错题本/复盘 |
这就是为什么它适合 Agent:Agent 不只是生成答案,还能根据环境反馈迭代。
反思记忆是什么
反思记忆不是事实记忆,而是经验记忆。
例子:
代码块收起展开
上次失败是因为只考虑了正数数组,没有处理全负数情况。下次先检查边界条件。这种记忆不一定长期保存几个月,但在当前任务的多次尝试中很有用。
为什么它有效
LLM 很擅长读自然语言指令。如果你把失败原因写成清晰语言,它下一轮就可能避开错误。
它利用了 LLM 的强项:
- 总结错误。
- 从反馈中提取规则。
- 根据文字提示调整行为。
这比直接训练参数快得多。
实验论证与评价方式
实验阅读方法
论文测试了不同类型任务,包括决策、推理和编程。你读实验时看:
- 加反思后,多次尝试的成功率有没有提升。
- 反思记忆长度有限时是否还有效。
- 和普通 ReAct、Chain-of-Thought 等方法相比差在哪里。
- 失败案例中反思是不是具体,而不是空话。
重点不是某个 benchmark 数字,而是这个机制:
代码块收起展开
自然语言反馈能否替代一部分参数更新?概念校准
你刷算法题时,如果只是看答案然后下一题,很容易重复错。
如果你写一条错题反思:
代码块收起展开
这题我漏了空数组。
双指针移动条件写反。
以后先列边界条件。下一次遇到类似题就更稳。Reflexion 就是给 Agent 做这个。
相关工作与方法对比
和其他记忆论文的关系
| 论文 | 记忆内容 |
|---|---|
| MemoryBank | 用户对话和画像 |
| Zep | 时序事实图谱 |
| Voyager | 成功技能代码 |
| Reflexion | 失败后的语言反思 |
| CoALA | 把反思归到经验/工作记忆 |
Reflexion 的价值是提醒你:
记忆不只记录事实,也要记录“为什么失败”和“下次怎么改”。
工程映射与实践启发
工程落地方案
编程 Agent 的反思表:
代码块收起展开
agent_reflection(
id, -- 反思记录 ID。
task_id, -- 对应哪一个任务。
attempt_no, -- 第几次尝试后产生的反思。
failure_signal, -- 失败信号,例如测试错误、环境反馈、评分失败。
reflection, -- 模型总结出的失败原因。
next_strategy, -- 下一次尝试应采用的策略。
created_at -- 反思生成时间。
)一次循环:
flowchart TD
A[生成代码] --> B[运行测试]
B --> C{测试通过}
C -->|是| D[保存成功经验]
C -->|否| E[提取错误日志]
E --> F[生成反思]
F --> G[下一轮 prompt]
G --> A
反思要写得具体
差反思:
代码块收起展开
我应该更仔细。好反思:
代码块收起展开
失败原因是没有处理空输入,下一轮先添加空数组判断,再检查循环边界。所以工程上要给反思生成器约束:
- 指出具体失败点。
- 指出证据。
- 给下一步动作。
- 不要写泛泛而谈。
局限、对比与阅读检查
局限与边界
- 反思可能是错的。
- 如果 evaluator 不可靠,反思也会偏。
- 反思太多会污染上下文。
- 它适合短期迭代,不等于长期知识库。
- 对需要真实环境探索的任务,仍可能尝试成本高。
阅读检查清单
- verbal reinforcement learning 是什么。
- Reflexion 为什么不需要更新模型参数。
- Actor/Evaluator/Self-Reflection 三角色。
- 反思记忆和事实记忆、技能记忆的区别。
- 如何把 Reflexion 用到编程 Agent 和学习 Agent。
深度精读:Reflexion 是最容易落地的“经验记忆”
1. 为什么它不需要训练
传统强化学习要更新策略参数。Reflexion 利用 LLM 的语言理解能力,把奖励/失败转换成自然语言提示:
代码块收起展开
失败信号 -> 反思文本 -> 下一轮上下文这意味着你用 API 模型也能做,不需要训练模型。
2. 反思必须来自反馈
没有反馈的反思容易变成自嗨。有效 Reflexion 需要:
| 任务 | 反馈 |
|---|---|
| 编程 | 测试失败、编译错误 |
| 网页操作 | 页面状态变化 |
| 问答 | 标准答案/用户纠正 |
| 学习 | 用户说哪里没懂 |
反馈越具体,反思越有用。
3. 反思不是总结
总结:
代码块收起展开
这次做了什么。反思:
代码块收起展开
为什么失败,下次要改变什么策略。差反思:
代码块收起展开
我应该更仔细。好反思:
代码块收起展开
这次讲解太短,因为只概括论文结论,没有拆 Method 和 Experiment;下一轮每篇必须增加机制流程、变量解释、工程伪代码和读实验问题。4. Reflexion buffer 应该有限
如果把所有反思都塞进上下文,会越来越乱。应该只保留:
- 当前任务相关反思。
- 最近失败原因。
- 高价值稳定教训。
长期反思可以沉淀成 procedural memory,短期反思留在 working memory。
5. 编程 Agent 落地
flowchart TD
A[生成修改] --> B[运行测试]
B --> C{通过}
C -->|是| D[保存成功技能]
C -->|否| E[提取失败信号]
E --> F[生成反思]
F --> G[下一轮修改约束]
G --> A
反思 prompt 要要求:
- 指出具体错误。
- 引用失败证据。
- 给下一步动作。
- 不要泛泛道歉。
6. 学习 Agent 落地
当你说“太浅”时,系统应该形成反思:
代码块收起展开
用户不是要摘要,而是要论文级拆解。
之后每篇必须包括原文结构、方法细节、实验读法、工程迁移和易混点。这条反思应该立刻影响后续输出。
7. Reflexion 的风险
- 反思可能错。
- 反馈可能不完整。
- 反思可能过拟合一次失败。
- 反思太多会干扰。
所以反思也要有置信度和适用范围。
8. 和 Voyager 组合
最强闭环是:
代码块收起展开
失败 -> Reflexion 保存教训
成功 -> Voyager-style skill 保存做法这就同时有“错题本”和“技能库”。
精读补强:Reflexion 的证据链与工程判断
Reflexion 最容易落地,但也最容易被滥用成“失败后写一句鸡汤”。读它时要看反思是否由可验证失败信号驱动。
| 判断点 | 原文证据 | 工程含义 |
|---|---|---|
| 失败信号 | 环境反馈、测试结果、评分器输出 | 反思必须绑定具体失败,不是泛泛总结 |
| 反思内容 | verbal reflection 写入长期记忆 | 记录应包含原因、约束、下次策略 |
| 再尝试机制 | 下一轮 prompt 读取反思 | 反思必须进入后续决策链路才有价值 |
| 评价方式 | 多轮尝试成功率提升 | 要测“有反思 vs 无反思”的差异 |
工程落地时,agent_reflection 表至少要保存 task、attempt、failure_signal、reflection、next_strategy。没有 failure_signal,反思会变成无法审计的自然语言缓存。
工程落地的最小闭环
Reflexion 在代码 Agent 里最容易做成可验证闭环:
代码块收起展开
生成方案 -> 执行/测试 -> 提取失败信号 -> 写反思 -> 下一轮读取反思关键是反思不能脱离测试。比如一次 npm test 失败,系统应该保存:
| 字段 | 示例 | 作用 |
|---|---|---|
| failure_signal | Expected 200, received 500 | 锚定真实失败 |
| reflection | 接口没有处理空 userId | 总结可迁移原因 |
| next_strategy | 先补参数校验,再跑 user API 测试 | 约束下一轮行动 |
| evidence_ref | 测试日志路径或 trace span | 方便复查反思是否可靠 |
如果下一轮没有读取 next_strategy,这条反思就只是日志,不是记忆。真正的 Reflexion 必须让失败经验改变下一次行动。
还有一个硬约束:反思只能总结已观察到的失败,不能编造原因。比如测试日志只说明断言失败,就不要直接写“数据库连接池有问题”;应先写“接口返回值与期望不一致”,下一轮再通过更小的检查定位根因。这样反思才不会把猜测固化成长期记忆。
这条约束比“多写反思”更重要。
论文精读主线:问题-方法-验证-局限-启发
1. 核心结论
Reflexion 把失败反馈转成自然语言反思记忆,让 Agent 不更新参数也能在下一次尝试中避免同类错误。
本篇的阅读重心是:把失败反馈写成自然语言反思,使下一次尝试少犯同类错误。读完后要能说清它改变的是 runtime、存储、检索、更新还是评测层;否则只是记住了论文名。
2. 问题背景
LLM Agent 在编程、推理和网页任务中常会重复犯错。传统强化学习要训练参数,成本高;普通 ReAct 失败后未必吸取教训。问题是如何用低成本方式把失败经验转成可复用策略。
把问题具体化为失败样例:Agent 测试失败后只重试 prompt,不记录失败原因;下一轮又犯同一类 API、路径或逻辑错误。用这个样例反推论文动机,比泛泛说“上下文不够”或“记忆不准”更有判断力。
3. 核心方法
循环由 Actor、Evaluator、Self-Reflection 组成。Actor 执行任务,Evaluator 给成功/失败和反馈,Self-Reflection 把失败原因和下次策略写成反思,下一轮 Actor 把反思放入上下文。
flowchart TD
T[任务] --> A[Actor 尝试]
A --> E[Evaluator 反馈]
E --> D{成功?}
D -->|是| S[结束]
D -->|否| R[Self-Reflection]
R --> M[反思记忆]
M --> A
方法部分按数据流读:Actor 执行任务,Evaluator 判断成败并给证据,Self-Reflection 生成 lesson,下一轮把 lesson 注入上下文。每一步都要能标出输入、输出、控制点和失败后该记录的日志。
4. 技术细节
反思不是总结,而是可执行改进策略。好的反思包含失败原因、证据、下次动作、适用边界。Evaluator 的可靠性决定反思质量;反馈错了,Agent 会学错。
代码块收起展开
final class ReflexionLoop {
private final TaskActor actor; // 根据任务和反思记忆执行下一次尝试
private final FeedbackEvaluator evaluator; // 用测试、规则或人工反馈判断成功失败
private final ReflectionGenerator reflector; // 把失败转成具体、可执行、有限长度的反思
private final ReflectionBuffer buffer; // 保存最近有效反思,过期或无效反思要清理
MemoryResult handle(MemoryInput input) {
// 1. 根据任务和反思记忆执行下一次尝试
// 2. 用测试、规则或人工反馈判断成功失败
// 3. 把失败转成具体、可执行、有限长度的反思
// 4. 保存最近有效反思,过期或无效反思要清理
// 输出中保留 traceId、sourceIds、decisionReason,方便复盘和回滚。
return MemoryResult.withTrace(input.traceId());
}
}字段检查重点是 failureEvidence、lesson、nextAction、scope、ttl 决定反思是否可执行。这些字段不是装饰,它们决定后续能否检索、审计、回滚、限权和做离线评测。
5. 实验验证
实验看多次尝试成功率是否提升,特别是失败后是否减少同类错误。要和 ReAct、CoT、无反思记忆对比,并分析反思文本是否具体有效。
实验阅读重点:看多轮成功率、同类错误复发率、反思文本质量;没有可靠 evaluator 时不应写反思。如果论文没有直接给出这层证据,复现时就要自己补机制级指标。
6. 局限与启发
没有可靠反馈就不适合 Reflexion。启发是编程 Agent 可以把测试失败、lint 错误、用户批评写成短期反思;学习笔记任务可以把“太浅、标题不专业、代码没注释”转成写作约束。
迁移到你的项目时,结论是:你批评笔记“浅、废话、标题不专业、代码没注释”本身就可以转成写作反思约束。先把边界做对,再追求更复杂的模型或更大的记忆规模。
7. 关键术语表
| 术语 | 在论文里的意思 | 工程落地时要看什么 |
|---|---|---|
| Verbal reinforcement | 自然语言强化反馈 | 把失败经验写成文本策略 |
| Actor | 执行任务的 Agent | 读取反思并尝试解决 |
| Evaluator | 评估器 | 测试、规则、用户反馈 |
| Reflection buffer | 反思缓存 | 有限条数、可过期的经验记忆 |
8. 复现难点
反思最容易变空话,例如“下次更仔细”。必须要求具体到错误原因和下一步动作。反思过多会污染上下文,所以 buffer 要有限,且成功后要清理无效反思。
复现入口应从失败集开始:构造会失败的代码/写作任务,让系统读取上一轮反思后重做,检查是否避免同一错误。只有先证明旧方案会坏,才知道新机制是否真的解决了问题。
9. 代码框架建议
实现 Reflection(id,taskType,failureEvidence,lesson,nextAction,scope,createdAt,ttl)。生成反思前必须有 evaluator 输出;没有反馈不允许自我脑补。
代码验收门槛:反思必须绑定具体失败证据;没有证据的“下次更仔细”直接丢弃。这条门槛应写进测试或 trace 字段里,避免实现只停留在 demo。
10. 相关论文对比
Voyager 保存成功技能,Reflexion 保存失败教训。Generative Agents 的 reflection 是高层社会理解,Reflexion 的 reflection 是任务改进策略。CoALA 可把它放在 episodic/reflective 机制里。
横向比较时抓这个角度:Reflexion 存失败教训,Voyager 存成功技能,Generative Agents 存行为反思。比较对象不是谁“更先进”,而是谁负责哪一层失败。
11. 批判性思考
Reflexion 便宜好用,但不是万能 RL。它依赖反馈覆盖真实目标,也依赖模型正确解释失败。长期反思库如果不清理,会积累错误经验。
批判边界:错误反馈会产生错误反思;反思库必须有过期、覆盖和成功后清理机制。这不是否定论文,而是把论文结论放回真实系统的成本、权限和恢复约束里。