让智能体学会自我改进:从 0 理解 ReflectionAgent 的迭代优化

为什么 ReAct 还不够?

到了第二篇,我们已经有了一个很实用的 ReActAgent。

它能:

  • 理解任务
  • 调用工具
  • 根据 Observation 继续推理

看起来已经很像一个真正的智能体了。

但小王很快发现了另一个现实问题:

小王:"它会做事了,但结果质量并不稳定。有时很好,有时只是勉强能用。尤其是写代码、写方案、写总结的时候,第一版往往不够好。"

这句话其实准确指出了 ReAct 的边界:

  • ReAct 擅长"获取信息"和"完成动作"
  • 但不天然擅长"审查结果质量"和"持续优化输出"

Reflection 就是补这块能力的。


第一章:Reflection 到底想解决什么问题?

1.1 一次性输出为什么经常不够好?

很多 Agent 默认都是这种模式:

text 复制代码
收到任务 -> 生成一个答案 -> 结束

这个模式的问题非常常见:

  • 第一版不一定是最好的一版
  • 模型不会主动检查自己的漏洞
  • 明明还可以优化,但它默认"任务已完成"

例如:

任务:写一个高效的素数查找函数

第一版模型很可能给你一个"能跑"的版本,但不一定是"高质量"的版本。

1.2 人类高质量工作的方式,本来就是迭代的

人类在处理重要任务时,通常不是一次成稿,而是:

text 复制代码
先做出初稿 -> 审查 -> 发现问题 -> 修改 -> 再检查

比如:

  • 写代码时,先保证能运行,再看复杂度、边界和鲁棒性
  • 写方案时,先搭结构,再回头找漏洞
  • 写文章时,先把内容写出来,再修逻辑和表达

Reflection 的核心启发,就是把这套"先做,再审,再改"的工作模式引入 Agent。

1.3 Reflection 的定义

可以把 Reflection 理解成:

让智能体在生成初始结果之后,主动审查自己的输出,并根据反馈继续迭代优化。

它和"重新随机生成一版"不一样。它的关键是:

  • 有初稿
  • 有反馈
  • 有定向优化

第二章:先把这篇文章要用到的基础工程准备好

这一篇里你同样会看到 HelloAgentsLLM。 但这次我不假设你知道它是什么,我们还是从零搭。

2.1 项目结构

建议你先建这样一个目录:

text 复制代码
reflection-agent-demo/
├── package.json
├── tsconfig.json
├── .env
└── src/
    ├── shared/
    │   └── llm.ts
    ├── reflection/
    │   ├── memory.ts
    │   ├── prompts.ts
    │   └── agent.ts
    └── index.ts

2.2 package.json

json 复制代码
{
  "name": "reflection-agent-demo",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "ts-node src/index.ts",
    "build": "tsc"
  },
  "dependencies": {
    "dotenv": "^16.6.1",
    "openai": "^4.104.0"
  },
  "devDependencies": {
    "@types/node": "^20.10.0",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.0"
  }
}

然后执行:

bash 复制代码
npm install

2.3 tsconfig.json

