从零构建 Reflection 智能体:让大模型学会”自我批判”

为什么需要 Reflection?

我们之前构建的两种范式有一个共同点:一次执行,流程即终。

  • ReAct 完成任务后直接输出答案,行动轨迹中即使有错误也不回头修正;
  • Plan-and-Solve 按蓝图施工完毕即收工,即使中间某步算错了,最终答案也会带着错误交付。

可人类的解题过程从来不是这样:写完作文要校对,解完数学题要验算,写完代码要Review。我们完成”初稿”之后,总会退后一步审视它——这就是反思。

2023 年,Shinn, Noah 等人提出的 Reflexion 框架[1] 正是把这一人类本能固化成了智能体的运行机制。其核心思想一句话概括:

为智能体引入一个事后的(post-hoc)自我校正循环,让它像评审员一样审视自己的输出,并据此迭代优化。

1
2
3
4
flowchart LR
A["ReAct / Plan-and-Solve<br/>一次性输出,无回头路"] -->|"存在谬误或改进空间"| B["Reflection<br/>执行 → 反思 → 优化,循环迭代"]
style A fill:#fce4ec,stroke:#c62828
style B fill:#e8f5e9,stroke:#2e7d32

核心思想:执行 → 反思 → 优化

Reflection 的工作流程是一个简洁的三步循环:

阶段 角色 职责 产物
执行 (Execution) 初稿作者 用 ReAct / Plan-and-Solve 等方法完成任务 初始解决方案
反思 (Reflection) 评审员 从多个维度批判性审视初稿 结构化反馈
优化 (Refinement) 修订者 结合初稿与反馈进行修正 修订稿
1
2
3
4
5
6
7
8
9
10
11
12
flowchart TD
Q(["任务 Task"]) --> EXEC["执行 Execution<br/>生成初稿 O₀"]
EXEC --> REFL["反思 Reflection<br/>评审员批判性评估"]
REFL -->|"反馈 Fᵢ"| CHECK{"还有问题?<br/>且未达迭代上限?"}
CHECK -->|"是"| REFINE["优化 Refinement<br/>生成修订稿 Oᵢ₊₁"]
REFINE --> REFL
CHECK -->|"否"| DONE(["最终答案"])

style EXEC fill:#e3f2fd,stroke:#1565c0
style REFL fill:#fff3e0,stroke:#ef6c00
style REFINE fill:#e8f5e9,stroke:#2e7d32
style DONE fill:#f3e5f5,stroke:#6a1b9a

评审员从哪些维度找茬?

反思阶段的”评审员”会从多个维度评估初稿,并生成具体、可操作的反馈:

  • 事实性错误:是否存在与常识或已知事实相悖的内容?
  • 逻辑漏洞:推理过程是否存在不连贯或矛盾之处?
  • 效率问题:是否有更直接、更简洁的路径来完成任务?
  • 遗漏信息:是否忽略了问题的某些关键约束或方面?

形式化表达

假设 $O_i$ 是第 $i$ 次迭代的输出($O_0$ 为初始输出),反思模型 $\pi_{\text{reflect}}$ 生成反馈 $F_i$:

$$
F_i = \pi_{\text{reflect}}(\text{Task}, O_i)
$$

优化模型 $\pi_{\text{refine}}$ 结合原始任务、上一版输出与反馈,生成新一版输出:

$$
O_{i+1} = \pi_{\text{refine}}(\text{Task}, O_i, F_i)
$$

循环持续进行,直到反思不再发现新问题,或达到预设的迭代上限。

Reflection 的三个独特价值

与前两种范式相比,Reflection 的价值在于:

  1. 内部纠错回路:它不再依赖外部工具反馈(如 ReAct 的 Observation),而是通过自我审视修正更高层次的逻辑和策略错误。
  2. 从”一次性执行”到”持续优化”:把单次尝试变成迭代过程,显著提升复杂任务的最终成功率与答案质量。
  3. 构建”短期记忆”:整个”执行-反思-优化”轨迹形成宝贵的经验记录,智能体不仅知道最终答案,还记得自己如何从有缺陷的初稿迭代到最终版本。这个记忆系统还可以是多模态的——反思和修正文本以外的输出(代码、图像等),为构建更强大的多模态智能体奠定基础。

实战案例:用 Reflection 优化代码生成

理论很美好,让我们动手验证。目标是:

编写一个 Python 函数,找出 1 到 n 之间所有的素数 (prime numbers)。

