智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection

智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection

智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection

一句话总览:Agent 的核心不是让模型更会聊天,而是为模型设计三种控制循环:边想边做、先计划再做、做完再改。

三种 Agent 范式总览

这篇文章基于 Datawhale Hello-Agents 第四章整理,但不按原文目录复述。原章用 Python 从零实现 ReAct、Plan-and-Solve 和 Reflection;

快速结论

问题结论
三种范式分别解决什么问题

ReAct 解决外部信息不确定,Plan-and-Solve 解决复杂任务拆解,Reflection 解决初稿质量不够

什么时候优先用 ReAct需要搜索、查数据库、调用 API、根据工具反馈调整下一步
什么时候优先用 Plan-and-Solve任务目标稳定、步骤清楚、多步推理明显
什么时候优先用 Reflection代码、报告、方案、研究分析等高质量输出
工程上怎么组合先 Plan 拆任务,中间 ReAct 调工具,最后 Reflection 做质检
最大风险控制循环越多,成本、延迟、不可控性越高

核心地图

范式一句话核心循环控制权主要来自
ReAct边想边查Thought -> Action -> Observation每一轮环境反馈
Plan-and-Solve先画路线再走Plan -> Execute -> Summarize初始计划
Reflection先做初稿再评审Execute -> Reflect -> Refine事后反馈
flowchart LR
User["用户"] --> LLM["LLM"]
LLM --> Decide["判断任务特征"]
Decide -->|需要外部反馈| React["ReAct"]
Decide -->|步骤稳定| Plan["Plan-and-Solve"]
Decide -->|质量要求高| Reflection["Reflection"]
React --> Draft["候选结果"]
Plan --> Draft
Draft --> Reflection
Reflection --> Final["更可靠的结果"]
Final --> User

先准备底座:模型客户端和工具执行器

一句话:三种 Agent Loop 的差异在控制流程,底层都需要一个 LLM 客户端和一套工具调用接口。

解决的问题:如果每个范式都自己写模型调用和工具注册,后面会很快失控。先抽底座,可以让 ReAct、Plan-and-Solve、Reflection 复用同一套能力。

适合场景:所有 Agent 原型都应该先做这一步,哪怕只是 demo。

核心机制:LLM 客户端只负责发送消息和返回文本;工具执行器只负责注册、描述和调用工具;Agent Loop 负责决定什么时候调用模型、什么时候调用工具。

最小实现:

Terminal window
npm init -y
npm install dotenv

.env

Terminal window
LLM_API_KEY="YOUR_API_KEY"
LLM_MODEL_ID="YOUR_MODEL"
LLM_BASE_URL="https://api.openai.com/v1"
SERPAPI_API_KEY="YOUR_SERPAPI_API_KEY"
import 'dotenv/config';
export class HelloAgentsLLM {
constructor({
model = process.env.LLM_MODEL_ID,
apiKey = process.env.LLM_API_KEY,
baseUrl = process.env.LLM_BASE_URL,
timeoutMs = Number(process.env.LLM_TIMEOUT_MS || 60_000)
} = {}) {
if (!model || !apiKey || !baseUrl) {
throw new Error('LLM_MODEL_ID、LLM_API_KEY、LLM_BASE_URL 必须配置。');
}
this.model = model;
this.apiKey = apiKey;
this.baseUrl = baseUrl.replace(/\/$/, '');
this.timeoutMs = timeoutMs;
}
async think(messages, { temperature = 0 } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const response = await fetch(`${this.baseUrl}/chat/completions`, {
method: 'POST',
signal: controller.signal,
headers: {
'content-type': 'application/json',
authorization: `Bearer ${this.apiKey}`
},
body: JSON.stringify({
model: this.model,
messages,
temperature
})
});
if (!response.ok) {
throw new Error(`LLM API failed: ${response.status} ${await response.text()}`);
}
const data = await response.json();
return data.choices?.[0]?.message?.content?.trim() ?? '';
} finally {
clearTimeout(timer);
}
}
}
export class ToolExecutor {
constructor() {
this.tools = new Map();
}
registerTool(name, description, fn) {
this.tools.set(name, { description, fn });
}
getAvailableToolsText() {
return [...this.tools.entries()]
.map(([name, tool]) => `- ${name}: ${tool.description}`)
.join('\n');
}
async execute(name, input) {
const tool = this.tools.get(name)?.fn;
if (!tool) return `错误:未找到工具 ${name}`;
return await tool(input);
}
}