json 复制代码
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "node",
    "lib": ["ES2022"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "types": ["node"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

2.4 .env

bash 复制代码
LLM_API_KEY="你的模型 API Key"
LLM_MODEL_ID="gpt-4o-mini"
LLM_BASE_URL="https://api.openai.com/v1"

2.5 共享层 llm.ts

创建 src/shared/llm.ts

typescript 复制代码
// src/shared/llm.ts
import "dotenv/config";
import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";

export interface LLMOptions {
  model?: string;
  apiKey?: string;
  baseUrl?: string;
  timeout?: number;
}

export class HelloAgentsLLM {
  private readonly client: OpenAI;
  private readonly model: string;

  constructor(options: LLMOptions = {}) {
    const apiKey = options.apiKey ?? process.env.LLM_API_KEY;
    const baseUrl = options.baseUrl ?? process.env.LLM_BASE_URL;

    if (!apiKey) {
      throw new Error("缺少 LLM_API_KEY,请检查 .env 配置");
    }

    this.model = options.model ?? process.env.LLM_MODEL_ID ?? "gpt-4o-mini";

    this.client = new OpenAI({
      apiKey,
      baseURL: baseUrl,
      timeout: options.timeout ?? 60_000,
    });
  }

  async think(
    messages: ChatCompletionMessageParam[],
    temperature = 0.7
  ): Promise<string> {
    const response = await this.client.chat.completions.create({
      model: this.model,
      messages,
      temperature,
    });

    return response.choices[0]?.message?.content ?? "";
  }
}

第三章:Reflection 的核心机制是什么?

3.1 三步循环:执行、反思、优化

Reflection 最核心的结构可以概括成:

flowchart LR E["Execution\n先生成初稿"] --> R["Reflection\n审查当前结果"] R --> F["Refinement\n根据反馈优化"] F --> R R --> D["Done\n无需继续改进时结束"]

这三个阶段分别负责:

  • Execution 先产出一个可用版本,不追求完美,但至少要有东西可改。
  • Reflection 站在审查者视角找问题、找遗漏、找优化点。
  • Refinement 按反馈修正,生成下一版。

3.2 Reflection 和 ReAct 的本质区别

两者都长得像"循环",但关注点完全不同。

维度 ReAct Reflection
核心循环 Thought -> Action -> Observation Execution -> Reflection -> Refinement
反馈来源 外部工具反馈 内部自我批判
核心目标 获取信息、完成动作 提升输出质量
常见场景 查询、执行、调用 代码、写作、分析、方案优化

一句话总结:

  • ReAct 更像"边做边找路"
  • Reflection 更像"做完以后回头打磨"

3.3 为什么 Reflection 不是无限循环?

如果不给它停下来的条件,它就很容易:

  • 一直改不完
  • 越改越啰嗦
  • 成本越来越高

所以实际实现里必须有终止条件,例如:

  • 模型明确说"无需改进"
  • 达到最大迭代次数
  • 连续几轮没有显著提升
  • 已达到目标质量阈值

第四章:Reflection 通常在反思什么?

"反思"两个字听起来很抽象,但在工程实现里,它其实就是一组可枚举的质量维度检查。

最常见的维度包括:

  1. 正确性 有没有事实错误、逻辑错误、边界遗漏?
  2. 完整性 有没有漏掉关键条件、关键步骤或关键结论?
  3. 效率 尤其是代码任务里,是否存在更优算法或更合理的数据结构?
  4. 可读性 表达是否清楚?结构是否合理?命名是否规范?
  5. 鲁棒性 有没有异常场景、非法输入、边界情况没处理?

例如,一个代码审查型 Reflection 反馈可能是这样的:

text 复制代码
当前方案能够工作,但存在以下问题:
1. 时间复杂度偏高
2. 边界情况未覆盖
3. 注释和结构不够清晰
建议改用更优算法并补齐异常处理

Reflection 真正强的地方在于:

它不是重新随机生成一版,而是基于一轮明确反馈去做定向改进。


第五章:先实现轨迹记忆系统

Reflection 和 ReAct 最大不同之一在于: 它必须记住每一轮"产出了什么"和"反馈了什么",否则后续优化根本无从谈起。

创建 src/reflection/memory.ts

typescript 复制代码
// src/reflection/memory.ts
export type RecordType = "execution" | "reflection";

export interface ReflectionRecord {
  type: RecordType;
  content: string;
  timestamp: Date;
  iteration: number;
}

export class Memory {
  private records: ReflectionRecord[] = [];
  private currentIteration = 0;

  addRecord(type: RecordType, content: string): void {
    this.records.push({
      type,
      content,
      timestamp: new Date(),
      iteration: this.currentIteration,
    });
  }

  startNewIteration(): void {
    this.currentIteration++;
  }

  getLastExecution(): string | null {
    for (let i = this.records.length - 1; i >= 0; i--) {
      if (this.records[i].type === "execution") {
        return this.records[i].content;
      }
    }
    return null;
  }

  getLastReflection(): string | null {
    for (let i = this.records.length - 1; i >= 0; i--) {
      if (this.records[i].type === "reflection") {
        return this.records[i].content;
      }
    }
    return null;
  }

  getTrajectory(): string {
    const parts: string[] = [];

    for (const record of this.records) {
      const title =
        record.type === "execution"
          ? `--- 第 ${record.iteration + 1} 轮执行结果 ---`
          : `--- 第 ${record.iteration + 1} 轮反思反馈 ---`;

      parts.push(`${title}\n${record.content}`);
    }

    return parts.join("\n\n");
  }

  clear(): void {
    this.records = [];
    this.currentIteration = 0;
  }
}

5.1 为什么这层 Memory 很关键?

因为 Reflection 不是单轮系统,它要支持:

  • 找到最近一次产出
  • 找到最近一次反馈
  • 保留完整轨迹用于调试或展示

没有这层记忆,整个迭代循环就只是"反复重新生成",而不是"基于上一轮持续改进"。


第六章:设计三套 Prompt

Reflection 一般至少要有三套 Prompt:

  1. initial 负责第一次生成结果
  2. reflect 负责批判性审查
  3. refine 负责根据反馈优化

创建 src/reflection/prompts.ts

typescript 复制代码
// src/reflection/prompts.ts
export const PROMPTS = {
  initial: `
你是一位资深的程序员。请根据以下任务,给出完整解决方案。

任务: {task}

要求:
1. 提供完整的解决方案
2. 包含必要的注释说明
3. 考虑边界情况和错误处理

请直接输出解决方案:
`,

  reflect: `
你是一位极其严格的评审专家。请仔细审查以下解决方案,并指出其中的问题或改进空间。

# 原始任务:
{task}

# 当前解决方案:
{content}

请从以下维度进行分析:
1. 正确性:是否存在逻辑错误或边界情况遗漏?
2. 效率:时间/空间复杂度是否可以优化?
3. 可读性:结构是否清晰,命名是否规范?
4. 健壮性:错误处理是否完善?

如果解决方案已经非常优秀,请回答"无需改进"。
`,

  refine: `
请根据评审反馈优化你的解决方案。

# 原始任务:
{task}

# 上一轮方案:
{last_attempt}

# 评审反馈:
{feedback}

请输出优化后的解决方案:
`,
};

6.1 为什么不能只用一个 Prompt?

因为如果你只给一个 Prompt,让模型既产出、又审查、又优化,它很容易:

  • 审查不够严格
  • 优化方向不清晰
  • 最终输出混在一起

拆成三套 Prompt,职责才会足够清楚。


第七章:实现 ReflectionAgent 主循环

现在进入核心部分:把 Memory 和 Prompt 串成一个可运行的迭代 Agent。

创建 src/reflection/agent.ts

typescript 复制代码
// src/reflection/agent.ts
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";
import { HelloAgentsLLM } from "../shared/llm";
import { Memory } from "./memory";
import { PROMPTS } from "./prompts";

export interface ReflectionAgentConfig {
  name: string;
  maxIterations: number;
  customPrompts?: Partial<typeof PROMPTS>;
}

export class ReflectionAgent {
  private readonly llm: HelloAgentsLLM;
  private readonly memory: Memory;
  private readonly config: ReflectionAgentConfig;
  private readonly prompts: typeof PROMPTS;

  constructor(
    llm: HelloAgentsLLM,
    config: Partial<ReflectionAgentConfig> = {}
  ) {
    this.llm = llm;
    this.memory = new Memory();
    this.config = {
      name: "ReflectionAgent",
      maxIterations: 3,
      ...config,
    };
    this.prompts = { ...PROMPTS, ...config.customPrompts };
  }

  async run(task: string): Promise<string> {
    this.memory.clear();

    console.log(`\n🤖 ${this.config.name} 开始处理任务: ${task}\n`);

    // 1. 初始执行
    console.log("--- 初始执行 ---");
    const initialResult = await this.execute(
      this.prompts.initial.replace("{task}", task)
    );

    this.memory.addRecord("execution", initialResult);
    console.log(`📝 初稿:\n${initialResult}\n`);

    // 2. 反思与优化循环
    for (let i = 0; i < this.config.maxIterations; i++) {
      this.memory.startNewIteration();
      console.log(`=== 第 ${i + 1}/${this.config.maxIterations} 轮迭代 ===`);

      const currentWork = this.memory.getLastExecution() || "";

      console.log("-> 正在反思...");
      const reflectionPrompt = this.prompts.reflect
        .replace("{task}", task)
        .replace("{content}", currentWork);

      const feedback = await this.execute(reflectionPrompt);
      this.memory.addRecord("reflection", feedback);
      console.log(`💬 反馈:\n${feedback}\n`);

      if (this.shouldTerminate(feedback)) {
        console.log("✅ 反思认为已无需改进,任务结束。\n");
        break;
      }

      console.log("-> 正在优化...");
      const refinePrompt = this.prompts.refine
        .replace("{task}", task)
        .replace("{last_attempt}", currentWork)
        .replace("{feedback}", feedback);

      const refinedResult = await this.execute(refinePrompt);
      this.memory.addRecord("execution", refinedResult);
      console.log(`✨ 优化后:\n${refinedResult}\n`);
    }

    const finalResult = this.memory.getLastExecution() || "";
    console.log("--- 任务完成 ---");
    console.log(finalResult);
    return finalResult;
  }

  private async execute(prompt: string): Promise<string> {
    const messages: ChatCompletionMessageParam[] = [
      { role: "user", content: prompt },
    ];

    return await this.llm.think(messages, 0.7);
  }

  private shouldTerminate(feedback: string): boolean {
    const terminationPhrases = [
      "无需改进",
      "已经最优",
      "没有问题",
      "非常优秀",
      "no improvement needed",
      "already optimal",
      "perfect",
    ];

    return terminationPhrases.some((phrase) =>
      feedback.toLowerCase().includes(phrase.toLowerCase())
    );
  }

  getTrajectory(): string {
    return this.memory.getTrajectory();
  }
}

7.1 这段循环到底在做什么?

你可以把它理解成下面这 6 步:

  1. 先生成一版初稿
  2. 把初稿存进 Memory
  3. 用反思 Prompt 去审查它
  4. 如果反馈说"无需改进",就结束
  5. 否则用优化 Prompt 再生成一版
  6. 继续下一轮

这就是 Reflection 的本质。

7.2 shouldTerminate() 为什么不能省?

因为如果没有终止机制,模型可能会:

  • 一直说还可以更好
  • 一直反复改写同一个版本
  • 造成不必要的 token 浪费和响应延迟

所以终止逻辑虽然简单,但非常重要。


第八章:完整示例,做一个"代码优化型 ReflectionAgent"

下面我们做一个完整例子: 让 ReflectionAgent 优化一段找素数的 Python 代码。

创建 src/index.ts

typescript 复制代码
// src/index.ts
import "dotenv/config";
import { HelloAgentsLLM } from "./shared/llm";
import { ReflectionAgent } from "./reflection/agent";

async function main() {
  const llm = new HelloAgentsLLM();

  const agent = new ReflectionAgent(llm, {
    name: "代码优化助手",
    maxIterations: 2,
    customPrompts: {
      reflect: `
你是一位极其严格的代码评审专家和资深算法工程师,对代码性能有极致要求。

# 原始任务:
{task}

# 待审查代码:
\`\`\`
{content}
\`\`\`

请分析以下方面:
1. 时间复杂度:当前复杂度是多少?是否可以优化?
2. 空间复杂度:内存使用是否合理?
3. 算法选择:是否存在更优算法思路?

如果代码在算法层面已经达到最优,请回答"无需改进"。
`,
    },
  });

  const task = `编写一个 Python 函数,找出 1 到 n 之间所有的素数。
要求:算法效率尽可能高。`;

  const result = await agent.run(task);

  console.log("=".repeat(60));
  console.log("📊 最终结果:");
  console.log(result);
}

main().catch(console.error);

8.1 运行时会发生什么?

第一轮,模型可能给你一个基于试除法的实现:

text 复制代码
def find_primes(n):
    primes = []
    for num in range(2, n + 1):
        is_prime = True
        for i in range(2, int(num ** 0.5) + 1):
            if num % i == 0:
                is_prime = False
                break
        if is_prime:
            primes.append(num)
    return primes

然后反思阶段指出:

  • 时间复杂度仍然较高
  • 大规模输入下性能不足
  • 建议改用埃拉托斯特尼筛法

接着优化阶段给出更优版本。

8.2 为什么这个例子很典型?

因为它特别适合展示 Reflection 的价值:

  • 第一版不算错,但不够好
  • 审查可以给出非常明确的优化方向
  • 改进效果明显可见

这正是 Reflection 最擅长的场景。


第九章:Reflection 适合什么任务,不适合什么任务?

9.1 很适合的任务

Reflection 特别适合这些任务:

  • 代码生成与代码优化
  • 长文写作与润色
  • 复杂方案设计
  • 数据分析总结
  • PR 评论草稿
  • 高质量答复生成

这些任务有个共同点:

  • 第一版通常不是最终版
  • 结果质量很重要
  • 通过审查往往能发现明确优化空间

9.2 不一定值得开的任务

下面这些情况,Reflection 往往不划算:

  • 只是查一个事实
  • 只是算一个表达式
  • 只是做固定规则判断
  • 对时延极度敏感
  • 第一版通常已经足够好

一个简单判断方法是:

flowchart LR T["当前任务"] --> Q1{"结果质量比速度更重要吗?"} Q1 -- "否" --> R1["不一定要用 Reflection"] Q1 -- "是" --> Q2{"第一版通常有明显优化空间吗?"} Q2 -- "否" --> R2["按需开启"] Q2 -- "是" --> R3["很适合 Reflection"]

所以 Reflection 不是默认选项,而更像一种"高质量模式"。


第十章:Reflection 和 ReAct 可以一起用吗?

答案是:完全可以,而且很常见。

一种非常自然的组合方式是:

  1. 先用 ReAct 获取信息、完成基础任务
  2. 再用 Reflection 优化输出质量

例如:

typescript 复制代码
async function combinedAgent(question: string) {
  const info = await reactAgent.run(question);

  const optimizedAnswer = await reflectionAgent.run(
    `基于以下信息回答问题:${question}\n信息:${info}`
  );

  return optimizedAnswer;
}

你可以把它理解成:

  • ReAct 负责"把事情做出来"
  • Reflection 负责"把结果打磨得更好"

总结

这一篇最重要的,不只是"Reflection 是执行-反思-优化循环",而是你要真正理解它补上的那块能力:

它让智能体第一次具备了"回头检查自己,并根据反馈继续改进"的能力。

你现在应该已经掌握这些关键点:

  1. Reflection 解决的是"一次性输出质量不稳定"的问题。
  2. 它的核心循环是 Execution -> Reflection -> Refinement
  3. 一个从零实现的 ReflectionAgent 至少需要 llm.ts、Memory、三套 Prompt 和终止机制。
  4. 你可以自己从零搭一个最小 ReflectionAgent。

如果说 ReAct 让 Agent 学会了边想边做,那么 Reflection 则让 Agent 开始具备"做完以后回头改进"的能力。


下篇预告

到了这里,我们已经有了两种非常重要的范式:

  • ReAct:边想边做
  • Reflection:做完再改

但如果任务本身就很复杂,比如需要先把大任务拆成多个小步骤,再逐个执行呢?

下一篇,我们会进入另一个经典模式:Plan-and-Solve Agent

也就是让智能体先学会:

flowchart LR G["大任务"] --> P["先规划步骤"] P --> S["逐步执行"] S --> R["汇总结果"]

到那时,Agent 会从"会做单步任务",进一步进化到"能处理复杂多阶段目标"的状态。


参考资料

相关推荐
尽欢i3 小时前
前端响应式布局新宠:vw 和 clamp (),你了解吗?
前端·css
沸点小助手3 小时前
「 AI 整活大赛,正式开擂 & 最近一次面试被问麻了吗」沸点获奖名单公示|本周互动话题上新🎊
前端·人工智能·后端
网络工程小王3 小时前
【大模型基础部署】(学习笔记)
人工智能·深度学习·机器学习
亦暖筑序3 小时前
手写 Spring AI Agent:让大模型自主规划任务,ReAct 模式全流程拆解
java·人工智能·spring
万里鹏程转瞬至3 小时前
论文简读:Embarrassingly Simple Self-Distillation Improves Code Generation
人工智能·深度学习
空中湖3 小时前
大模型修炼秘籍
人工智能·agi
别或许3 小时前
4、高数----一元函数微分学的计算
人工智能·算法·机器学习
攀登的牵牛花4 小时前
我用 Mac 折腾本地生图一整天,实现了本地文生图自由
前端·llm
嵌入式老牛4 小时前
第4课 机器学习的三要素
人工智能·机器学习·优化·模型·学习准则