让大模型学会在执行任务后主动反思,是构建高质量 Agent 的关键一步。
前言
如果你使用过基于 ReAct 模式的 AI Agent,你一定熟悉这样的流程:思考 → 行动 → 观察 → 再思考 → 再行动。这种范式在许多场景下效果不错,但它有一个显著的局限——Agent 只能依赖外部环境给出的反馈来调整行为。如果外部工具返回了错误信息,Agent 可以纠正;但如果 Agent 犯了逻辑错误、采用了低效策略、或者遗漏了关键约束,而外部环境并没有指出这些——这个错误就会悄无声息地留在最终结果中。
这就像一位程序员写完代码后直接提交,没有任何 review、没有自我检查。我们都知道这有多危险。
Reflection(反思)机制正是为解决这个问题而设计的。它赋予智能体一个内部纠错回路,让它在没有外部信号的情况下也能审视自己的输出,发现并修正深层次的问题。
本文将以一个完整的代码实战案例为主线,带你从原理到实现,彻底理解 Reflection 机制的设计与落地。
一、Reflection 的核心思想
Reflection 的灵感直接来源于人类的学习方式:写完文章后校对一遍,解完数学题后验算一次,做完项目后复盘总结。
在学术上,这一思路最早由 Shinn, Noah 等人在 2023 年的 Reflexion 框架中系统地提出。它的核心工作流可以概括为一个简洁的三步循环:
graph LR
A[执行 Execution<br/>生成初稿] --> B[反思 Reflection<br/>评审员评估]
B --> C[优化 Refinement<br/>根据反馈修正]
C --> A
第一步:执行 (Execution)
Agent 使用我们熟悉的范式(ReAct、Plan-and-Solve 等)尝试完成任务,生成一个初步的解决方案。可以理解为"初稿"。
第二步:反思 (Reflection)
Agent 切换角色,调用一个独立的、带有特殊提示词的大模型实例来扮演"评审员"。这个评审员会从多个维度审视初稿:
- 事实性错误:是否存在与常识或已知事实相悖的内容?
- 逻辑漏洞:推理过程是否存在不连贯或前后矛盾?
- 效率问题:是否有更直接、更简洁的路径来完成任务?
- 遗漏信息:是否忽略了问题的某些关键约束或边界条件?
评审员最终产出的是一段结构化反馈,明确指出问题所在,并给出具体改进建议。
第三步:优化 (Refinement)
Agent 将"初稿"和"反馈"一起作为新的上下文,再次调用大模型,要求它根据反馈内容修正初稿,生成"修订稿"。
这个循环可以重复执行,直到反思阶段不再发现新问题,或者达到预设的迭代上限。
与前代范式的本质区别
理解了这三个步骤后,Reflection 与 ReAct、Plan-and-Solve 的差异就清晰了:
| 维度 | ReAct | Plan-and-Solve | Reflection |
|---|---|---|---|
| 反馈来源 | 外部工具/环境 | 预设计划 | 内部评审员 |
| 纠错范围 | 操作层面(工具报错) | 计划层面(步骤缺失) | 逻辑与策略层面 |
| 是否需要外部信号 | 强依赖 | 部分依赖 | 可自主纠错 |
换言之,Reflection 让 Agent 从"依靠环境打耳光才改"升级为"自己照镜子发现不足"。这个能力在处理复杂逻辑任务时尤其关键——它把一次性执行变成了一个持续优化的过程。
此外,整个"执行-反思-优化"的轨迹还形成了一个宝贵的短期记忆。Agent 不仅知道最终答案,还保留了自己从有缺陷的初稿一步步迭代到最终版本的完整路径。这为进一步的知识积累和长期学习打下了基础。
二、模块设计:从概念到代码
理论讲完了,接下来我们动手实现一个完整的 Reflection Agent。我们的目标任务是:
编写一个 Python 函数,找出 1 到 n 之间所有的素数。
这个任务非常适合验证 Reflection 机制:
- 初稿很可能是功能正确但效率低下的实现;
- 反思点非常明确——时间复杂度、空间利用、Python 特有的性能坑;
- 优化方向清晰——从朴素算法到筛法,从筛法到优化筛法。
整个 Agent 由三个核心模块组成:记忆模块、提示词系统、以及主控逻辑。我们逐个击破。
graph TB
subgraph Agent[ReflectionAgent]
Memory[Memory 记忆模块<br/>records列表]
Exec[初始执行<br/>Execution Prompt]
Refl[反思评审<br/>Reflection Prompt]
Refi[优化修正<br/>Refinement Prompt]
end
Memory -->|获取最新代码| Refl
Refl -->|存储反馈| Memory
Memory -->|获取历史轨迹| Refi
Refi -->|存储新代码| Memory
Input[用户任务] --> Exec
Exec -->|初始代码| Memory
Refl -->|反馈| Refi
Refi -->|优化代码| Refl
Refl -->|无需改进| Output[最终代码]
2.1 记忆模块:存储迭代轨迹
Reflection 的每一次"执行"和"反思"都需要被记录下来,作为后续优化的上下文。这正是记忆模块的职责。
设计要点:
- 使用列表按时间顺序存储所有记录
- 每条记录包含类型(
execution或reflection)和内容 - 提供轨迹序列化方法,将记忆转换为可直接嵌入提示词的文本
- 提供快速获取最新执行结果的方法
from typing import List, Dict, Any, Optional
class Memory:
"""短期记忆模块,存储智能体的行动与反思轨迹。"""
def __init__(self):
self.records: List[Dict[str, Any]] = []
def add_record(self, record_type: str, content: str):
"""添加一条记录到记忆中。"""
record = {"type": record_type, "content": content}
self.records.append(record)
print(f"记忆已更新,新增一条记录: {record_type}")
def get_trajectory(self) -> str:
"""将所有记忆记录格式化为连贯文本,用于构建提示词。"""
trajectory_parts = []
for record in self.records:
if record["type"] == "execution":
trajectory_parts.append(
f"---上一轮尝试---\n{record['content']}"
)
elif record["type"] == "reflection":
trajectory_parts.append(
f"---评审员反馈---\n{record['content']}"
)
return "\n\n".join(trajectory_parts)
def get_last_execution(self) -> Optional[str]:
"""获取最近一次执行结果。"""
for record in reversed(self.records):
if record["type"] == "execution":
return record["content"]
return None
这个模块是整个 Reflection Agent 的数据底座。它的设计思路是:分离存储逻辑与业务逻辑——Memory 只关心"怎么记"和"怎么取",至于"记什么"和"取来做什么",完全交给上层 Agent 决策。
2.2 提示词系统:三个角色,三种指令
提示词是 Reflection 机制的灵魂。我们需要为三个不同阶段设计完全不同的角色指令。这三种提示词的质量,直接决定了反思的深度和优化的效果。
提示词一:初始执行 (Execution Prompt)
这个提示词最直接——要求模型扮演程序员,生成一份代码初稿。
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
要求: {task}
请直接输出代码,不要包含任何额外的解释。
提示词二:反思评审 (Reflection Prompt)
这是整个机制的核心。它要求模型完全切换身份,从"执行者"变成"批判者"。注意提示词中的关键设计:
- 明确给出了评审的聚焦维度:算法效率,而非代码风格或文档质量
- 为模型提供了具体的思考路径:分析时间复杂度 → 寻找算法层面的优化空间 → 给出可操作的建议
- 设置了硬性退出条件:"如果代码在算法层面已经达到最优,才能回答'无需改进'"
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
你的任务是审查以下Python代码,并专注于找出其在算法效率上的主要瓶颈。
# 原始任务:
{task}
# 待审查的代码:
```python
{code}
```
请分析该代码的时间复杂度,并思考是否存在一种算法上更优的解决方案来显著提升性能。
如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议(例如,使用筛法替代试除法)。
如果代码在算法层面已经达到最优,才能回答"无需改进"。
请直接输出你的反馈,不要包含任何额外的解释。
这里有一个值得讨论的设计决策:反思和执行的提示词应该由同一个模型处理,还是分开?
在我们的实现中,我们使用了同一个 LLM 实例,但通过不同的提示词让它扮演截然不同的角色。这种做法在工程上足够简洁,在实践中也效果不错。但如果你的场景对反思深度要求极高,使用一个更强的模型专门做评审,而日常执行使用更具成本优势的模型,是完全可以考虑的架构选择。
提示词三:优化修正 (Refinement Prompt)
执行者拿到评审员的反馈后,需要据此修改代码。这个提示词的核心是将反馈显式地嵌入上下文,确保模型不是"随便改改",而是"针对反馈逐条回应"。
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。
# 原始任务:
{task}
# 你上一轮尝试的代码:
{last_code_attempt}
# 评审员的反馈:
{feedback}
请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
2.3 Agent 主控逻辑:编排三步循环
有了 Memory 和提示词,我们就可以把它们串联成完整的工作流了。
ReflectionAgent 的核心逻辑在 run() 方法中,它实现了以下流程:
graph TD
Start([开始]) --> Init[初始执行<br/>生成初稿代码]
Init --> Store1[存入记忆]
Store1 --> Loop{达到最大<br/>迭代次数?}
Loop -->|否| GetLast[从记忆获取<br/>最新代码]
GetLast --> Reflect[反思评审<br/>生成反馈]
Reflect --> Store2[反馈存入记忆]
Store2 --> Check{反馈为<br/>无需改进?}
Check -->|是| Done([任务完成])
Check -->|否| Refine[根据反馈优化代码]
Refine --> Store3[优化代码存入记忆]
Store3 --> Loop
Loop -->|是| Done
对应的代码实现如下:
class ReflectionAgent:
def __init__(self, llm_client, max_iterations: int = 3):
self.llm_client = llm_client
self.max_iterations = max_iterations
self.memory = Memory()
def run(self, task: str):
print(f"\n---开始处理任务---\n{task}")
# 阶段一:初始执行
initial_prompt = INITIAL_PROMPT_TEMPLATE.format(task=task)
initial_code = self._get_llm_response(initial_prompt)
print(f"初始代码:\n{initial_code}")
self.memory.add_record("execution", initial_code)
# 阶段二:迭代循环
for i in range(self.max_iterations):
print(f"\n--- 第 {i + 1} 轮迭代 ---")
# 反思
print("\n正在进行反思...")
last_code = self.memory.get_last_execution()
reflect_prompt = REFLECT_PROMPT_TEMPLATE.format(
task=task, code=last_code
)
feedback = self._get_llm_response(reflect_prompt) or ""
self.memory.add_record("reflection", feedback)
# 检查停止条件
if feedback == "无需改进":
print("\n代码已优化至最优,无需进一步改进。")
break
# 优化
print("\n------正在进行优化------")
refine_prompt = REFINE_PROMPT_TEMPLATE.format(
task=task,
last_code_attempt=last_code,
feedback=feedback,
)
refined_code = self._get_llm_response(refine_prompt)
self.memory.add_record("execution", refined_code)
final_code = self.memory.get_last_execution()
print(f"\n✅ 任务完成,最终代码:\n{final_code}")
return final_code
def _get_llm_response(self, prompt: str) -> str:
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
return response_text
三、实战观察:素数函数的进化之路
让我们运行这个 Agent,观察 Reflection 机制在实际执行中是如何驱动代码一步步进化的。
第一轮:初始执行
Agent 首先生成了一个基于埃拉托斯特尼筛法 (Sieve of Eratosthenes) 的实现。这是一个经典的素数查找算法,时间复杂度为 O(n log log n)。
from typing import List
def find_primes(n: int) -> List[int]:
"""Find all prime numbers between 1 and n inclusive."""
if n < 2:
return []
is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(n**0.5) + 1):
if is_prime[i]:
for j in range(i * i, n + 1, i):
is_prime[j] = False
return [i for i in range(2, n + 1) if is_prime[i]]
这段代码功能完全正确。如果没有 Reflection 机制,Agent 在这里就会停下来了。
但请看评审员给出的反馈:
第一轮反思:评审员的批判
评审员的分析非常精准,直接命中了三个要害:
- Python 循环开销(致命瓶颈) :内层的
for j in range(...)在 Python 解释器层面逐行执行,常数级开销极大。即使理论复杂度正确,实际运行效率也大打折扣。 - 空间冗余:
List[bool]存储布尔值占用内存过大,且偶数(除 2 外)都是合数,浪费了约 50% 的计算和存储。 - 理论复杂度非最优:存在 O(n) 的线性筛法(欧拉筛)在理论上优于 O(n log log n)。
评审员同时给出了三条具体、可操作的改进建议:
切片赋值优化:利用 Python 列表切片的 C 层实现,将内层循环替换为批量赋值,消除解释器循环开销。
奇数空间压缩:仅存储奇数索引,数组大小减半,直接跳过偶数判断。
使用
bytearray替代list[bool]:减少对象头开销,提高缓存局部性。
第一轮优化:从"能用"到"好用"
Agent 根据反馈进行了优化。生成的新代码结合了评审员的所有建议:
- 用
bytearray替代list[bool] - 只存储奇数,通过索引映射
index → 2*k + 1实现空间减半 - 用切片赋值
is_prime[i*i : n+1 : i] = ...替换内层for循环
第二轮反思:确认最优
优化后的代码进入了第二轮评审。这次评审员得出了不同的结论:
当前算法采用奇数优化的埃拉托斯特尼筛法,已通过
bytearray和切片赋值将内层循环下沉至 C 语言层面。虽然存在理论 O(n) 的线性筛法,但在 Python 环境中,线性筛需要维护最小质因子数组并执行显式的 Python 级循环,其常数因子远大于利用切片优化的埃氏筛,实际运行效率反而更低。
结论:无需改进。
这个判断非常关键——它说明了一个好的 Reflection 机制不仅能推动优化,更能识别出"优化过头"的风险,在成本和收益之间找到最佳平衡点。
四、成本收益分析:Reflection 值得吗?
坦率地说,Reflection 绝非免费午餐。在决定是否引入它之前,你需要诚实地评估它的代价和回报。
成本:你付出的
| 成本项 | 说明 |
|---|---|
| 模型调用翻倍 | 每轮迭代至少额外调用 2 次 LLM(反思 + 优化)。3 轮迭代意味着 7 次调用,而不用 Reflection 只需要 1 次。 |
| 延迟显著增加 | Reflection 是串行过程——执行 → 反思 → 优化 → 反思 → 优化...。每一轮都必须等待上一轮完成,任务总耗时线性增加。 |
| 提示工程复杂度上升 | 你需要为三个不同阶段设计和调试高质量的提示词。反思提示词的质量直接决定了机制的有效性——一个糟糕的评审员会让整个循环变成"瞎指挥"。 |
收益:你得到的
| 收益项 | 说明 |
|---|---|
| 方案质量跃迁 | 从"功能正确"跃升到"性能高效"、"逻辑严谨"。这种阶梯式的质量提升在关键任务中是不可替代的。 |
| 鲁棒性增强 | 自我纠错回路能捕获初稿中的逻辑漏洞、边界情况和效率陷阱,这些都是纯外部反馈无法覆盖的。 |
| 长上下文下的结构化思考 | 迭代轨迹本身就是高质量的思考记录。如果你需要可解释的推理过程(而非仅仅一个答案),Reflection 是天然的选择。 |
适用边界
Reflection 适合的场景:
- 生成关键业务代码或技术报告,正确性和效率是第一优先级
- 复杂的逻辑推理(科学研究、数学证明、决策分析)
- 对"为什么是这个答案"的解释质量有要求的场景
- 任务的实时性要求宽松,可以接受数秒到数分钟的延迟
Reflection 不适合的场景:
- 需要亚秒级响应的交互式应用(聊天助手、实时翻译)
- 任务本身足够简单,一次输出就足够好("大致正确"即可接受)
- 成本极度敏感,额外的模型调用不可接受
一个实践经验法则是:如果任务的"出错成本"远高于"多调几次模型的花费",就值得使用 Reflection。
五、小结与下一步
Reflection 机制是智能体范式演进中的一个重要里程碑。它填补了 ReAct 和 Plan-and-Solve 留下的空白——在缺乏外部反馈信号时,Agent 如何自主地提升输出质量。
回顾本文的核心要点:
- Reflection 的三步循环(执行 → 反思 → 优化)为 Agent 提供了内部纠错能力,使其能修正逻辑和策略层面的错误,而不仅仅是响应环境反馈。
- 提示词的设计是灵魂所在——评审员提示词需要明确的聚焦维度、思考路径和退出条件,才能产出真正有价值的反馈。
- 记忆模块是数据底座——将执行和反思的轨迹结构化存储,是后续优化和长期学习的基础。
- 成本与收益需要权衡——Reflection 是一种"以成本换质量"的策略,适用于高价值的复杂任务。
如果你正在构建 Agent 系统,建议从以下步骤开始尝试 Reflection:
- 先在单个高价值任务上引入一轮反思循环,观察效果;
- 如果效果好,逐步扩展到更多任务,并考虑引入记忆持久化,让反思经验跨任务复用;
- 探索分层反思——让反思本身也成为被反思的对象(meta-reflection),进一步提升深度。
Agent 的进化才刚刚开始。而"学会反思",无疑是让它变得更可靠、更智能的关键一步。