常见坑:

处理方式
LLM 客户端里混入业务循环保持客户端只做模型调用
工具描述太含糊描述清楚何时用、输入是什么、返回什么
所有工具一次性塞给模型工具多时先做检索或路由

什么时候不要展开这一步:一次性脚本可以直接调用模型;但只要准备复用或组合多个 Agent Loop,就应该抽象底座。

ReAct:适合需要外部反馈的任务

一句话:ReAct 让模型每一步都先想清楚,再调用工具,然后用工具结果修正下一步。

解决的问题:只让模型思考,它可能凭空编;只让模型行动,它可能乱调工具。ReAct 把推理和行动交替起来,让外部世界不断校正模型。

适合场景:

场景为什么适合
查询实时信息模型自身知识可能过期
调用 API / 数据库每次返回会影响下一步
搜索和总结需要边查边缩小问题
工具链任务不同工具之间存在依赖

核心机制:

flowchart LR
User["用户"] --> LLM["LLM"]
LLM --> T["Thought"]
T --> A["Action"]
A --> O["Observation"]
O --> LLM
A --> F["Finish"]
F --> User

模型每一轮输出固定格式:

Thought: 我现在知道什么,还缺什么
Action: Search[要搜索的问题]

如果信息足够:

Thought: 信息已经足够
Action: Finish[最终答案]

最小实现:

const REACT_PROMPT = `
你是一个可以调用外部工具的智能助手。
可用工具:
{tools}
请严格按下面格式输出:
Thought: 你的思考
Action: 工具名[工具输入] 或 Finish[最终答案]
问题:
{question}
历史:
{history}
`;
function parseAction(text) {
const match = text.match(/Action:\s*([A-Za-z_][\w]*)\[(.*)\]/s);
if (!match) return null;
return {
toolName: match[1],
toolInput: match[2].trim()
};
}
export class ReActAgent {
constructor({ llm, toolExecutor, maxSteps = 5 }) {
this.llm = llm;
this.toolExecutor = toolExecutor;
this.maxSteps = maxSteps;
}
async run(question) {
let history = '';
for (let step = 1; step <= this.maxSteps; step += 1) {
const prompt = REACT_PROMPT.replace('{tools}', this.toolExecutor.getAvailableToolsText())
.replace('{question}', question)
.replace('{history}', history || '');
const output = await this.llm.think([{ role: 'user', content: prompt }]);
const action = parseAction(output);
if (!action) {
history += `\n模型输出无法解析:\n${output}\n`;
continue;
}
if (action.toolName === 'Finish') {
return action.toolInput;
}
const observation = await this.toolExecutor.execute(action.toolName, action.toolInput);
history += `\n${output}\nObservation: ${observation}\n`;
}
return '达到最大步骤数,未得到稳定答案。';
}
}

常见坑:

说明工程处理
输出解析脆弱

模型不一定遵守 Action[…]

用 JSON Schema、函数调用或结构化输出
工具选择错误模型可能搜索不该搜的问题增强工具描述,加入工具路由
死循环一直搜索或重复调用设置最大步数,检测重复动作
上下文膨胀Observation 越积越多对历史做摘要,只保留关键证据

什么时候不要用:任务不需要外部信息、步骤很清楚、答案可以一次性推理出来时,ReAct 可能只是增加调用成本。

Plan-and-Solve:适合路径清楚的复杂任务

一句话:Plan-and-Solve 先生成完整路线,再按路线一步步执行。

解决的问题:复杂任务如果直接让模型回答,很容易漏步骤。Plan-and-Solve 先把任务拆开,让模型从一次大跳跃变成多次小推进。

适合场景:

场景为什么适合
多步数学题步骤稳定,推理链清楚
报告大纲生成先定结构,再填内容
稳定流程任务不需要频繁依赖外部反馈
代码脚手架生成可先规划模块,再逐个实现

核心机制:

flowchart LR
User["用户"] --> LLM["LLM"]
LLM --> P["Plan"]
P --> S1["Step 1"]
S1 --> S2["Step 2"]
S2 --> S3["Step 3"]
S3 --> A["Answer"]
A --> User

