Reflexion:用语言反馈让 Agent 从失败中学习
原始来源
Noah Shinn 等,2023:Reflexion: Language Agents with Verbal Reinforcement Learning,本页按 arXiv v4 阅读。
本页是原创学习导读,不含原文复刻;论文版权与完整内容以原始来源为准。
学完后你应该能做到什么
读完本篇,你应该能够:
- 给 Actor、Evaluator 与 Self-Reflection 分别写出输入和输出,而不是把它们统称为“模型自我反思”。
- 画出一次尝试的短期轨迹、跨尝试的反思记忆以及下一次策略之间的数据流。
- 解释为什么 Reflexion 没有更新模型权重,以及上下文变化怎样改变下一轮行为。
- 区分环境二值/标量反馈、任务启发式、模型评价和自生成测试的可靠性边界。
- 用一个失败案例写出“已证实事实—失败原因推断—下一步可执行改动”三段式反思。
- 为重试次数、反思记忆容量、成功条件与升级人工条件设计明确规则。
先把问题说清楚:重跑不等于学习
假设一个编码 Agent 第一次写出的函数没有通过测试。最简单的重试方式是把同一个任务再交给它一次;但如果第二次看见的上下文与第一次相同,它很可能重复相同假设。另一种方式是把整段日志原样塞回去,可冗长轨迹里真正有用的失败信号又可能被淹没。
传统强化学习可以通过大量试验和梯度更新改变策略参数,但对大型语言模型来说代价很高。Reflexion 选择一条轻量路径:把奖励或失败信号翻译成具体语言经验,存入下一次尝试可见的情景记忆。
因此,Reflexion 的学习单位不是一组新权重,而是“这次发生了什么、为什么可能失败、下次应怎样改变”的文本记录。
一句话抓住它
Reflexion 让 Actor 完成一次轨迹,由 Evaluator 给出可检验结果,再让 Self-Reflection 把结果与轨迹压缩成可操作的语言经验;经验进入跨尝试记忆,下一次 Actor 在相同模型参数下依据新的上下文采取不同策略。
flowchart TD T["任务 + 当前反思记忆"] --> A["Actor:生成答案或动作轨迹 τ"] A --> E["环境 / Evaluator:得到 reward、pass/fail 或文字反馈"] E --> S{"已经通过?"} S -->|"是"| F["结束并交给外部验收"] S -->|"否"| R["Self-Reflection:轨迹 + 反馈 + 旧经验"] R --> M["写入长期情景记忆 mem"] M --> B{"达到最大尝试或升级条件?"} B -->|"否"| A B -->|"是"| H["停止并报告失败 / 请求人工判断"]
三个模型与两类记忆组成什么契约
Actor:负责执行,不负责宣布自己成功
Actor 接收任务、环境观察、当前尝试内的轨迹以及跨尝试反思记忆,输出文本、代码或动作。论文尝试过 Chain-of-Thought 和 ReAct 等 Actor 形式;Reflexion 并不限定 Actor 必须用一种固定提示模板。
Actor 与环境交互时形成轨迹:
这里的动作和观察属于当前尝试的短期记忆。任务重置后,原轨迹不一定完整带入下一次。
Evaluator:把结果压成可以触发学习的信号
Evaluator 读取 Actor 的结果或完整轨迹,输出 reward、成功/失败、分数或语言反馈。论文根据任务使用不同评价方式:
- 推理任务可以用 exact match 等确定规则;
- 交互任务可以用环境完成信号和预定义启发式;
- 某些设置让另一个语言模型进行分类或评价;
- 编程设置可以使用编译器、解释器、测试执行和自生成测试。
Evaluator 的输出决定 Agent 是否停止,也决定反思依赖什么证据。它不是一个可以随意省略的“打分提示”。没有可信评价,就没有可靠的失败方向。
Self-Reflection:把稀疏奖励放大成语义方向
Self-Reflection 接收任务、失败轨迹、Evaluator 信号和已有反思记忆,生成一段面向未来尝试的文字。例如,环境只给 0,反思器仍可从动作和观察中推断“我错误地假定物品在第一个抽屉;下一次应先搜索房间并在拿取成功后再继续”。
论文把这种文字称作类似 semantic gradient 的方向信号:它没有数学梯度,也不保证最优,但比一个标量更能指出下一次应改变哪一步。
短期轨迹与长期经验不能混为一谈
| 记忆 | 保存什么 | 生命周期 | 作用 |
|---|---|---|---|
| 短期记忆 | 当前尝试的细粒度动作与观察轨迹 | 一次 trial 内 | 定位本次具体错误 |
| 长期情景记忆 | Self-Reflection 生成的压缩经验 | 跨 trial | 让下一次策略看到过去教训 |
论文实践中把长期记忆限制为很小的经验窗口,通常只有 1~3 条;ALFWorld 设置保留最近 3 次反思。这个限制既适应当时上下文窗口,也避免旧反思无限堆积。生产系统仍需自己设计去重、过期、冲突和任务隔离。
为什么它不是传统的权重更新
设 Actor 使用的语言模型参数为 ,跨尝试记忆为 mem。Reflexion 改变的是 mem,而不是对 做反向传播。下一次输出变化来自条件上下文变化:
同一任务 + 同一模型参数 + 空记忆
→ 第一次策略
同一任务 + 同一模型参数 + “上次失败原因与改法”
→ 条件化后的第二次策略这使它具有三个现实优势:不需要微调模型;语言反馈能承载具体动作建议;经验可以被人检查。但代价同样清楚:上下文移除后改进不会固化在权重里,错误反思也会直接污染下一次决策,而且没有收敛保证。
“verbal reinforcement”应怎样理解
它借用了强化学习的 trial、reward、policy improvement 语言,但实现的是基于上下文与情景记忆的策略修正。不要把论文结果写成“模型已经通过强化学习训练了新策略参数”。
完整控制流:一次 trial 到下一次 trial
- 初始化 Actor、Evaluator、Self-Reflection 和空/已有
mem。 - Actor 根据任务与
mem生成轨迹 。 - Evaluator 对轨迹或结果计算 ,并决定是否通过。
- 如果已通过,停止重试,由真实任务验收输出。
- 如果未通过,把 、 与
mem交给 Self-Reflection。 - 生成反思 并写入
mem;超过容量时按策略压缩或淘汰。 - 如果仍有尝试预算,重置任务环境并开始下一次 trial。
- 达到最大尝试、重复失败或风险升级条件时停止,不能无限自我循环。
def reflexion_loop(task, actor, evaluator, reflector, max_trials=3):
memory = []
for trial in range(max_trials):
trajectory = actor.run(task, lessons=memory)
evaluation = evaluator.check(task, trajectory)
if evaluation.passed:
return {
"status": "passed",
"trajectory": trajectory,
"evidence": evaluation.evidence,
}
lesson = reflector.summarize(
task=task,
trajectory=trajectory,
feedback=evaluation,
previous_lessons=memory,
)
memory = keep_recent_nonduplicate(memory + [lesson], limit=3)
if evaluation.requires_human_judgment:
break
return {"status": "not_passed", "lessons": memory}注意循环条件:成功或耗尽预算,任一发生都应结束。论文伪代码表达的是围绕“未通过且仍有尝试”的迭代思想;工程实现要把布尔条件写清并测试边界,不能因为一段伪代码排版就复制出无限循环。
反馈来源决定了反思上限
外部环境或测试:最接近任务事实
例如目标物是否真的放到指定位置、单元测试是否通过、API 是否返回预期状态。这些信号可能稀疏,却通常比模型自评更可靠。反思器的任务是解释它,不是改写它。
预定义启发式:能发现已知坏模式
论文在 ALFWorld 中用规则检测连续重复相同动作和观察,或动作数过多等低效轨迹,再触发反思。启发式便宜、可重复,但只能覆盖编写者预见的失败。
LLM 评价:覆盖广,但可能与 Actor 一起误判
另一个模型可以输出分类或文字批评,适合难以写成规则的语义任务;可它可能被同一种错误假设影响,也可能更偏爱措辞而不是真实结果。
自生成测试:提供反馈,也可能验证错规格
编程 Agent 可以先生成测试再执行候选代码。这样无需看到隐藏测试即可获得运行反馈,但如果它误解题意,测试和实现可能“共同犯错”。最终仍需独立隐藏测试或维护者验收。
| 反馈 | 优点 | 主要风险 | 合适定位 |
|---|---|---|---|
| 真实环境完成信号 | 贴近任务结果 | 信息可能只有 0/1 | 成功条件主依据 |
| 单元/集成测试 | 可重复、可定位 | 覆盖不完整 | 编程任务主要证据 |
| 启发式 | 快、稳定 | 漏掉未知模式 | 触发诊断与早停 |
| LLM Evaluator | 能处理语义 | 自信误判、偏好漂移 | 辅助评价,需校准 |
| 自生成测试 | 能主动寻找反例 | 测试与代码同源偏差 | 探索反馈,不是最终真值 |
走完一次:修复括号匹配函数
下面是教学用的简化编程案例,不是论文原始 HumanEval 样本。
任务要求 balanced(s) 判断圆括号是否正确嵌套。Actor 第一次只比较左右括号总数:
def balanced(s):
return s.count("(") == s.count(")")Trial 1:轨迹和评价
- Actor 的假设:数量相等即可。
- 自生成测试:
"()"、"(())",都通过。 - 独立 Evaluator 测试:
")( "(忽略空格后)失败,因为右括号先出现。 - reward:
0,并返回失败输入和断言。
Self-Reflection:把事实、推断、改动分开
已证实事实:输入 ")(" 的左右括号数量相同,但期望 False,我的函数返回 True。
失败原因推断:我只检查总数,没有检查扫描过程中的前缀余额;右括号不能先于可匹配的左括号。
下一次改动:从左到右维护 balance;遇 '(' 加一、遇 ')' 减一;balance 任何时刻小于 0 都失败,结尾必须等于 0。这段文本进入长期情景记忆,而不是把整份执行日志无差别复制过去。
Trial 2:Actor 条件化于经验
def balanced(s):
balance = 0
for char in s:
if char == "(":
balance += 1
elif char == ")":
balance -= 1
if balance < 0:
return False
return balance == 0Evaluator 重新运行目标与回归测试;只有全部通过才停止。关键变化不是“模型想得更久”,而是第二次上下文里有一条针对首个失败的可执行约束。
决策任务中怎样使用轨迹反馈
在 ALFWorld 一类文本环境中,Actor 可以采用 ReAct 生成动作。环境只在任务完成时给明确成功;中间失败常表现为“动作重复、观察不变”或“Agent 以为自己持有某物,但环境状态并非如此”。
论文设置中的启发式会在相同动作—观察循环超过一定次数,或当前轨迹动作过多时触发反思。Reflexion 随后:
- 根据完整轨迹定位最早的错误假设;
- 写下下次要先验证的环境状态或替代动作;
- 保留最近少量经验;
- 重置环境并从新 trial 开始。
这里要区分 trial 内回退 和 trial 间学习。Reflexion 论文的核心是后者:当前 episode 结束后,压缩经验影响下一次从头尝试。
证据该怎么看
论文在三类任务上测试框架:ALFWorld 顺序决策、HotPotQA 语言推理,以及 HumanEval、MBPP、LeetCodeHardGym 等编程任务;并比较反馈信号、反馈纳入方式和 Actor 类型。
arXiv v4 报告的历史设置中,Reflexion 在 HumanEval 达到 91% pass@1,并相对当时论文引用的 GPT-4 80% 结果更高;论文也报告 ALFWorld 和 HotPotQA 等任务的提升。它们支持“在这些模型、提示、反馈和迭代预算下,语言反思记忆能改善后续尝试”。
证据边界
- 这些是 2023 年论文配置的结果,不是当前模型排行榜。
- 不同任务使用不同 Evaluator、启发式和工具,不能把一个数字当作框架在所有场景的固定增益。
- 多次试验会消耗额外推理和执行预算;与单次生成比较时要同时报告尝试次数和总成本。
- 自生成测试或 LLM 评价并非独立真值,隐藏测试的存在也不代表覆盖所有规格。
- 反思文字可读并不等于它对内部因果做出了真实解释;它是下一次可用的策略提示。
反思为什么会失败
| 失败模式 | 示例 | 后果 | 改进方向 |
|---|---|---|---|
| Evaluator 误判 | 正确答案被模型评论为错误 | 正确策略被抛弃 | 优先确定性外部证据,校准 evaluator |
| 信用分配错误 | 把最后一步当根因,实际第一步就拿错物品 | 下次只修改症状 | 回放状态转移,寻找最早分叉点 |
| 建议不可执行 | “下次更仔细” | Actor 没有可改变动作 | 指向工具、检查或具体决策 |
| 事实与推断混写 | “测试证明算法思路错误”但只失败一个边界 | 错误结论进入记忆 | 三段式标注事实、推断、建议 |
| 记忆冲突 | 一条说使用缓存,另一条说禁止缓存 | 上下文不稳定 | 去重、版本、作用域和冲突消解 |
| 记忆过长 | 保留全部轨迹与十几条反思 | 有用经验被稀释 | 小容量、摘要、按任务检索 |
| 无限重试 | 每次换一种表面写法 | 成本失控 | 最大 trial、重复检测、人工升级 |
| 成功信号可投机 | 删除测试或绕过检查 | 假成功 | 隔离评测、保留回归测试与审计 |
历史贡献与今天的工程解释
原典的历史贡献是把 Actor、Evaluator、Self-Reflection 与跨 trial 的语言情景记忆明确组成一个模块化循环,展示不微调模型也能利用试错反馈改善后续行为。
今天仍值得复用的工程思想包括:
- 每次失败都形成小而可执行的经验,而不是盲目重跑或重放全部日志;
- 让外部验收负责“是否成功”,让反思负责“下一次改什么”;
- 把环境事实、原因推断和行动建议分层记录;
- 为记忆容量、重试次数、重复失败和人工升级设置硬边界;
- 记录每条经验来自哪个任务版本、测试和轨迹,避免跨任务污染。
不应直接推出:模型自评天然可靠;写出一段有说服力的反思就找到了真实根因;Reflexion 已把能力永久训练进权重;或增加无限重试一定能解决任务。
与相邻原典的区别
Generative Agents 的反思把多条生活经历合成为人物和关系认识;Reflexion 的反思更直接服务于下一次任务尝试;Voyager 则把通过环境验证的成功行为保存成可检索、可执行技能。
自测:确认你真的掌握了机制
- Actor、Evaluator、Self-Reflection 各自不应该负责什么?
- 为什么一个
reward=0不能直接告诉 Actor 下一步怎样改?Reflexion 增加了哪一步? - “编译器报第 18 行类型错误,所以算法思想错误,应全部重写”中,哪些是事实,哪些是推断或建议?
- 如果三个 trial 都生成同一种失败,系统应继续重试、修改反思,还是升级人工?需要哪些证据决定?
- 为什么通过自生成测试后仍不能宣布最终成功?
参考答案与判断要点
- Actor 不应自行宣布成功;Evaluator 不应在没有依据时编造根因;Self-Reflection 不应改写环境事实。三者分别负责执行、评价和生成下一次经验。
- 标量只说明结果差,没有完成信用分配。Self-Reflection 结合轨迹与反馈,生成指向具体动作或检查的语言方向并写入记忆。
- “编译器在第 18 行报告类型错误”是可核验事实;“算法思想错误”是未经充分支持的原因推断;“全部重写”是行动建议。
- 先确认评价信号可靠、三次是否真的同根因、反思是否进入 Actor 上下文。重复轨迹通常应触发去重和升级,而不是无限烧预算;若根因明确且有新动作才值得再试。
- 测试与实现可能共享同一误解,且自生成测试覆盖有限。最终需要独立测试、真实环境或维护者定义的验收合同。
上一篇:Generative Agents · 下一篇:Voyager · 返回 里程碑原典目录