把失败经验写成文字存入上下文,不需要更新参数也能改进后续尝试
背景
传统强化学习训练 Agent 需要大量 episode、精心设计的奖励函数和梯度反向传播,对已经训练好的大型 LLM,在推理时进行参数更新并不现实。
Reflexion(Shinn et al., 2023)提出了一种不更新参数的替代方式:让 Agent 在每次失败后用自然语言写下反思,把反思存入记忆,在下一轮试错时读取。
核心机制
系统结构
Reflexion 由三个模块组成:
┌─────────────────────────────────────────────────────────┐
│ Reflexion │
│ │
│ Actor Evaluator Self-Reflection │
│ (LLM) → (判断结果) → (LLM 写反思) │
│ ↑ │ │
│ └──────── 记忆(反思文本)────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Actor:执行动作的 LLM,生成代码、做决策或写答案。
Evaluator:判断结果好坏,可以是代码执行结果(测试通过或失败)、外部评判模型,或环境反馈。
Self-Reflection:当 Evaluator 判定失败后,LLM 生成一段自然语言反思,说明哪里错了、为什么错、下次应该怎么做。
记忆的形式
反思存入一个文本列表,在下一次 episode 开始时追加到 prompt 前面:
[过往反思]
- 第1次:我用了二分查找,但没有处理数组为空的情况,导致 IndexError
- 第2次:加了空数组检查,但忘记处理目标值不在数组中的情况,返回了错误的 -1
- 第3次需要注意:确保同时处理空数组和目标值缺失两种边界情况
[当前任务]
实现一个二分查找函数,找不到时返回 -1...
这个机制本质上是把跨 episode 的学习信号打包成自然语言,放回上下文,用 prompt 的方式模拟记忆更新。
在代码生成上的效果
Reflexion 在 HumanEval 上的实验结果:
| 方法 | pass@1 |
|---|---|
| GPT-4(直接生成) | 67.0% |
| GPT-4 + 执行反馈重试 | 73.5% |
| GPT-4 + Reflexion | 91.0% |
从 73.5% 到 91.0% 的差距说明:仅仅重试还不够,需要理解失败原因。反思的作用是把"失败"转化为"因为 X 所以失败、下次要做 Y"。
在决策任务(AlfWorld 文字游戏)上:
| 方法 | 成功率 |
|---|---|
| ReAct(无反思) | 45% |
| ReAct + Reflexion | 97% |
与其他迭代改进方法的对比
| 方法 | 如何利用失败信号 | 跨 episode 记忆 | 是否更新参数 |
|---|---|---|---|
| 直接重试 | 无 | 无 | 否 |
| Self-Debugging(第06篇) | 读错误信息并修复 | 无(单轮) | 否 |
| CodeRL(第03篇) | 测试结果作为 RL 奖励 | 通过参数 | 是 |
| Reflexion | 自然语言反思 | 有(文本记忆) | 否 |
Reflexion 填补的是"不更新参数但需要跨轮次记忆"这个位置:比 Self-Debugging 多了记忆机制,比 CodeRL 少了参数更新的成本。
局限性
记忆长度受限:反思文本随尝试次数增加,最终会超出上下文窗口。论文通常限制记忆为最近 1-3 次,但更早的经验会被丢弃。
反思质量依赖模型能力:如果 LLM 无法准确诊断失败原因,生成的反思是错误的,下一轮会被错误信息误导。模型能力越弱,这个问题越突出。
需要明确的评判标准:Evaluator 需要给出清晰的好坏判断。对于开放域生成任务(写文章、翻译),很难给出精确的对比信号,Reflexion 在这类任务上效果有限。
无法弥补知识缺口:如果失败的根本原因是模型不知道某个知识点,反思也帮不了忙。这时候需要的是 RAG 或工具调用来获取外部知识。
工程实现
一个简化的 Reflexion 循环:
def reflexion_loop(task, max_attempts=5):
memory = []
for attempt in range(max_attempts):
context = "\n".join([
"[过往反思]",
*[f"- 第{i+1}次:{m}" for i, m in enumerate(memory)],
"\n[当前任务]",
task
]) if memory else task
code = llm.generate(f"请解决以下任务:\n{context}")
result = executor.run(code)
if result.passed:
return code
reflection = llm.generate(f"""
任务:{task}
你的代码:{code}
执行结果:{result.error or result.output}
请用 1-2 句话总结:你犯了什么错误,以及下次应该怎么改进。
""")
memory.append(reflection)
return None
Self-Reflection 这步要求模型主动诊断失败原因,而不只是描述失败现象。"代码报了 IndexError"是现象描述;"我没有检查列表为空的情况"才是有用的反思。
后续影响
Reflexion 之后,多篇工作在此基础上延伸:
- LATS(Language Agent Tree Search):把反思机制嵌入树搜索,每条路径失败后生成反思
- ExpeL(Experience Learning):把成功案例也存入记忆,不只学失败经验
- Agent Hospital:把类似机制用于医疗诊断 Agent,让模型从历史案例中积累经验
Reflexion 说明了一件事:在不更新权重的情况下,用语言描述的经验也可以对后续尝试产生影响。这对实际部署中的 Agent 系统有一定参考价值,因为参数更新的成本和门槛往往比调整 prompt 高得多。
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:08. Reflexion:用语言反思替代梯度更新
本文链接:https://www.sshipanoo.com/blog/ai/code-agent-harness/08-Reflexion/
本文最后一次更新为 天前,文章中的某些内容可能已过时!