最小实现:

const PLAN_PROMPT = `
请把用户任务拆成可以依次执行的步骤。
只输出 JSON,不要输出解释。
JSON 格式:
{
"steps": [
{ "id": 1, "goal": "这一步要完成什么", "instruction": "具体执行说明" }
]
}
用户任务:
{task}
`;
function extractJson(text) {
const start = text.indexOf('{');
const end = text.lastIndexOf('}');
if (start === -1 || end === -1 || end <= start) {
throw new Error('模型没有返回 JSON 对象。');
}
return JSON.parse(text.slice(start, end + 1));
}
export class PlanAndSolveAgent {
constructor({ llm }) {
this.llm = llm;
}
async plan(task) {
const prompt = PLAN_PROMPT.replace('{task}', task);
const output = await this.llm.think([{ role: 'user', content: prompt }]);
return extractJson(output).steps ?? [];
}
async execute(task, steps) {
const results = [];
for (const step of steps) {
const prompt = `
原始任务:
${task}
当前步骤:
${JSON.stringify(step, null, 2)}
之前结果:
${results.join('\n\n') || ''}
请完成当前步骤,只输出本步骤结果。
`;
const result = await this.llm.think([{ role: 'user', content: prompt }]);
results.push(`Step ${step.id}: ${result}`);
}
return results;
}
async run(task) {
const steps = await this.plan(task);
const results = await this.execute(task, steps);
return await this.llm.think([
{
role: 'user',
content: `请根据以下步骤结果汇总最终答案:\n\n${results.join('\n\n')}`
}
]);
}
}

常见坑:

说明工程处理
第一版计划错了后面执行越认真越偏加检查点和动态重规划
计划太粗每一步仍然不可执行要求每步包含目标、输入、输出
计划太细成本变高,流程拖长限制步骤数,合并低价值步骤

动态重规划更像真实系统:

flowchart LR
User["用户"] --> LLM["LLM"]
LLM --> Plan["生成计划"]
Plan --> Execute["执行一步"]
Execute --> Check["检查结果"]
Check -->|正常| Execute
Check -->|失败或偏离| LLM
LLM --> Replan["重规划剩余步骤"]
Replan --> Execute
Check -->|完成| Final["最终答案"]
Final --> User

什么时候不要用:任务高度依赖实时反馈、用户目标不断变化、工具结果会强烈改变后续路径时,单纯的静态计划不够。

Reflection:适合高质量输出

一句话:Reflection 让模型先交初稿,再切换成评审员找问题,最后根据反馈修订。

Reflection 循环工作台

解决的问题:模型一次生成的结果通常只是合格初稿。代码可能能跑但不高效,报告可能完整但不犀利,方案可能合理但漏掉风险。Reflection 把一次性生成变成迭代优化。

适合场景:

场景为什么适合
代码生成可以检查复杂度、边界条件、可读性
技术报告可以检查逻辑、遗漏和证据
研究分析可以从多个维度反驳初稿
高风险决策可以在输出前加一道质量门禁

核心机制:

flowchart LR
User["用户"] --> LLM["LLM"]
LLM --> Draft["Execution"]
Draft --> Review["Reflection"]
Review --> Refine["Refinement"]
Refine --> Review
Review -->|无需改进| Final["Final"]
Final --> User

最小实现:

