Reflexion:用语言反馈让 Agent 从失败中学习

原始来源

Noah Shinn 等,2023:Reflexion: Language Agents with Verbal Reinforcement Learning,本页按 arXiv v4 阅读。

本页是原创学习导读,不含原文复刻;论文版权与完整内容以原始来源为准。

学完后你应该能做到什么

读完本篇,你应该能够:

  1. 给 Actor、Evaluator 与 Self-Reflection 分别写出输入和输出,而不是把它们统称为“模型自我反思”。
  2. 画出一次尝试的短期轨迹、跨尝试的反思记忆以及下一次策略之间的数据流。
  3. 解释为什么 Reflexion 没有更新模型权重,以及上下文变化怎样改变下一轮行为。
  4. 区分环境二值/标量反馈、任务启发式、模型评价和自生成测试的可靠性边界。
  5. 用一个失败案例写出“已证实事实—失败原因推断—下一步可执行改动”三段式反思。
  6. 为重试次数、反思记忆容量、成功条件与升级人工条件设计明确规则。

先把问题说清楚:重跑不等于学习

假设一个编码 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

  1. 初始化 Actor、Evaluator、Self-Reflection 和空/已有 mem。
  2. Actor 根据任务与 mem 生成轨迹 。
  3. Evaluator 对轨迹或结果计算 ,并决定是否通过。
  4. 如果已通过,停止重试,由真实任务验收输出。
  5. 如果未通过,把 、 与 mem 交给 Self-Reflection。
  6. 生成反思 并写入 mem;超过容量时按策略压缩或淘汰。
  7. 如果仍有尝试预算,重置任务环境并开始下一次 trial。
  8. 达到最大尝试、重复失败或风险升级条件时停止,不能无限自我循环。
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 == 0

Evaluator 重新运行目标与回归测试;只有全部通过才停止。关键变化不是“模型想得更久”,而是第二次上下文里有一条针对首个失败的可执行约束。

决策任务中怎样使用轨迹反馈

在 ALFWorld 一类文本环境中,Actor 可以采用 ReAct 生成动作。环境只在任务完成时给明确成功;中间失败常表现为“动作重复、观察不变”或“Agent 以为自己持有某物,但环境状态并非如此”。

论文设置中的启发式会在相同动作—观察循环超过一定次数,或当前轨迹动作过多时触发反思。Reflexion 随后:

  1. 根据完整轨迹定位最早的错误假设;
  2. 写下下次要先验证的环境状态或替代动作;
  3. 保留最近少量经验;
  4. 重置环境并从新 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 则把通过环境验证的成功行为保存成可检索、可执行技能。

自测:确认你真的掌握了机制

  1. Actor、Evaluator、Self-Reflection 各自不应该负责什么?
  2. 为什么一个 reward=0 不能直接告诉 Actor 下一步怎样改?Reflexion 增加了哪一步?
  3. “编译器报第 18 行类型错误,所以算法思想错误,应全部重写”中,哪些是事实,哪些是推断或建议?
  4. 如果三个 trial 都生成同一种失败,系统应继续重试、修改反思,还是升级人工?需要哪些证据决定?
  5. 为什么通过自生成测试后仍不能宣布最终成功?

上一篇:Generative Agents · 下一篇:Voyager · 返回 里程碑原典目录