Reflexion:让 Agent 用「言语」做强化学习

一、为什么需要 Reflexion?

传统强化学习的修复路径是:

text 复制代码
失败 → 算梯度 → 更新权重 → 上千次试验 → 收敛

生产 Agent 不可能为每次失败都备一份训练预算 。Shinn 等人在论文 arXiv:2303.11366 里换了个问法:

如果 Agent 只是想一想自己为什么失败 ,然后带着这段思考再试一次,会怎么样?

  • 不动权重
  • 不算梯度
  • 只在试验之间存一段自然语言

结果是:

  • ALFWorld:打过 ReAct 等所有未微调基线(论文里有 ~130% 提升的说法);
  • HotpotQA:稳定优于 ReAct;
  • HumanEval / MBPP(代码生成):当时的 SOTA。

全程零梯度。


二、Reflexion 的三件套 + 一个数据结构

text 复制代码
Actor          : 跑一次 trajectory(典型 ReAct 循环)
Evaluator      : 给 trajectory 打分(二值 / 启发式 / 自评)
Self-Reflector : 写一段自然语言反思 ------「我为什么失败」
─────────────────────────────────────────────
Episodic Memory: 反思的有界列表,下一次试验时前置到 prompt

一轮 Reflexion 长这样

text 复制代码
trial 1
  Actor 跑一次 → Evaluator 判定失败
  Self-Reflector:"我挑错了工具,因为把问题误读成 X,其实是 Y"
  记忆 += 这段反思

trial 2
  Actor 看到记忆里的反思 → 选了正确的工具 → Evaluator 判定成功

关键点 :每次试验都是从零开始的全新轨迹,但 prompt 里多了"上次为什么错"。这是它和 ReAct(同一条轨迹里递归思考)的本质差别。


三、三种 Evaluator,按信号强度排序

类型 信号来源 优势 风险
标量(Scalar) 外部 ground truth(测试通过 / 任务成功) 信号最强、最干净 不是所有任务都有 GT
启发式(Heuristic) 预定义失败特征(连续相同动作、超过 50 步 ...) 便宜、不依赖 LLM 漏报 / 误报
自评(Self-Eval) LLM 给自己的轨迹当裁判 任何场景都能用 信号最弱,容易自吹自擂

2026 年的默认混搭

有 ground truth 就用 scalar;没有就用 self-eval;启发式做安全护栏。

特别地:self-eval 最好和工具锚定验证一起用,单独用很容易飘。


四、为什么 Reflexion 几乎被所有"自愈"Agent 抄袭?

不是新算法,而是一个被命名的模式。生产里到处是它的变体:

产品 / 框架 它的"Reflexion"
Letta sleep-time compute ------ 独立 agent 反思过往对话,写进 memory block
Claude Code CLAUDE.md / "保存记忆" ------ 反思被捕获为 learnings,前置到未来会话
pro-workflow /learn-rule 命令 ------ 把纠正显式化成规则
LangGraph "反思节点" ------ 给输出打分,必要时路由去 refine

它们共享同一个洞见:

自然语言是个足够丰富的媒介,能在多次运行之间承载"我从失败里学到了什么"。


五、Reflexion 什么时候有用、什么时候帮倒忙

✅ 有效场景

  • 失败信号清晰:测试失败、工具报错、答案错误;
  • 任务可复现:同一类问题能被再问一遍;
  • 预算够:还允许多试几次。

❌ 帮倒忙场景

  • 第一次就成功 → 反思纯属浪费 token;
  • 失败是外部的(网络挂了、服务 500)→ 反思"网络挂了"毫无价值;
  • 反思变成迷信:一次偶发抖动,被存成"以后永远别走这条路"。

⚠️ 2026 年的最大坑:记忆腐烂(Memory Rot)

反思会越攒越多,里面总有过时或错误的条目。情景缓冲区一大,重试反而变慢。缓解办法:

  1. 周期性压实(compaction):把多条反思总结成一条;
  2. TTL(Time To Live):每条反思加过期时间;
  3. sleep-time 清理 agent:把"整理记忆"挪出热路径,主 agent 保持低延迟。

六、关键术语速查表

