Reflexion:言语强化学习 本节摘要:基于梯度的强化学习要修一个失败模式,需要上千次试验和一整片 GPU 集群。Reflexion(Shinn 等人,NeurIPS 2023)用自然语言做到了这件事:每次失败的尝试之后,Agent 写一段反思,存进情节记忆(episodic memory),再用这段记忆条件化下一次尝试。没有权重更新,没有梯度,只是把「我从失败中学到了什么」用自然语言存在两次运行之间。这是 Letta 睡眠期计算、Claude Code 的 CLAUDE.md 学习、pro-workflow 的 learn-rule 背后的共同模式。
本节摘要:基于梯度的强化学习要修一个失败模式,需要上千次试验和一整片 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)。
阅读完本节,你应当能够:
一个 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」)。反思进情节记忆。下一次尝试从头开始,但会看到这段反思。
2026 年的默认做法是混合:有标量就用标量,没有就用自评,启发式作为安全护栏。
💡 评估者的选择直接决定 Reflexion 有没有用。一个误报率高的评估者会让反思变成无的放矢;一个总是报成功的评估者会让 Agent 永远不反思。先想清楚「我怎么知道这次失败了」,再写反思者。
Reflexion 与其说是一个新算法,不如说是一个被命名的模式。几乎每一个生产级「自我修复」Agent 都跑着它的某种变体:
CLAUDE.md /「保存记忆」模式:反思被捕获为学习项,前置到未来的会话。/learn-rule 命令:把纠正捕获为显式规则。它们都源自同一个洞察:自然语言是一种足够丰富的媒介,能在两次运行之间承载「我从失败中学到了什么」。
Reflexion 在以下情况有用:
Reflexion 在以下情况帮不上忙:
⚠️ 2026 年陷阱:记忆腐烂(memory rot)。反思会累积,有些过时了、有些根本就是错的;随着情节缓冲增长,重跑越来越慢。缓解办法:周期性压缩(原课程第 06 节)、给反思设 TTL(存活时间),或单独跑一个睡眠期清理 Agent(Letta)。
原课程 code/main.py 在一个玩具谜题上实现 Reflexion:生成一个加起来等于目标值的三元素列表。行动者吐出候选列表,评估者检查求和,自我反思者写一行诊断。反思进情节记忆,供下次尝试使用。
核心组件如下,用伪代码展示骨架。
一个脚本策略,看到反思时会改进:
def actor(task, episodic_memory): prompt = task + "\n过往反思:\n" + "\n".join(episodic_memory) return llm.generate(prompt) # 真实用 LLM;demo 里是脚本
对目标和值做通过/不通过判断:
def evaluator_binary(candidate, target): return abs(sum(candidate) - target) == 0
针对失败产出一条诊断:
def self_reflector(task, candidate, target): diff = sum(candidate) - target return f"上次差了 {diff},这次往反方向调。"
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]
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 调用即可投入生产。
下一节,我们将进入「思维树与 LATS:审慎搜索」——把单条线性的思维链变成一棵可回溯的树,再用蒙特卡洛树搜索把 ToT、ReAct、Reflexion 统一进一个框架。