这是检验 Reflection 机制的绝佳场景,因为它具备三个特性:

  1. 存在明确的优化路径:LLM 初次生成的代码很可能是功能正确但效率低下的试除法实现;
  2. 反思点清晰:可以通过反思发现”时间复杂度过高”、”存在重复计算”等问题;
  3. 优化方向明确:可以根据反馈优化为更高效的筛法实现。

记忆模块:迭代的前提

Reflection 的核心是迭代,而迭代的前提是记住之前的尝试和反馈。如果上下文无限长,可以让”评审员”直接读取全部信息;但现实中,这样做会传入大量冗余信息。因此,一个”短期记忆”模块是实现该范式的必需品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 (str): 记录的类型 ('execution' 或 'reflection')。
- 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]:
"""
获取最近一次的执行结果 (例如,最新生成的代码)。
如果不存在,则返回 None。
"""
for record in reversed(self.records):
if record['type'] == 'execution':
return record['content']
return None

Memory 类的主体设计很简洁,但角色关键:

  • records 列表按顺序存储每一次行动与反思,形成完整轨迹;
  • add_record 负责写入新条目;
  • get_trajectory 是核心——将记忆轨迹序列化为文本,直接插入后续提示词,为反思和优化提供完整上下文;
  • get_last_execution 方便快速获取最新”初稿”供评审。
1
2
3
4
5
6
7
8
9
10
11
12
flowchart TB
subgraph MEM ["Memory 短期记忆"]
R1["execution: 初稿代码"] --> R2["reflection: 评审反馈"]
R2 --> R3["execution: 优化后代码"] --> R4["reflection: 复审反馈"] --> R5["..."]
end

A["执行阶段"] -->|"add_record"| MEM
B["反思阶段"] -->|"get_last_execution<br/>获取最新代码"| MEM
C["优化阶段"] -->|"get_trajectory<br/>序列化完整轨迹"| MEM
MEM -->|"轨迹文本"| B

style MEM fill:#ede7f6,stroke:#5e35b1

三套提示词:让 LLM 扮演三个角色

与之前的范式不同,Reflection 需要多个角色的提示词协同工作。

① 初始执行提示词——首次尝试,只要求完成任务:

1
2
3
4
5
6
7
8
INITIAL_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。请根据以下要求,编写一个Python函数。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。

要求: {task}

请直接输出代码,不要包含任何额外的解释。
"""

② 反思提示词——Reflection 机制的灵魂。指示模型扮演”代码评审员”,做批判性分析:

1
2
3
4
5
6
7
8
9
10
REFLECT_PROMPT_TEMPLATE = """
你是一位极其严格的代码评审专家和资深算法工程师,对代码的性能有极致的要求。
你的任务是审查以下Python代码,并专注于找出其在算法效率上的主要瓶颈。

# 原始任务:
{task}

# 待审查的代码:
```python
{code}

请分析该代码的时间复杂度,并思考是否存在一种算法上更优的解决方案来显著提升性能。
如果存在,请清晰地指出当前算法的不足,并提出具体的、可行的改进算法建议(例如,使用筛法替代试除法)。
如果代码在算法层面已经达到最优,才能回答”无需改进”。

请直接输出你的反馈,不要包含任何额外的解释。
“””

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

**③ 优化提示词**——根据反馈修正代码:

```python
REFINE_PROMPT_TEMPLATE = """
你是一位资深的Python程序员。你正在根据一位代码评审专家的反馈来优化你的代码。

# 原始任务:
{task}

# 你上一轮尝试的代码:
{last_code_attempt}
评审员的反馈:
{feedback}

请根据评审员的反馈,生成一个优化后的新版本代码。
你的代码必须包含完整的函数签名、文档字符串,并遵循PEP 8编码规范。
请直接输出优化后的代码,不要包含任何额外的解释。
"""

整合成 ReflectionAgent

将提示词逻辑与 Memory 模块整合进 ReflectionAgent,整个流程围绕”执行-反思-优化”循环展开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 假设 llm_client.py 和 memory.py 已定义
# from llm_client import HelloAgentsLLM
# from memory import Memory

class ReflectionAgent:
def __init__(self, llm_client, max_iterations=3):
self.llm_client = llm_client
self.memory = Memory()
self.max_iterations = max_iterations

def run(self, task: str):
print(f"\n--- 开始处理任务 ---\n任务: {task}")