术语 通俗叫法 它实际是什么
Reflexion "自我纠正" Actor + Evaluator + Self-Reflector + 情景记忆
Verbal Reinforcement "无梯度学习" 反思前置进下次 prompt,等价于一种 in-context 学习
Episodic Memory "按任务反思" 某任务类别下既往反思的有界缓冲区
Scalar Evaluator "二值成功信号" GT 给出的 pass/fail 或数值分数
Heuristic Evaluator "模式探测器" 预定义的失败特征(卡住、步数过多)
Self-Evaluator "自己当裁判" LLM 给自己打分 ------ 弱信号兜底
Memory Rot "记忆腐烂" 缓冲区里过时条目越来越多
Sleep-time Reflection "异步反思" 反思挪出热路径,避免拖主 agent 延迟

代码精讲:约 120 行的玩具 Reflexion

任务定义:从 1...9 里挑 3 个整数,让它们的和等于 20。

Actor 被故意写"死板":

  • 没看到反思 → 永远挑 [1, 2, 3]
  • 看到第 1 条反思 → 改成 [5, 6, 7]
  • 看到第 2 条反思 → 改成 [6, 7, 7] → 命中。

这样我们能同一份代码跑两遍(开 / 关 memory),直接看出反思的价值。


1. 用 @dataclass 定义反思和情景记忆

python 复制代码
@dataclass
class Reflection:
    trial: int
    text: str


@dataclass
class EpisodicMemory:
    items: list[Reflection] = field(default_factory=list)
    max_len: int = 6

    def add(self, r: Reflection) -> None:
        self.items.append(r)
        if len(self.items) > self.max_len:
            self.items.pop(0)

    def as_prompt(self) -> str:
        if not self.items:
            return "(no prior reflections)"
        lines = [f"- trial {r.trial}: {r.text}" for r in self.items]
        return "\n".join(lines)

语法点

  • field(default_factory=list) :上一节讲过 ------ 可变默认值的正确写法,每个实例各自一个 list,不共享。
  • max_len: int = 6 :dataclass 字段可以带默认值;带默认的必须放在没默认的字段后面(和函数参数一样)。
  • self.items.pop(0) :删除并返回列表第一个元素 ,相当于一个"先进先出"队列。list.pop(0) 是 O(n) 的,如果性能敏感应换 collections.deque(maxlen=6),效果一样且 O(1)。
  • 列表推导式 [f"- trial {r.trial}: {r.text}" for r in self.items] :把每条反思格式化成一行,返回新 list
  • "\n".join(lines) :用换行把字符串列表拼起来。比 "+".join(...) 或循环 += 都更 Pythonic、更快。

设计意图

这就是最简的"有界 FIFO 反思缓冲区"

  • 超过 max_len 自动淘汰最早的(粗暴版的"记忆压实");
  • as_prompt() 把所有反思格式化进 prompt,供下次 Actor 看到。

这两点合起来就是 LangGraph / Claude Code memory / Letta sleep-time 的最小内核 ------ 真实系统只是把"淘汰策略"和"压实策略"做得更聪明。


2. Actor:脚本化的"死板策略"

python 复制代码
class Actor:
    """Scripted policy. Without reflections it stays on bad choices; with
    at least one reflection it moves toward the target sum."""

    def act(self, memory: EpisodicMemory) -> list[int]:
        n = len(memory.items)
        if n == 0:
            return [1, 2, 3]
        if n == 1:
            return [5, 6, 7]
        if n == 2:
            return [6, 7, 7]
        return [6, 7, 7]

设计意图

这里不调真 LLM ,而是用反思条数模拟"模型看到反思后改进行为"

反思条数 选择 sum 行为
0 1, 2, 3 6 远离目标
1 5, 6, 7 18 已接近
2 6, 7, 7 20 命中

把这个 act 替换成真 LLM 调用,循环本身一行不用改 ------ 和前两节的 ToyLLM / ScriptedPlanner 是一个套路。

语法点

  • len(memory.items):取列表长度。
  • 多个 if 而不用 elif :因为每个分支都 return,等价但更清晰。Python 里这是常见风格。
  • 类型注解 -> list[int]:明确告诉调用方"返回 int 的 list"。

3. Evaluator:返回二元组(成功 + 偏差)

python 复制代码
def binary_evaluator(attempt: list[int], target: int) -> tuple[bool, int]:
    total = sum(attempt)
    return total == target, total - target

语法点

  • tuple[bool, int]:返回一个二元组,类型注解明确告诉外面"第一个是 bool,第二个是 int"。

  • return total == target, total - target :Python 里多返回值的本质就是一个 tuple ,逗号自动打包成 tuple,调用方可以解包:

    python 复制代码
    success, delta = binary_evaluator([1,2,3], 20)

设计意图

虽然叫 binary_evaluator,但附带了一个连续的 delta(差多少)------ 给 Self-Reflector 提供更丰富的信号,能写出"少了 14"而不仅仅是"失败"。

