为什么 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 最核心的结构可以概括成:
这三个阶段分别负责:
- Execution 先产出一个可用版本,不追求完美,但至少要有东西可改。
- Reflection 站在审查者视角找问题、找遗漏、找优化点。
- Refinement 按反馈修正,生成下一版。
3.2 Reflection 和 ReAct 的本质区别
两者都长得像"循环",但关注点完全不同。
| 维度 | ReAct | Reflection |
|---|---|---|
| 核心循环 | Thought -> Action -> Observation | Execution -> Reflection -> Refinement |
| 反馈来源 | 外部工具反馈 | 内部自我批判 |
| 核心目标 | 获取信息、完成动作 | 提升输出质量 |
| 常见场景 | 查询、执行、调用 | 代码、写作、分析、方案优化 |
一句话总结:
- ReAct 更像"边做边找路"
- Reflection 更像"做完以后回头打磨"
3.3 为什么 Reflection 不是无限循环?
如果不给它停下来的条件,它就很容易:
- 一直改不完
- 越改越啰嗦
- 成本越来越高
所以实际实现里必须有终止条件,例如:
- 模型明确说"无需改进"
- 达到最大迭代次数
- 连续几轮没有显著提升
- 已达到目标质量阈值
第四章:Reflection 通常在反思什么?
"反思"两个字听起来很抽象,但在工程实现里,它其实就是一组可枚举的质量维度检查。
最常见的维度包括:
- 正确性 有没有事实错误、逻辑错误、边界遗漏?
- 完整性 有没有漏掉关键条件、关键步骤或关键结论?
- 效率 尤其是代码任务里,是否存在更优算法或更合理的数据结构?
- 可读性 表达是否清楚?结构是否合理?命名是否规范?
- 鲁棒性 有没有异常场景、非法输入、边界情况没处理?
例如,一个代码审查型 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:
initial负责第一次生成结果reflect负责批判性审查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 步:
- 先生成一版初稿
- 把初稿存进 Memory
- 用反思 Prompt 去审查它
- 如果反馈说"无需改进",就结束
- 否则用优化 Prompt 再生成一版
- 继续下一轮
这就是 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 往往不划算:
- 只是查一个事实
- 只是算一个表达式
- 只是做固定规则判断
- 对时延极度敏感
- 第一版通常已经足够好
一个简单判断方法是:
所以 Reflection 不是默认选项,而更像一种"高质量模式"。
第十章:Reflection 和 ReAct 可以一起用吗?
答案是:完全可以,而且很常见。
一种非常自然的组合方式是:
- 先用 ReAct 获取信息、完成基础任务
- 再用 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 是执行-反思-优化循环",而是你要真正理解它补上的那块能力:
它让智能体第一次具备了"回头检查自己,并根据反馈继续改进"的能力。
你现在应该已经掌握这些关键点:
- Reflection 解决的是"一次性输出质量不稳定"的问题。
- 它的核心循环是
Execution -> Reflection -> Refinement。 - 一个从零实现的 ReflectionAgent 至少需要
llm.ts、Memory、三套 Prompt 和终止机制。 - 你可以自己从零搭一个最小 ReflectionAgent。
如果说 ReAct 让 Agent 学会了边想边做,那么 Reflection 则让 Agent 开始具备"做完以后回头改进"的能力。
下篇预告
到了这里,我们已经有了两种非常重要的范式:
- ReAct:边想边做
- Reflection:做完再改
但如果任务本身就很复杂,比如需要先把大任务拆成多个小步骤,再逐个执行呢?
下一篇,我们会进入另一个经典模式:Plan-and-Solve Agent。
也就是让智能体先学会:
到那时,Agent 会从"会做单步任务",进一步进化到"能处理复杂多阶段目标"的状态。