# --- 1. 初始执行 ---
print("\n--- 正在进行初始尝试 ---")
initial_prompt = INITIAL_PROMPT_TEMPLATE.format(task=task)
initial_code = self._get_llm_response(initial_prompt)
self.memory.add_record("execution", initial_code)

# --- 2. 迭代循环:反思与优化 ---
for i in range(self.max_iterations):
print(f"\n--- 第 {i+1}/{self.max_iterations} 轮迭代 ---")

# a. 反思
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)
self.memory.add_record("reflection", feedback)

# b. 检查是否需要停止
if "无需改进" in feedback:
print("\n✅ 反思认为代码已无需改进,任务完成。")
break

# c. 优化
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最终生成的代码:\n```python\n{final_code}\n```")
return final_code

def _get_llm_response(self, prompt: str) -> str:
"""一个辅助方法,用于调用LLM并获取完整的流式响应。"""
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
return response_text

注意两个关键的工程细节:

  • 终止条件:通过检测反馈中是否包含”无需改进”来收敛循环,避免无限迭代烧钱;
  • max_iterations 上限:即使评审员始终不满意,循环也会在预设轮次后停止——这是防止失控的双保险。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
sequenceDiagram
participant Agent as ReflectionAgent
participant Mem as Memory
participant LLM as LLM

Agent->>LLM: 初始执行提示词
LLM-->>Agent: 初稿代码 O₀
Agent->>Mem: add_record(execution, O₀)

loop 迭代 i = 1..max_iterations
Agent->>Mem: get_last_execution()
Mem-->>Agent: 最新代码
Agent->>LLM: 反思提示词(评审员)
LLM-->>Agent: 反馈 Fᵢ
Agent->>Mem: add_record(reflection, Fᵢ)

alt 反馈包含"无需改进"
Agent->>Agent: 收敛,终止循环
else
Agent->>LLM: 优化提示词(修订者)
LLM-->>Agent: 修订稿 Oᵢ₊₁
Agent->>Mem: add_record(execution, Oᵢ₊₁)
end
end

Agent->>Mem: get_last_execution()
Mem-->>Agent: 最终代码

运行实例:从 O(n√n) 到 O(n log log n)

用素数任务跑一遍,观察完整的优化轨迹:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
--- 开始处理任务 ---
任务: 编写一个Python函数,找出1到n之间所有的素数。

--- 正在进行初始尝试 ---
📝 记忆已更新,新增一条 'execution' 记录。

--- 第 1/2 轮迭代 ---
-> 正在进行反思...
✅ 反馈:当前代码的时间复杂度为O(n * sqrt(n))。虽然对于较小的n值可以接受,
但当n非常大时,性能显著下降。主要瓶颈在于每个数都需要进行试除法检查。
建议使用埃拉托斯特尼筛法(Sieve of Eratosthenes),时间复杂度为O(n log(log n))。
📝 记忆已更新,新增一条 'reflection' 记录。

-> 正在进行优化...
✅ 优化完成,生成筛法版本代码。
📝 记忆已更新,新增一条 'execution' 记录。

--- 第 2/2 轮迭代 ---
-> 正在进行反思...
✅ 反馈:当前代码使用了Eratosthenes筛法,时间复杂度为O(n log log n),
空间复杂度为O(n)。此算法已非常高效,通常情况下无需进一步优化。
(还提及分段筛法、奇数筛法等高级方向,但判断一般场景下无需改进。)
📝 记忆已更新,新增一条 'reflection' 记录。

✅ 反思认为代码已无需改进,任务完成。

--- 任务完成 ---
最终生成的代码:
def find_primes(n):
"""
Finds all prime numbers between 1 and n using the Sieve of Eratosthenes algorithm.
...
"""
if n < 2:
return []

is_prime = [True] * (n + 1)
is_prime[0] = is_prime[1] = False

p = 2
while p * p <= n:
if is_prime[p]:
for i in range(p * p, n + 1, p):
is_prime[i] = False
p += 1

primes = [num for num in range(2, n + 1) if is_prime[num]]
return primes