这正是真实 Reflexion 的关键工程经验:

二值评估器虽然干净,但额外暴露一点点连续信号能让反思质量大幅提升。


4. Self-Reflector:把数字翻译成"建议"

python 复制代码
class SelfReflector:
    def reflect(self, attempt: list[int], delta: int) -> str:
        if delta < 0:
            return f"sum {sum(attempt)} is {-delta} short; pick larger values"
        if delta > 0:
            return f"sum {sum(attempt)} overshoots by {delta}; pick smaller values"
        return "succeeded"

语法点

  • -delta:取负数 ------ delta 是负的(不够),描述成"还差多少"更自然。
  • f"{sum(attempt)}":f-string 里可以直接嵌入函数调用。
  • 三个分支覆盖 少了 / 多了 / 刚好 三种情形。

设计意图

真实 Reflexion 里这里是一次 LLM 调用

text 复制代码
"你刚才做了 X,得到 Y,目标是 Z,写一段一句话反思,告诉下一次该怎么调整。"

反思的有效性高度依赖

  • 它必须给出可执行建议("pick larger values"),不能是空话("再小心点");
  • 它必须只关于这次失败,不能夹带不相关的全局教训。

工程里最常见的反模式:让 LLM 自由发挥写反思,结果第 10 条反思变成一段"agent 哲学独白" ------ 既没营养又占 token。让 reflector 输出结构化字段(如 root_cause + suggestion)比让它自由写要稳定得多。


5. 一次 Reflexion 跑通(核心循环)

python 复制代码
@dataclass
class TrialResult:
    trial: int
    attempt: list[int]
    success: bool
    delta: int
    reflection: str


def run_reflexion(max_trials: int, use_memory: bool) -> list[TrialResult]:
    actor = Actor()
    reflector = SelfReflector()
    memory = EpisodicMemory()
    trials: list[TrialResult] = []
    for t in range(1, max_trials + 1):
        attempt = actor.act(memory if use_memory else EpisodicMemory())
        success, delta = binary_evaluator(attempt, TARGET)
        text = reflector.reflect(attempt, delta)
        trials.append(TrialResult(t, attempt, success, delta, text))
        if success:
            break
        memory.add(Reflection(trial=t, text=text))
    return trials

设计意图(逐行精读)

在做什么 对应 Reflexion 哪一块
actor.act(memory if use_memory else EpisodicMemory()) 把"反思记忆"传进去;关掉时传记忆 控制变量对比
binary_evaluator(attempt, TARGET) 打分 Evaluator
reflector.reflect(attempt, delta) 写反思 Self-Reflector
trials.append(...) 记录这次试验完整结果 调试 / 可观测
if success: break 命中就退出 早停
memory.add(Reflection(...)) 失败才写记忆 情景记忆

语法点

  • memory if use_memory else EpisodicMemory()三元表达式 (条件表达式),等价于 use_memory ? memory : EpisodicMemory()。这是 Python 控制变量对照的常用写法。
  • range(1, max_trials + 1) :从 1 开始计数,而不是默认的 0;这是因为下游 trial 字段对人更友好。
  • for t in range(...) :经典的"轮数预算"守卫,思路和第 1 节 AgentLoop.max_turns 完全一样。
  • if success: break :典型早停模式。如果省掉这行,Reflexion 会一直跑到 max_trials,浪费 token ------ 这是新手常见错。

关键设计取舍

只有失败才写反思if success: breakmemory.add 之前)。

如果你成功也写反思("我成功了,因为 ......"),会变成自我表扬式 prompt 污染。Reflexion 的核心假设是"反思的价值在于纠错",不要破坏它。


6. 打印对比 + 主函数

python 复制代码
def summarize(trials: list[TrialResult], name: str) -> None:
    print(f"\n{name}")
    print("-" * 60)
    for r in trials:
        mark = "OK " if r.success else "..."
        print(f"  trial {r.trial}: {r.attempt} sum={sum(r.attempt)} "
              f"delta={r.delta:+d} {mark} -> {r.reflection}")
    last = trials[-1]
    print(f"  final: {'success' if last.success else 'failed'} "
          f"at trial {last.trial}")


def main() -> None:
    print("=" * 70)
    print(f"REFLEXION --- pick three ints in [1..9] summing to {TARGET}")
    print("=" * 70)

    trials_no_mem = run_reflexion(max_trials=4, use_memory=False)
    summarize(trials_no_mem, "BASELINE (no episodic memory)")

    trials_mem = run_reflexion(max_trials=4, use_memory=True)
    summarize(trials_mem, "REFLEXION (episodic memory on)")

