Skip to content

Reflection ​

标签
AI/agent/范式
字数
3880 字
阅读时间
16 分钟

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)——它明确指出了「下降」的方向。

代价对比一目了然:

传统 RLReflexion
反馈形态一个数值(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                             │
│                                                               │
└───────────────────────────────────────────────────────────────┘
循环持续到任务成功或达到最大迭代次数。

循环持续到任务成功或达到最大迭代次数。

形式化(O0 为初始输出,Oi 为第 i 次迭代的输出):

Fi=πreflect(Task,Oi)Oi+1=πrefine(Task,Oi,Fi)

记忆分两层 ​

层存什么作用
短期记忆当前这次任务的完整轨迹(每步的思考、行动、观察)提供细粒度的、正在发生的事实
长期记忆历史反思文本库提供跨任务的「教训」

为了不让上下文窗口爆炸,长期记忆只保留最近的 k 条反思(该文取 k=3)。 这跟人一样——记住最重要的教训,淡忘久远的细节。

下一次行动时,Actor 的提示词里同时包含三样:任务描述 + 长期记忆(过去几条反思)+ 当前尝试的轨迹(短期记忆)。

这个机制在工程实现上就是一个显式的 Memory 模块:

python
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-only97%(134 个环境 12 次尝试内解决 130 个)+22 个百分点
HotPotQAReAct 32%53%+21
HotPotQA(给定标准答案上下文)61%75%+14

三个值得注意的地方:

  1. HumanEval 91% 超过同期的 GPT-4(80.1%)——而 Reflexion 自己不改任何参数,只是给 GPT-4 加了一层反思循环。
  2. HotPotQA 的第二个数字更说明问题:把标准答案的上下文直接给它、只考纯推理,成功率仍能从 61% 提到 75%。信息都在手上,Agent 依然会因为错误的推理路径得出错误结论——反思修的是这一层。
  3. 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 轮反思认可现有复杂度,另提分段筛法、奇数筛法结论:「一般情况下无需改进」
终止反思无新问题收敛

三个观察:

  1. 有效的批判是优化的前提。 第一轮反思之所以有价值,是因为提示词被设定成「极其严格」且「专注算法效率」——否则模型会满足于「功能正确的初版」。
  2. O(n·√n) → O(n log log n) 是算法层面的跃迁,不是代码风格微调。Reflection 能驱动的正是这种跃迁。
  3. 收敛判断和优化同等重要。 第二轮反思在提到更高级的优化方向之后主动判定「无需改进」,这个判断触发了终止条件。没有它,迭代会一直烧钱。

成本收益:典型的以成本换质量 ​

三项成本:

成本量级
模型调用开销每轮迭代至少多调两次 LLM(一次反思、一次优化),多轮则成倍增加
任务延迟串行过程,每轮优化必须等上一轮反思完成,实时场景不可接受
提示工程复杂度执行、反思、优化三套提示词都要单独设计调试

两项收益:

  1. 解决方案质量的跃迁——从「合格」到「优秀」,从功能正确到性能更优
  2. 鲁棒性提升——内部纠错能发现并修复逻辑漏洞、事实性错误、边界情况处理不当

适用判据:对结果质量、准确性、可靠性要求极高,且对实时性要求宽松。典型场景是生成关键业务代码或技术报告、科研中的复杂逻辑推演、需要深度分析与规划的决策支持系统。

反过来,需要快速响应、或「大致正确」就够的场景,用 ReAct 或 Plan-and-Solve 性价比更高。

三条局限 ​

一条边界(后续分析也反复提到):

  1. 效果上限取决于反思者的能力。 如果 LLM 本身「固执」或推理能力不足,它生成的反思可能无效甚至误导——用同一把尺子量同一块布,很难量出自己的盲区。这也是 LLM Evaluation 与反馈闭环 里「同源盲区」要单独处理的原因。
  2. 可能陷入局部最优。 Agent 反思出一种「还行」的策略后就一直在这条路上小修小补,跳不出去探索全局更优解。在 WebShop 上就没有显著提升——那个任务需要更多样化、更有创造性的探索行为,而不是对既有策略的微调。
  3. 没有收敛保证。

这三条合起来给出一条判据:Reflection 擅长「把一个方向上的解做好」,不擅长「换一个方向」。

相关 ​

参考 ​

贡献者 ​

文件历史 ​