Reflexion:言语强化学习


文档摘要

Reflexion:言语强化学习 本节摘要:基于梯度的强化学习要修一个失败模式,需要上千次试验和一整片 GPU 集群。Reflexion(Shinn 等人,NeurIPS 2023)用自然语言做到了这件事:每次失败的尝试之后,Agent 写一段反思,存进情节记忆(episodic memory),再用这段记忆条件化下一次尝试。没有权重更新,没有梯度,只是把「我从失败中学到了什么」用自然语言存在两次运行之间。这是 Letta 睡眠期计算、Claude Code 的 CLAUDE.md 学习、pro-workflow 的 learn-rule 背后的共同模式。

Reflexion:言语强化学习

本节摘要:基于梯度的强化学习要修一个失败模式,需要上千次试验和一整片 GPU 集群。Reflexion(Shinn 等人,NeurIPS 2023)用自然语言做到了这件事:每次失败的尝试之后,Agent 写一段反思,存进情节记忆(episodic memory),再用这段记忆条件化下一次尝试。没有权重更新,没有梯度,只是把「我从失败中学到了什么」用自然语言存在两次运行之间。这是 Letta 睡眠期计算、Claude Code 的 CLAUDE.md 学习、pro-workflow 的 learn-rule 背后的共同模式。本节讲透这个模式:三个组件(行动者、评估者、自我反思者)、三种评估者类型、为什么它能在 ALFWorld 上把成功率拉到远超 ReAct,以及它的失效边界——什么时候反思只会变成迷信。

对应原课程:Phase 14 · Lesson 03 · reflexion-verbal-rl(原英文 phases/14-agent-engineering/03-reflexion-verbal-rl/docs/en.md)。

学习目标

阅读完本节,你应当能够:

  1. 说出 Reflexion 的三个组件(行动者 Actor、评估者 Evaluator、自我反思者 Self-Reflector),以及情节记忆的角色。
  2. 用标准库实现一个 Reflexion 循环:二值评估器、反思缓冲区、全新的重新尝试。
  3. 针对一个给定任务,在「标量、启发式、自评」三种反馈来源之间做出选择。
  4. 解释为什么言语强化能抓住那些梯度 RL 要上千次试验才能修的错误。

一、问题与直觉

一个 Agent 把任务搞砸了。在标准 RL 里,你会再跑上千次试验、算梯度、更新权重。昂贵、缓慢,而且大多数生产 Agent 没有为每一次失败都准备一份训练预算

Reflexion(Shinn 等人,arXiv:2303.11366)换了个问题:要是 Agent 只是想想自己为什么失败,然后带着这个想法再试一次呢? 没有权重更新,没有梯度,只是把一段自然语言存在两次尝试之间。

结果是:在 ALFWorld 上它击败了 ReAct 和其他非微调基线;在 HotpotQA 上它优于 ReAct;在代码生成(HumanEval/MBPP)上它当时刷新了 SOTA。而这一切,没有一个梯度步

三个组件

行动者 Actor : 生成一条轨迹(ReAct 风格循环) 评估者 Evaluator : 给轨迹打分——二值、启发式、或自评 自我反思者 Self-Reflector : 写一段关于失败的自然语言反思

外加一个数据结构:

情节记忆 Episodic Memory : 过往反思的列表,前置到下一次尝试的提示里

一次尝试跑行动者,评估者打分。如果分数低,自我反思者产出反思(比如「我选错了工具,因为我把问题里问的 X 看成了 Y」)。反思进情节记忆。下一次尝试从头开始,但会看到这段反思。

三种评估者

  1. 标量(Scalar) —— 一个外部二值信号。ALFWorld 成功或失败,HumanEval 测试通过或不通过。最简单,信号最强。
  2. 启发式(Heuristic) —— 预定义的失败签名。「如果 Agent 连续两次产出相同行动,标记为卡住。」「如果轨迹超过 50 步,标记为低效。」
  3. 自评(Self-evaluated) —— LLM 给自己的轨迹打分。在没有真值时需要。信号较弱;与工具锚定的验证(第 05 节 CRITIC)搭配效果好。