语法点

  • {r.delta:+d} :f-string 格式化指令,+d 表示正数也加 + 号 ,便于阅读(例如 -14+0+3)。
  • 'success' if last.success else 'failed':再次出现的三元表达式,把 bool 翻译成可读字符串。
  • trials[-1] :负下标取最后一个元素 ,Python 特色,避免写 trials[len(trials)-1]

期望输出

text 复制代码
BASELINE (no episodic memory)
  trial 1: [1, 2, 3] sum=6  delta=-14 ... -> sum 6 is 14 short; pick larger values
  trial 2: [1, 2, 3] sum=6  delta=-14 ... -> sum 6 is 14 short; pick larger values
  trial 3: [1, 2, 3] sum=6  delta=-14 ... -> sum 6 is 14 short; pick larger values
  trial 4: [1, 2, 3] sum=6  delta=-14 ... -> ...
  final: failed at trial 4

REFLEXION (episodic memory on)
  trial 1: [1, 2, 3] sum=6  delta=-14 ... -> sum 6 is 14 short; pick larger values
  trial 2: [5, 6, 7] sum=18 delta=-2  ... -> sum 18 is 2 short; pick larger values
  trial 3: [6, 7, 7] sum=20 delta=+0  OK  -> succeeded
  final: success at trial 3

对比一目了然:没有反思的 Actor 卡死在初始策略;带反思 3 步收敛

这就是 Reflexion 的精髓,无关 RL,无关梯度,全凭一段自然语言


七、本节小结 & 易错点回顾

核心知识点

  1. Reflexion = Actor + Evaluator + Self-Reflector + 情景记忆
  2. 核心创新:用自然语言代替梯度做强化。
  3. 三种评估器:标量(首选)→ 自评(兜底)→ 启发式(护栏)。
  4. 失败时才写反思,避免自我表扬式污染。
  5. 生产里最大风险是记忆腐烂:必须配合压实 / TTL / sleep-time 清理。

Python 入门易错点

正确做法
dataclass 默认值用 [] / {} field(default_factory=list / dict)
长 FIFO 用 list.pop(0) 性能差 collections.deque(maxlen=N)
返回多个值忘了类型注解 -> tuple[bool, int]
解包时左右数量不匹配 success, delta = func() 必须严格对齐
三元表达式顺序写反 Python 是 X if cond else Y,不是 C 风格 cond ? X : Y
for x in [-1] 想取最后一个 直接 lst[-1]
循环里忘了 break 提前退出 成功后必须 break,否则浪费 trial

设计层易错点

解释
反思写得太泛("再小心点") 应输出结构化字段 {root_cause, suggestion}
成功后也写反思 自表扬污染 prompt,下次反而走偏
不限定 max_len 记忆腐烂 + token 爆炸
自评 Evaluator 单独用 容易自吹自擂;要和工具锚定验证(CRITIC)搭配
把外部错误(网络挂了)也存反思 该类反思对未来运行无价值,应过滤

参考项目 https://github.com/fancyboi999/ai-engineering-from-scratch-zh

相关推荐
姚青&1 小时前
Rules(行为约束)
ai·ai编程
搬石头的马农1 小时前
御三家旗舰模型混战下的企业选型策略:GPT-5.6、Fable 5、Gemini 3.5 Pro 怎么选? - 微元算力(weytoken)
java·人工智能·python·gpt·ai编程
Artech1 小时前
[MAF预定义ChatClient中间件-08]OpenTelemetryChatClient-实现链路跟踪和性能监控
ai·agent·open telemetry·maf
DS随心转插件1 小时前
实测 AI 导出鸭!Markdown 转 Word 工具效果实测与质量解析
人工智能·ai·word·deepseek·ai导出鸭
牧子川1 小时前
019-JSON-Schema-自动生成
算法·大模型·格式化输出·tools
没有腰的嘟嘟嘟1 小时前
Easy-agent介绍
ai·llm·agent·rag·skill·spring ai·mcp
xingyuzhisuan1 小时前
Redis 多级缓存落地聚合 API:重复请求降本 70% 实战数据
数据库·redis·缓存·ai
小白学大数据1 小时前
知网数据实战:爬虫 + 网络分析打造论文关键词图谱
爬虫·python·scrapy
一锅炖出任易仙2 小时前
创梦汤锅学习日记day31
学习·ai