export class Memory {
constructor() {
this.records = [];
}
addRecord(type, content) {
this.records.push({ type, content });
}
getLastExecution() {
return [...this.records].reverse().find((record) => record.type === 'execution')?.content ?? '';
}
}
const INITIAL_PROMPT = `
你是一位资深 Node.js 工程师。请根据要求编写 JavaScript 函数。
请只输出代码。
要求:
{task}
`;
const REFLECT_PROMPT = `
你是一位严格的代码评审专家。
请审查下面的 JavaScript 代码,重点找出算法效率、边界条件和可读性问题。
原始任务:
{task}
代码:
\`\`\`js
{code}
\`\`\`
如果有改进空间,请给出具体反馈。
如果已经足够好,请回答:无需改进。
`;
const REFINE_PROMPT = `
请根据评审反馈优化代码。
原始任务:
{task}
上一版代码:
\`\`\`js
{lastCode}
\`\`\`
评审反馈:
{feedback}
请只输出优化后的 JavaScript 代码。
`;
export class ReflectionAgent {
constructor({ llm, maxIterations = 3 }) {
this.llm = llm;
this.maxIterations = maxIterations;
this.memory = new Memory();
}
async run(task) {
const firstDraft = await this.llm.think([
{
role: 'user',
content: INITIAL_PROMPT.replace('{task}', task)
}
]);
this.memory.addRecord('execution', firstDraft);
for (let i = 0; i < this.maxIterations; i += 1) {
const lastCode = this.memory.getLastExecution();
const feedback = await this.llm.think([
{
role: 'user',
content: REFLECT_PROMPT.replace('{task}', task).replace('{code}', lastCode)
}
]);
this.memory.addRecord('reflection', feedback);
if (feedback.includes('无需改进')) break;
const nextDraft = await this.llm.think([
{
role: 'user',
content: REFINE_PROMPT.replace('{task}', task)
.replace('{lastCode}', lastCode)
.replace('{feedback}', feedback)
}
]);
this.memory.addRecord('execution', nextDraft);
}
return this.memory.getLastExecution();
}
}

案例:找出 1n 的所有素数。初版可能逐个试除,复杂度接近 O(n * sqrt(n));Reflection 如果专注算法效率,就会推动它改成埃拉托斯特尼筛法。

export function findPrimes(n) {
if (!Number.isInteger(n) || n < 2) return [];
const isPrime = Array(n + 1).fill(true);
isPrime[0] = false;
isPrime[1] = false;
for (let p = 2; p * p <= n; p += 1) {
if (!isPrime[p]) continue;
for (let current = p * p; current <= n; current += p) {
isPrime[current] = false;
}
}
return isPrime.map((value, index) => (value ? index : null)).filter((value) => value !== null);
}

常见坑:

说明工程处理
评审太空泛只说“可以更好”给评审明确维度,如复杂度、边界、事实性
终止条件粗糙只靠“无需改进”加分数、测试、阈值或人工 gate
成本过高每轮至少多一次评审和一次修订只给高价值任务启用

什么时候不要用:实时问答、低风险闲聊、一次性简单任务,不值得为 Reflection 付出额外延迟和成本。

组合策略:真实 Agent 很少只用一种循环

最实用的默认组合是:

任务类型默认组合原因
实时搜索问答ReAct需要外部观察
复杂报告Plan-and-Solve + Reflection先定结构,再做质检
多 API 工作流Plan-and-Solve + ReAct先规划接口顺序,再按反馈执行
代码生成Plan-and-Solve + Reflection先拆模块,再审查优化
高风险业务决策ReAct + Reflection + 人工 gate需要证据、反思和最终审批

一个工程化架构可以这样组织:

flowchart TD
User["用户"] --> Input["用户输入"]
Input --> LLM["LLM"]
LLM --> Classifier["任务分类"]
Classifier -->|外部信息| React["ReAct 工具循环"]
Classifier -->|复杂内部任务| Planner["Plan-and-Solve"]
Classifier -->|简单任务| Direct["直接回答"]
React --> Draft["候选答案"]
Planner --> Draft
Direct --> Draft
Draft --> Gate["质量门禁"]
Gate -->|低风险| Output["输出"]
Gate -->|高价值或高风险| Reflection["Reflection 反思优化"]
Reflection --> Output
Output --> User

取舍与风险

Reflection 成本收益权衡

这三种范式不是越复杂越好。它们本质上是在不同位置给模型重新判断的机会。

范式重新判断发生在哪里代价
ReAct每次工具返回后工具调用多,上下文增长快
Plan-and-Solve初始计划阶段计划错了会系统性偏离
Reflection初稿生成之后延迟和模型调用成本增加

我的判断是:Agent 架构的核心问题不是模型够不够聪明,而是系统允许它在什么时候重新判断。能重新判断的地方越多,系统越灵活,但也越贵、越慢、越难控。

结论

ReAct 解决的是:我不知道外部世界,所以我要边查边想。

Plan-and-Solve 解决的是:任务太复杂,所以我要先拆路线。

Reflection 解决的是:初稿不够好,所以我要让系统学会改。

理解这三种循环,再去看 LangChain、LlamaIndex、AutoGen、CrewAI 或内部 Agent 平台,就不会只看到框架 API,而能看到它们背后的控制结构。