七、智能体的Reflection机制:让AI学会三省吾身

0 阅读15分钟

让大模型学会在执行任务后主动反思,是构建高质量 Agent 的关键一步。

前言

如果你使用过基于 ReAct 模式的 AI Agent,你一定熟悉这样的流程:思考 → 行动 → 观察 → 再思考 → 再行动。这种范式在许多场景下效果不错,但它有一个显著的局限——Agent 只能依赖外部环境给出的反馈来调整行为。如果外部工具返回了错误信息,Agent 可以纠正;但如果 Agent 犯了逻辑错误、采用了低效策略、或者遗漏了关键约束,而外部环境并没有指出这些——这个错误就会悄无声息地留在最终结果中。

这就像一位程序员写完代码后直接提交,没有任何 review、没有自我检查。我们都知道这有多危险。

Reflection(反思)机制正是为解决这个问题而设计的。它赋予智能体一个内部纠错回路,让它在没有外部信号的情况下也能审视自己的输出,发现并修正深层次的问题。

本文将以一个完整的代码实战案例为主线,带你从原理到实现,彻底理解 Reflection 机制的设计与落地。

Reflection工作流程.png

一、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 的差异就清晰了:

维度ReActPlan-and-SolveReflection
反馈来源外部工具/环境预设计划内部评审员
纠错范围操作层面(工具报错)计划层面(步骤缺失)逻辑与策略层面
是否需要外部信号强依赖部分依赖可自主纠错

换言之,Reflection 让 Agent 从"依靠环境打耳光才改"升级为"自己照镜子发现不足"。这个能力在处理复杂逻辑任务时尤其关键——它把一次性执行变成了一个持续优化的过程

此外,整个"执行-反思-优化"的轨迹还形成了一个宝贵的短期记忆。Agent 不仅知道最终答案,还保留了自己从有缺陷的初稿一步步迭代到最终版本的完整路径。这为进一步的知识积累和长期学习打下了基础。


二、模块设计:从概念到代码

理论讲完了,接下来我们动手实现一个完整的 Reflection Agent。我们的目标任务是:

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

这个任务非常适合验证 Reflection 机制:

  1. 初稿很可能是功能正确但效率低下的实现;
  2. 反思点非常明确——时间复杂度、空间利用、Python 特有的性能坑;
  3. 优化方向清晰——从朴素算法到筛法,从筛法到优化筛法。

整个 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 的每一次"执行"和"反思"都需要被记录下来,作为后续优化的上下文。这正是记忆模块的职责。

设计要点:

  • 使用列表按时间顺序存储所有记录
  • 每条记录包含类型(executionreflection)和内容
  • 提供轨迹序列化方法,将记忆转换为可直接嵌入提示词的文本
  • 提供快速获取最新执行结果的方法
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 在这里就会停下来了。

但请看评审员给出的反馈:

第一轮反思:评审员的批判

评审员的分析非常精准,直接命中了三个要害:

  1. Python 循环开销(致命瓶颈) :内层的 for j in range(...) 在 Python 解释器层面逐行执行,常数级开销极大。即使理论复杂度正确,实际运行效率也大打折扣。
  2. 空间冗余List[bool] 存储布尔值占用内存过大,且偶数(除 2 外)都是合数,浪费了约 50% 的计算和存储。
  3. 理论复杂度非最优:存在 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 如何自主地提升输出质量。

回顾本文的核心要点:

  1. Reflection 的三步循环(执行 → 反思 → 优化)为 Agent 提供了内部纠错能力,使其能修正逻辑和策略层面的错误,而不仅仅是响应环境反馈。
  2. 提示词的设计是灵魂所在——评审员提示词需要明确的聚焦维度、思考路径和退出条件,才能产出真正有价值的反馈。
  3. 记忆模块是数据底座——将执行和反思的轨迹结构化存储,是后续优化和长期学习的基础。
  4. 成本与收益需要权衡——Reflection 是一种"以成本换质量"的策略,适用于高价值的复杂任务。

如果你正在构建 Agent 系统,建议从以下步骤开始尝试 Reflection:

  1. 先在单个高价值任务上引入一轮反思循环,观察效果;
  2. 如果效果好,逐步扩展到更多任务,并考虑引入记忆持久化,让反思经验跨任务复用;
  3. 探索分层反思——让反思本身也成为被反思的对象(meta-reflection),进一步提升深度。

Agent 的进化才刚刚开始。而"学会反思",无疑是让它变得更可靠、更智能的关键一步。