2026 年的默认做法是混合:有标量就用标量,没有就用自评,启发式作为安全护栏。

💡 评估者的选择直接决定 Reflexion 有没有用。一个误报率高的评估者会让反思变成无的放矢;一个总是报成功的评估者会让 Agent 永远不反思。先想清楚「我怎么知道这次失败了」,再写反思者。

为什么这个模式能泛化

Reflexion 与其说是一个新算法,不如说是一个被命名的模式。几乎每一个生产级「自我修复」Agent 都跑着它的某种变体:

  • Letta 的睡眠期计算(原课程第 08 节):一个独立的 Agent 反思过往对话,写进记忆块。
  • Claude Code 的 CLAUDE.md /「保存记忆」模式:反思被捕获为学习项,前置到未来的会话。
  • pro-workflow 的 /learn-rule 命令:把纠正捕获为显式规则。
  • LangGraph 的反思节点:一个节点给输出打分,需要时路由到精炼。

它们都源自同一个洞察:自然语言是一种足够丰富的媒介,能在两次运行之间承载「我从失败中学到了什么」

什么时候有用,什么时候没用

Reflexion 在以下情况有用:

  • 有一个清晰的失败信号(测试失败、工具报错、答案错误)。
  • 任务类别可复现(同类问题可以再问一次)。
  • 反思有改进空间(还有足够的行动预算)。

Reflexion 在以下情况帮不上忙:

  • Agent 第一次就成功了。
  • 失败是外部的(网络断了、工具坏了)——反思「网络刚才断了」对未来运行没帮助。
  • 反思退化为迷信——存下了一段关于某次偶然抖动的叙事。

⚠️ 2026 年陷阱:记忆腐烂(memory rot)。反思会累积,有些过时了、有些根本就是错的;随着情节缓冲增长,重跑越来越慢。缓解办法:周期性压缩(原课程第 06 节)、给反思设 TTL(存活时间),或单独跑一个睡眠期清理 Agent(Letta)。

二、从零实现

原课程 code/main.py 在一个玩具谜题上实现 Reflexion:生成一个加起来等于目标值的三元素列表。行动者吐出候选列表,评估者检查求和,自我反思者写一行诊断。反思进情节记忆,供下次尝试使用。

核心组件如下,用伪代码展示骨架。

Step 1:行动者

一个脚本策略,看到反思时会改进:

def actor(task, episodic_memory): prompt = task + "\n过往反思:\n" + "\n".join(episodic_memory) return llm.generate(prompt) # 真实用 LLM;demo 里是脚本

Step 2:二值评估者

对目标和值做通过/不通过判断:

def evaluator_binary(candidate, target): return abs(sum(candidate) - target) == 0

Step 3:自我反思者

针对失败产出一条诊断:

def self_reflector(task, candidate, target): diff = sum(candidate) - target return f"上次差了 {diff},这次往反方向调。"

Step 4:情节记忆(带 TTL 的有界列表)

class EpisodicMemory: def __init__(self, ttl=10): self.entries = [] self.ttl = ttl def add(self, reflection): self.entries.append({"text": reflection, "age": 0}) def tick(self): # 每次新尝试后调用 for e in self.entries: e["age"] += 1 self.entries = [e for e in self.entries if e["age"] < self.ttl]

Step 5:Reflexion 主循环

def reflexion(task, target, max_trials=5): memory = EpisodicMemory() for trial in range(max_trials): candidate = actor(task, [e["text"] for e in memory.entries]) if evaluator_binary(candidate, target): return candidate, trial reflection = self_reflector(task, candidate, target) memory.add(reflection) memory.tick() return None, max_trials # 预算耗尽

运行 python3 code/main.py 会打印三次尝试:尝试 1 失败、存反思、尝试 2 看到反思后改进但还失败、尝试 3 成功。和一次基线运行(不带反思)对比——它会一直卡在尝试 1 的答案上。

