从零构建 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 的价值在于:
内部纠错回路 :它不再依赖外部工具反馈(如 ReAct 的 Observation),而是通过自我审视修正更高层次的逻辑和策略错误。
从”一次性执行”到”持续优化” :把单次尝试变成迭代过程,显著提升复杂任务的最终成功率与答案质量。
构建”短期记忆” :整个”执行-反思-优化”轨迹形成宝贵的经验记录,智能体不仅知道最终答案,还记得自己如何从有缺陷的初稿迭代到最终版本。这个记忆系统还可以是多模态 的——反思和修正文本以外的输出(代码、图像等),为构建更强大的多模态智能体奠定基础。
实战案例:用 Reflection 优化代码生成 理论很美好,让我们动手验证。目标是:
编写一个 Python 函数,找出 1 到 n 之间所有的素数 (prime numbers)。
这是检验 Reflection 机制的绝佳场景,因为它具备三个特性:
存在明确的优化路径 :LLM 初次生成的代码很可能是功能正确但效率低下的试除法实现;
反思点清晰 :可以通过反思发现”时间复杂度过高”、”存在重复计算”等问题;
优化方向明确 :可以根据反馈优化为更高效的筛法实现。
记忆模块:迭代的前提 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 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} " ) 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) for i in range (self .max_iterations): print (f"\n--- 第 {i+1 } /{self.max_iterations} 轮迭代 ---" ) 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) if "无需改进" in 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最终生成的代码:\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 驱动深度优化的完整链条:
有效的”批判”是优化的前提 :第一轮反思中,”极其严格”且”专注于算法效率”的提示词,让评审员没有满足于功能正确的初版,而是精准指出 O(n·√n) 的瓶颈,并给出算法级改进建议——埃拉托斯特尼筛法。
迭代式改进 :优化阶段成功实现了筛法,复杂度降至 O(n log log n),完成第一次有意义的自我迭代。
收敛与终止 :第二轮反思中,评审员不仅肯定了筛法的效率,甚至提及分段筛法、奇数筛法等更高级方向,但最终做出”在一般情况下无需改进”的正确判断——触发终止条件,让优化过程收敛。
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 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,通过三套提示词就能扮演”作者、评审员、修订者”三个角色——角色分工是反思质量的关键。
当你的任务”一次做对很难、但做得更好可行”时,不妨让智能体停下来,回头审视自己的作品。