这个实例展示了 Reflection 驱动深度优化的完整链条:

  1. 有效的”批判”是优化的前提:第一轮反思中,”极其严格”且”专注于算法效率”的提示词,让评审员没有满足于功能正确的初版,而是精准指出 O(n·√n) 的瓶颈,并给出算法级改进建议——埃拉托斯特尼筛法。
  2. 迭代式改进:优化阶段成功实现了筛法,复杂度降至 O(n log log n),完成第一次有意义的自我迭代。
  3. 收敛与终止:第二轮反思中,评审员不仅肯定了筛法的效率,甚至提及分段筛法、奇数筛法等更高级方向,但最终做出”在一般情况下无需改进”的正确判断——触发终止条件,让优化过程收敛。
1
2
3
4
5
6
flowchart LR
V1["初稿: 试除法<br/>O(n·√n)"] -->|"反思: 时间复杂度瓶颈"| V2["修订稿: 埃拉托斯特尼筛法<br/>O(n log log n)"]
V2 -->|"复审: 已足够高效"| V3["收敛: 最终方案"]
style V1 fill:#fce4ec,stroke:#c62828
style V2 fill:#e3f2fd,stroke:#1565c0
style V3 fill:#e8f5e9,stroke:#2e7d32

这个案例证明:设计良好的 Reflection 机制,价值不仅在于修复错误,更在于驱动解决方案在质量和效率上实现阶梯式提升。


成本收益分析:一场”以成本换质量”的交易

Reflection 并非免费的午餐。它的能力有明确代价,需要我们权衡取舍。

主要成本

成本 说明
模型调用开销增加 每轮迭代至少额外调用两次 LLM(一次反思 + 一次优化),多轮迭代下 API 成本成倍增长
任务延迟显著提高 反思-优化是串行过程,每轮必须等上一轮完成,总耗时明显拉长,不适合实时场景
提示工程复杂度上升 需要为”执行/反思/优化”三个角色分别设计并调试高质量提示词,开发投入更大

核心收益

  1. 解决方案质量的跃迁:把一个”合格”的初稿,迭代成一个”优秀”的终稿——从功能正确到性能高效,从逻辑粗糙到逻辑严谨。
  2. 鲁棒性与可靠性增强:内部自我纠错循环能发现并修复逻辑漏洞、事实性错误、边界情况处理不当等问题。

选型建议

1
2
3
4
5
6
7
8
9
flowchart TD
Q{"任务的核心诉求?"} --> C1{"对最终质量要求极高?"}
C1 -->|"是"| C2{"对实时性要求宽松?"}
C2 -->|"是"| R["Reflection<br/>以成本换质量"]
C1 -->|"否"| P["ReAct / Plan-and-Solve<br/>轻量、快速"]
C2 -->|"否"| P

style R fill:#e8f5e9,stroke:#2e7d32
style P fill:#fff3e0,stroke:#ef6c00

适合 Reflection 的场景:生成关键业务代码或技术报告、科学研究中的复杂逻辑推演、需要深度分析与规划的决策支持系统——这类任务对结果质量要求极高、实时性要求相对宽松。

不适合的场景:需要快速响应,或”大致正确”即可——此时更轻量的 ReAct 或 Plan-and-Solve 才是性价比之选。


三种范式:从”做对”到”做好”

至此,三种经典范式已全部登场。它们代表了智能体解决问题的三种不同策略:

范式 核心机制 关键优势 典型场景
ReAct Thought → Action → Observation 动态循环 环境适应性、动态纠错 探索性任务、需外部工具
Plan-and-Solve 先规划完整计划,再逐步执行 结构性、稳定性 逻辑路径确定、推理密集的任务
Reflection 执行 → 反思 → 优化迭代循环 输出质量跃迁 质量与可靠性要求极高的任务

三种范式并非互相排斥,而是可以组合使用:Reflection 的第一阶段”执行”,完全可以由 ReAct 或 Plan-and-Solve 充当。这也是 Agent 设计中最有魅力的部分——范式是积木,而非终点。


总结

Reflection 用一句话概括:让智能体成为自己作品的第一个读者,也是最严格的评审员。

它的工程实现同样留下了三个可迁移的设计经验:

  • 记忆是迭代的载体:没有 Memory 存储轨迹,反思就无从谈起——信息流管理决定了循环能否持续;
  • 终止条件比循环本身更重要:通过”无需改进”信号 + 迭代上限的双保险,把无限优化收敛为有限迭代;
  • 提示词即角色:同一个 LLM,通过三套提示词就能扮演”作者、评审员、修订者”三个角色——角色分工是反思质量的关键。

当你的任务”一次做对很难、但做得更好可行”时,不妨让智能体停下来,回头审视自己的作品。