💡 设计要点:每次尝试都从头开始——反思不是改权重,而是改提示。这就是「言语强化」的字面含义:强化信号是语言,作用点是上下文,不是参数。

三、框架对比

框架 对 Reflexion 的支持形态
LangGraph 把反思作为一种节点模式提供:一个节点给输出打分,需要时路由到精炼节点
Claude Code /memory 命令把情节缓冲外化成一个 markdown 文件,跨会话持久
pro-workflow /learn-rule 把纠正捕获为显式规则,等价于带结构的反思
Letta 睡眠期计算:在空闲时间跑自我反思者,主 Agent 保持低延迟
OpenAI Agents SDK 没有直接内置 Reflexion;可用自定义 Guardrail(按分拒绝轨迹)+ 跨运行存活的 Session 记忆拼出这个模式

注意 OpenAI Agents SDK 是「拼装」而非「开箱即用」:你需要自己把反思写入 Session,并在下一轮把它读回提示。这反而说明 Reflexion 是一个模式,不是一个产品特性——理解了原理,在任何框架里都能复现。

四、可复用产物

本节产出一份可复用技能(原课程 outputs/skill-reflexion-buffer.md):

  • skill-reflexion-buffer.md:创建并维护一个情节缓冲,带反思捕获、TTL、去重。给定一个任务类别和一次失败,它会产出一条对下一次尝试真正有用的反思(而不是泛泛的「下次仔细点」)。它包含一份反思质量的自检清单:是否指向具体步骤?是否可操作?是否会过期?

Python 代码(code/main.py)是独立可运行的玩具,行动者/评估者/反思者/记忆四件套与 TTL 逻辑都是厂商无关的;把脚本式反思者换成真实 LLM 调用即可投入生产。

五、练习

  1. (Easy) 把二值评估者换成返回距离度量的标量评估者(离目标多远)。收敛会更快吗?
  2. (Easy) 给反思加一个 10 次尝试的 TTL。过了那个点之后,旧反思是帮还是害?
  3. (Medium) 实现启发式评估者:如果同一行动重复出现,就把这次尝试标记为卡住。它和自我反思者怎么配合?
  4. (Medium) 用一个故意忽略反思的对抗性行动者跑 Reflexion。迫使行动者注意到反思,最低限度的反思提示工程是什么?
  5. (Hard) 读 Reflexion 论文第 4 节关于 ALFWorld 的内容。从概念上复现那 130% 的成功率提升:相比朴素 ReAct,关键的差异是什么?

本节要点回顾

  1. Reflexion = 行动者 + 评估者 + 自我反思者 + 情节记忆:零梯度,强化信号是自然语言,作用点是上下文而非参数。
  2. 每次尝试从头开始,但看到反思:反思前置到下一次提示,不修改权重。
  3. 三种评估者:标量(外部二值,最强)、启发式(预定义失败签名)、自评(无真值时兜底,信号较弱)。
  4. 2026 年默认是混合:有标量用标量,没有用自评,启发式做安全护栏。
  5. 它是一个被命名的模式:Letta 睡眠期计算、Claude Code 的 CLAUDE.md、pro-workflow 的 learn-rule、LangGraph 反思节点都是变体。
  6. 自然语言是足够丰富的媒介:能在两次运行之间承载「我从失败中学到了什么」。
  7. 有用条件:有清晰失败信号、任务可复现、还有改进预算。
  8. 失效条件:第一次就成功、失败是外部的(网络/工具)、反思退化为迷信。
  9. 2026 年陷阱是记忆腐烂:反思累积会过时/出错/拖慢重跑,靠周期性压缩、TTL、睡眠期清理 Agent 缓解。
  10. 反思质量决定一切:好的反思指向具体步骤、可操作、会过期;坏的反思是「下次仔细点」。

下一节,我们将进入「思维树与 LATS:审慎搜索」——把单条线性的思维链变成一棵可回溯的树,再用蒙特卡洛树搜索把 ToT、ReAct、Reflexion 统一进一个框架。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U