上一篇我们让 Agent 学会了"反问",在执行前搞清楚需求。但搞清楚需求之后呢?Agent 该怎么一步步执行?遇到意外怎么办?执行完了怎么保证质量?今天聊三种经典 Agent 设计模式——ReAct、Plan-Execute、Reflection——它们分别解决"边做边想"、"先规划后执行"、"做完自我检查"三个问题,而且在真实项目中往往需要组合使用。
一、为什么需要设计模式
没有设计模式的 Agent 是什么样的?
用户:帮我分析一下这个商机的风险等级
Agent:
→ 直接调用 LLM 分析
→ 输出:风险等级为中
→ 完成
问题:
├── 没有查询数据,直接"编"了结论
├── 不知道"中"是怎么判断的
├── 没有验证结论是否合理
└── 用户追问"为什么",Agent 只能重新"编"
设计模式解决的核心问题:如何让 Agent 的推理过程更可靠、更可追溯、更可纠错。
Agent 设计模式要解决的三个问题
│
├── 1. 推理可靠性:Agent 不能"编",每步推理要有依据
│ └── ReAct:边推理边行动,每步都有真实数据支撑
│
├── 2. 执行可控性:Agent 不能"跑偏",要按计划执行
│ └── Plan-Execute:先规划步骤,再逐步执行
│
└── 3. 输出质量:Agent 不能"糊弄",要自我检查
└── Reflection:执行后反思,迭代改进
二、ReAct 模式:推理 + 行动
2.1 核心思想
ReAct = Reasoning + Acting。Agent 在每一步都先推理(Thought),再行动(Action),然后观察结果(Observation),形成循环。
ReAct 循环
│
├── Thought:根据当前信息推理下一步该做什么
├── Action:执行一个工具调用
├── Observation:观察工具返回的结果
│
└── 重复上述步骤,直到得出最终答案
2.2 一个完整的 ReAct 示例
用户:这个商机值得跟进吗?
Thought 1:我需要先查询商机的详细信息
Action 1:queryOpportunity(id="OPP-2024-001")
Observation 1:客户=XX科技,金额=50万,行业=电商,阶段=需求确认
Thought 2:有了基本信息,现在需要评估风险。先查客户历史合作记录
Action 2:queryCustomerHistory(customer="XX科技")
Observation 2:历史合作3次,按时回款率100%,无违约记录
Thought 3:客户信用不错。再查行业趋势
Action 3:queryIndustryTrend(industry="电商")
Observation 3:电商行业增长15%,竞争激烈,但客户是头部企业
Thought 4:综合信息:客户信用好、行业增长、金额适中,但有竞争风险
Final Answer:建议跟进,风险等级为"中低"。关键依据:客户历史回款100%,
行业增长15%,但需关注竞品动态
对比无模式的直接回答:
| 对比项 | 直接回答 | ReAct 模式 |
|---|---|---|
| 数据来源 | LLM 编造 | 工具调用真实数据 |
| 推理可追溯 | 不可追溯 | 每步 Thought 可追溯 |
| 可纠错 | 无法纠错 | 某步出错可在后续修正 |
| Token 消耗 | 低 | 高(多次调用) |
| 延迟 | 快 | 慢(多轮交互) |
2.3 Spring AI 实现 ReAct
public class ReActAgent {
private final ChatClient chatClient;
private final List<Tool> tools;
private final int maxIterations;
public ReActAgent(ChatClient chatClient, List<Tool> tools, int maxIterations) {
this.chatClient = chatClient;
this.tools = tools;
this.maxIterations = maxIterations;
}
public String execute(String userRequest) {
String context = "用户请求:" + userRequest + "\n\n";
for (int i = 0; i < maxIterations; i++) {
// Step 1: Thought — 推理下一步
var thought = chatClient.prompt()
.user(context + "请推理下一步该做什么,或给出最终答案。" +
"如果信息足够,直接输出 Final Answer: ...")
.call()
.content();
context += "Thought: " + thought + "\n";
// Step 2: 检查是否已有最终答案
if (thought.contains("Final Answer:")) {
return thought.substring(thought.indexOf("Final Answer:") + 14);
}
// Step 3: Action — 执行工具调用
var action = parseAction(thought);
if (action != null) {
String observation = executeTool(action);
context += "Action: " + action + "\n";
context += "Observation: " + observation + "\n\n";
}
}
return "达到最大迭代次数,未能得出最终答案";
}
}
2.4 ReAct 的关键限制
ReAct 的局限
│
├── 1. 步骤数不可控
│ ├── 简单问题可能1步就够,复杂问题需要10+步
│ └── 设置 maxIterations 是必要的,但要平衡效率和完整性
│
├── 2. 推理可能跑偏
│ ├── LLM 可能在 Thought 中做出错误推理
│ └── 后续步骤基于错误推理继续,越走越偏
│
├── 3. 工具选择可能错误
│ ├── LLM 可能选择不合适的工具
│ └── 导致 Observation 无用,浪费步骤
│
└── 4. Token 消耗大
├── 每步都需要完整的上下文
└── 步骤越多,上下文越长,Token 越贵
三、Plan-Execute 模式:先规划后执行
3.1 核心思想
ReAct 是"边想边做",Plan-Execute 是"先想好再做"。把规划和执行分为两个独立阶段:
Plan-Execute 流程
│
├── Phase 1: Plan(规划阶段)
│ ├── 根据任务生成执行计划
│ ├── 计划包含有序的步骤列表
│ └── 每个步骤有明确的输入、工具、预期输出
│
├── Phase 2: Execute(执行阶段)
│ ├── 按计划逐步执行
│ ├── 每步执行后记录结果
│ └── 如果某步失败,标记异常
│
└── Phase 3: 总结(可选)
├── 汇总所有步骤结果
└── 生成最终输出
3.2 Plan-Execute 示例
用户:帮我分析这个商机的风险等级
Phase 1: Plan
├── Step 1: 查询商机详情 → queryOpportunity
├── Step 2: 查询客户历史 → queryCustomerHistory
├── Step 3: 查询行业趋势 → queryIndustryTrend
├── Step 4: 综合评估风险 → LLM 分析
└── Step 5: 生成风险报告 → 输出
Phase 2: Execute
├── Step 1: ✅ → {客户=XX科技, 金额=50万, ...}
├── Step 2: ✅ → {回款率100%, 合作3次, ...}
├── Step 3: ✅ → {增长15%, 竞争激烈, ...}
├── Step 4: ✅ → {风险等级=中低, 依据=...}
└── Step 5: ✅ → 风险报告已生成
3.3 代码实现
// 定义计划步骤
public record PlanStep(
int order,
String description,
String toolName,
Map<String, Object> params,
String expectedOutput
) {}
// 定义执行计划
public record ExecutionPlan(
String objective,
List<PlanStep> steps
) {}
// Plan-Execute Agent
public class PlanExecuteAgent {
private final ChatClient chatClient;
private final Map<String, Tool> toolRegistry;
// Phase 1: 生成计划
public ExecutionPlan createPlan(String userRequest) {
return chatClient.prompt()
.user(u -> u.text("""
根据用户请求,生成执行计划。
可用工具:{tools}
用户请求:{request}
输出 JSON 格式的执行计划。
""")
.param("tools", getToolDescriptions())
.param("request", userRequest))
.call()
.entity(ExecutionPlan.class);
}
// Phase 2: 执行计划
public Map<Integer, String> execute(ExecutionPlan plan) {
Map<Integer, String> results = new LinkedHashMap<>();
for (PlanStep step : plan.steps()) {
try {
Tool tool = toolRegistry.get(step.toolName());
String result = tool.execute(step.params());
results.put(step.order(), result);
} catch (Exception e) {
results.put(step.order(), "ERROR: " + e.getMessage());
// 可以选择终止或继续
}
}
return results;
}
// Phase 3: 总结
public String summarize(String userRequest, Map<Integer, String> results) {
return chatClient.prompt()
.user(u -> u.text("""
用户请求:{request}
执行结果:
{results}
请根据以上结果生成最终回答。
""")
.param("request", userRequest)
.param("results", formatResults(results)))
.call()
.content();
}
}
3.4 Plan-Execute vs ReAct
| 对比项 | ReAct | Plan-Execute |
|---|---|---|
| 规划方式 | 边做边想 | 先规划后执行 |
| 灵活性 | 高(每步动态决策) | 低(计划确定后较固定) |
| 可预测性 | 低(不知几步完成) | 高(计划可见) |
| 错误恢复 | 自然(下一步自动修正) | 需要额外逻辑 |
| 适用场景 | 探索性任务 | 步骤明确的任务 |
| 效率 | 可能多步试错 | 一次规划,高效执行 |
四、动态 Plan-Execute:结合两者优势
纯 Plan-Execute 太死板,纯 ReAct 太随机。最佳实践是动态 Plan-Execute:
动态 Plan-Execute
│
├── 1. 先生成初始计划(Plan)
│
├── 2. 逐步执行(Execute)
│ ├── 每步执行后检查结果
│ ├── 如果结果符合预期 → 继续
│ └── 如果结果不符合 → 重新规划(Re-Plan)
│
└── 3. 动态调整
├── 如果发现需要额外的步骤 → 追加到计划
└── 如果某些步骤不再需要 → 从计划移除
public class DynamicPlanExecuteAgent {
private final ChatClient chatClient;
private final Map<String, Tool> toolRegistry;
public String execute(String userRequest) {
// 1. 初始规划
ExecutionPlan plan = createPlan(userRequest);
Map<Integer, String> results = new LinkedHashMap<>();
int currentStep = 0;
while (currentStep < plan.steps().size()) {
PlanStep step = plan.steps().get(currentStep);
// 2. 执行当前步骤
Tool tool = toolRegistry.get(step.toolName());
String result;
try {
result = tool.execute(step.params());
} catch (Exception e) {
result = "ERROR: " + e.getMessage();
}
results.put(step.order(), result);
// 3. 检查是否需要重新规划
if (result.startsWith("ERROR:") || needsRePlan(results)) {
plan = rePlan(userRequest, plan, results);
// rePlan 可能追加/修改/删除步骤
continue; // 重新执行(可能从新步骤开始)
}
currentStep++;
}
// 4. 总结
return summarize(userRequest, results);
}
private ExecutionPlan rePlan(String request, ExecutionPlan oldPlan,
Map<Integer, String> results) {
return chatClient.prompt()
.user(u -> u.text("""
原始请求:{request}
原计划:{plan}
已执行结果:{results}
部分步骤执行失败。请根据已有结果重新规划剩余步骤。
保留已成功完成的步骤,只调整未完成的部分。
""")
.param("request", request)
.param("plan", oldPlan.toString())
.param("results", results.toString()))
.call()
.entity(ExecutionPlan.class);
}
private boolean needsRePlan(Map<Integer, String> results) {
return results.values().stream()
.anyMatch(r -> r.startsWith("ERROR:"));
}
}
五、Reflection 模式:自我反思与改进
5.1 核心思想
Reflection 让 Agent 在执行完成后,回顾自己的输出,评估质量并迭代改进。
Reflection 流程
│
├── 1. Generate:生成初始输出
│
├── 2. Critique:自我批评
│ ├── 输出是否完整?
│ ├── 是否有逻辑错误?
│ ├── 是否遗漏关键信息?
│ └── 是否可以更简洁/更准确?
│
├── 3. Revise:根据批评改进
│ └── 生成改进版本
│
└── 4. 重复 2-3,直到质量达标或达到最大迭代次数
5.2 示例
用户:写一个商机风险评估报告
Generate(第 1 版):
"该商机风险等级为中。建议跟进。"
Critique(自我批评):
├── 报告太简略,缺少具体依据
├── 没有说明"中"的判断标准
├── 没有给出具体的风险因素
└── 没有建议应对措施
Revise(第 2 版):
"风险等级:中低。
依据:1) 客户历史回款率100%;2) 行业增长15%
风险因素:竞品可能压价
建议:1) 加快推进速度;2) 提供差异化方案"
Critique(第 2 次批评):
├── 有了具体依据,好多了
├── 但缺少量化评分
└── 建议部分可以更具体
Revise(第 3 版 — 最终):
"风险评分:72/100(中低)
信用维度:85/100 — 历史回款率100%,合作3次0违约
市场维度:70/100 — 行业增长15%,但竞争激烈
金额维度:65/100 — 50万中等金额,利润率需确认
关键风险:竞品压价(概率60%,影响高)
应对建议:1) 3天内完成技术方案;2) 准备差异化演示;
3) 预留10%价格弹性空间"
5.3 代码实现
public class ReflectionAgent {
private final ChatClient chatClient;
private final int maxIterations;
public String execute(String userRequest) {
// Step 1: Generate
String output = generate(userRequest);
for (int i = 0; i < maxIterations; i++) {
// Step 2: Critique
String critique = critique(userRequest, output);
// 检查是否已足够好
if (critique.contains("无改进空间") || critique.contains("已达标")) {
break;
}
// Step 3: Revise
output = revise(userRequest, output, critique);
}
return output;
}
private String generate(String request) {
return chatClient.prompt()
.user(request)
.call()
.content();
}
private String critique(String request, String output) {
return chatClient.prompt()
.user(u -> u.text("""
请评估以下输出质量:
原始请求:{request}
当前输出:{output}
评估标准:
1. 完整性:是否覆盖了请求的所有方面?
2. 准确性:信息是否正确、有依据?
3. 清晰度:表达是否清晰、有条理?
4. 实用性:对读者是否有实际价值?
如果质量已达标,回复"已达标"。
否则,指出具体需要改进的地方。
""")
.param("request", request)
.param("output", output))
.call()
.content();
}
private String revise(String request, String currentOutput, String critique) {
return chatClient.prompt()
.user(u -> u.text("""
请根据批评意见改进输出:
原始请求:{request}
当前输出:{currentOutput}
批评意见:{critique}
请生成改进版本。保留好的部分,只改进批评中提到的问题。
""")
.param("request", request)
.param("currentOutput", currentOutput)
.param("critique", critique))
.call()
.content();
}
}
5.4 Reflection 的关键参数
Reflection 参数调优
│
├── 迭代次数
│ ├── 1-2 次:轻微改进,适合简单任务
│ ├── 3-5 次:显著改进,适合复杂任务
│ └── >5 次:收益递减,不建议
│
├── 评估标准
│ ├── 通用标准:完整性、准确性、清晰度
│ └── 定制标准:根据具体任务定义(如代码正确性、文档规范性)
│
└── 停止条件
├── Critique 表示"已达标"
├── 达到最大迭代次数
└── 连续两次输出差异小于阈值(改进幅度小)
六、三种模式的组合使用
单一模式都有局限,真实项目中往往需要组合。
6.1 组合架构
完整 Agent 架构
│
├── 1. Grill Me(执行前)
│ └── 澄清需求,确认边界
│
├── 2. Plan-Execute(执行中)
│ ├── 先规划步骤
│ ├── 按计划逐步执行
│ └── 遇到异常动态调整
│
├── 3. ReAct(执行中)
│ ├── 每个步骤内部用 ReAct 模式
│ ├── Thought → Action → Observation
│ └── 确保每步推理有数据支撑
│
└── 4. Reflection(执行后)
├── 自我评估输出质量
├── 迭代改进直到达标
└── 最终输出
6.2 组合代码框架
public class CombinedAgent {
private final ChatClient chatClient;
private final Map<String, Tool> toolRegistry;
public Response execute(String userRequest) {
// 1. Grill Me — 澄清需求
var grillResult = grill(userRequest);
if (grillResult.needGrill()) {
return Response.questions(grillResult.questions());
}
String clarifiedRequest = grillResult.clarifiedRequest();
// 2. Plan-Execute — 规划并执行
var plan = createPlan(clarifiedRequest);
var results = executePlan(plan);
// 3. ReAct — 处理计划中的复杂步骤
// (已集成在 executePlan 内部)
// 4. Reflection — 自我评估
String output = summarize(clarifiedRequest, results);
output = reflectAndImprove(clarifiedRequest, output);
return Response.content(output);
}
private String reflectAndImprove(String request, String output) {
for (int i = 0; i < 3; i++) {
String critique = critique(request, output);
if (critique.contains("已达标")) break;
output = revise(request, output, critique);
}
return output;
}
}
6.3 场景推荐
| 任务类型 | 推荐组合 | 原因 |
|---|---|---|
| 简单查询 | 直接执行 | 不需要复杂模式 |
| 数据分析报告 | Plan-Execute + Reflection | 步骤明确 + 需要质量保证 |
| 代码生成 | ReAct + Reflection | 需要试错 + 需要质量检查 |
| 方案设计 | Grill Me + Plan-Execute + Reflection | 需求模糊 + 步骤多 + 质量敏感 |
| 多步工作流 | Grill Me + Dynamic Plan-Execute | 需求模糊 + 可能遇异常 |
七、模式选择的决策树
选择哪种模式?
│
├── 任务是否模糊?
│ ├── 是 → 先 Grill Me 澄清
│ └── 否 → 继续
│
├── 步骤是否明确?
│ ├── 是 → Plan-Execute
│ │ └── 是否需要动态调整?
│ │ ├── 是 → Dynamic Plan-Execute
│ │ └── 否 → 纯 Plan-Execute
│ └── 否 → ReAct
│
├── 输出质量是否关键?
│ ├── 是 → 加 Reflection
│ └── 否 → 不加
│
└── 是否涉及不可逆操作?
├── 是 → 每步执行前确认
└── 否 → 正常执行
八、成本与性能对比
| 模式 | LLM 调用次数 | 延迟 | Token 消耗 | 适用复杂度 |
|---|---|---|---|---|
| 直接执行 | 1 | 1-2s | 基准 | 低 |
| ReAct | 3-10 | 5-20s | 3-10x | 中-高 |
| Plan-Execute | 2+N(N=步骤数) | 5-15s | 2-5x | 中 |
| Dynamic Plan-Execute | 2+N+M(M=重规划次数) | 8-25s | 3-8x | 高 |
| Reflection | 1+2K(K=迭代次数) | 3-10s | 2-5x | 中 |
| 组合模式 | 视组合而定 | 视组合而定 | 视组合而定 | 高 |
成本控制建议:
成本控制策略
│
├── 1. 根据任务复杂度选模式
│ ├── 简单任务用直接执行
│ └── 不要为了"高级"而用复杂模式
│
├── 2. 设置合理的上限
│ ├── ReAct: maxIterations ≤ 10
│ ├── Reflection: maxIterations ≤ 3
│ └── Plan steps ≤ 10
│
├── 3. 用快速模型做中间步骤
│ ├── 规划、批评用快速模型(如 gpt-4o-mini)
│ └── 最终输出用高质量模型
│
└── 4. 缓存中间结果
├── 相同的工具调用不重复执行
└── Reflection 每轮只改需要改的部分
九、最佳实践 Checklist
| # | 实践 | 说明 |
|---|---|---|
| 1 | 根据任务复杂度选择模式 | 简单任务不搞复杂 |
| 2 | ReAct 必须设 maxIterations | 防止无限循环 |
| 3 | Plan-Execute 先规划再执行 | 避免走一步看一步 |
| 4 | Dynamic Plan-Execute 更实用 | 纯计划太死板,动态调整更灵活 |
| 5 | Reflection 迭代不超过 3 次 | 收益递减,3 次足够 |
| 6 | 组合使用而非单独使用 | 每种模式解决不同阶段的问题 |
| 7 | 用快速模型做中间推理 | 规划、批评用便宜模型 |
| 8 | 监控每步执行结果 | 记录日志,便于调试 |
| 9 | 不可逆操作前要确认 | 删除、发送等需要用户确认 |
| 10 | 缓存工具调用结果 | 避免重复调用相同工具 |
下一篇我们聊 Multi-Agent 协作编排:当一个 Agent 搞不定时,如何让多个 Agent 分工协作——主管 Agent、专家 Agent、评审 Agent 各司其职,组合完成复杂任务。
本文是 AI Agent 开发实战系列第 10 篇,系列目录:
- AI Agent 核心概念与架构
- 三大基石之 LLM 调用与 Prompt 工程
- 三大基石之记忆系统
- 三大基石之工具调用
- Java 生态 Agent 框架横评
- 用 Spring AI 搭建第一个 Agent
- Harness Engineering 与约束管理
- 输出 Schema 约束与结构化输出
- Grill Me 反问式规划
- 本文:Agent 设计模式(ReAct / Plan-Execute / Reflection)