上一讲介绍了 ReAct。本讲继续阅读 Plan-and-Execute:先生成计划,再按步骤执行,由协调器连接规划器与执行器。

原文配图:Plan-and-Execute 模式。
以下沿用原文对两种模式的比较,未做性能或适用性复测:
| 对比维度 | ReAct Agent | Plan-and-Execute Agent |
|---|---|---|
| 思考模式 | 单步思考与行动循环 | 先规划,再执行 |
| 执行流程 | Thought → Action → Observation(循环) | Plan → Execute Step 1 → Execute Step 2 → … |
| 规划范围 | 规划下一步 | 预先规划完整执行路径 |
| LLM 调用 | 每次循环需要 LLM | 规划时一次,执行时可能多次 |
原文归纳的优势
- 效率更高:减少重复的"思考"步骤
- 可预测性:预先知道完整执行路径
- 易于调试:每个步骤都有明确的状态
- 适合复杂任务:结构化处理多步骤流程
原文列举的适用场景
- ✅ 数据处理的ETL流程
- ✅ 自动化工作流
- ✅ 需要审计的合规任务
- ✅ 批量处理任务
- ❌ 探索性任务(路径不确定)
- ❌ 需要实时交互的对话
示例代码
规划器 Planner
public interface Planner {
@SystemMessage("""
你是一个任务规划专家。请将用户的任务分解为详细的执行步骤。
输出格式必须是严格的JSON格式:
{
"plan_name": "任务名称",
"steps": [
{
"step_number": 1,
"description": "步骤描述",
"tool": "使用的工具名称",
"parameters": {"参数名": "参数值"}
}
],
"expected_output": "预期输出"
}
可用工具列表:
1. add - 两数相加
2. getWeather - 天气查询
3. calculateCircleArea - 计算圆的面积
4. getCurrentDateTime - 获取当前时间
5. calculateCuboidVolume - 计算长方体体积
6. multiply - 两数相乘
7. divide - 两数相除
8. queryExpressOrder - 查询快递单
9. queryRefundProgress - 查询退款进度
""")
@UserMessage("问:{{request}}")
@Agent("基于用户提供的问题生成计划")
String createPlan(@V("request") String request);
}
原文第 20—29 行在提示词中直接列出工具。原作者还尝试过像 ReAct 一样,把 sampleTools 的工具描述传给规划器,并报告能够运行、但 token 用量增加;本站没有复测这一对比。
执行器 Executor
public interface Executor {
@SystemMessage("""
你是一个任务执行器。根据给定的步骤执行任务。
每次只执行一个步骤,然后等待下一个指令。
执行完成后,报告结果和下一步建议。
输出格式:
步骤 {n}: [工具名称]
输入: {参数}
输出: {结果}
状态: [成功/失败]
如果步骤失败,请说明原因和建议。
""")
@UserMessage("{{step}}")
@Agent("基于用户提供的问题生成计划")
String executeStep(@V("step") String step);
}
协调器 Coordinator
/**
* @author junmingyang
*/
public class Coordinator {
private final Planner planner;
private final Executor executor;
private final SampleTools tools;
private final Map<String, Object> context;
public Coordinator(ChatModel model, SampleTools tools) {
this.tools = tools;
this.context = new HashMap<>();
//创建规划器
this.planner = AgenticServices.agentBuilder(Planner.class)
.chatModel(model)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(15))
// 如果明确知道工具的列表,可以显式提供,这里就无需再绑定,以减少token使用
//.tools(this.tools)
.build();
//创建执行器
this.executor = AgenticServices.agentBuilder(Executor.class)
.chatModel(model)
.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(15))
.tools(this.tools)
.build();
}
public Map<String, Object> executeTask(String task) {
System.out.println("\n" + "=".repeat(80));
System.out.println("🎯 任务: " + task);
System.out.println("=".repeat(80));
Map<String, Object> result = new LinkedHashMap<>();
result.put("task", task);
result.put("start_time", LocalDateTime.now().toString());
try {
// 阶段1: 规划
System.out.println("\n📋 阶段1: 任务规划");
System.out.println("-".repeat(40));
String planJson = planner.createPlan(task);
System.out.println("生成的计划:\n" + planJson);
// 解析计划(简化版,实际应该使用JSON解析)
List<Map<String, String>> steps = parsePlan(planJson);
result.put("plan", steps);
// 阶段2: 执行
System.out.println("\n⚡ 阶段2: 执行计划");
System.out.println("-".repeat(40));
List<Map<String, Object>> executionResults = new ArrayList<>();
for (int i = 0; i < steps.size(); i++) {
Map<String, String> step = steps.get(i);
System.out.printf("\n📝 步骤 %d/%d: %s%n",
i + 1, steps.size(), step.get("description"));
// 构建步骤指令
String stepInstruction = buildStepInstruction(step);
// 执行步骤
String stepResult = executor.executeStep(stepInstruction);
System.out.println("执行结果:\n" + stepResult);
// 保存结果
Map<String, Object> stepResultMap = new HashMap<>();
stepResultMap.put("step_number", i + 1);
stepResultMap.put("description", step.get("description"));
stepResultMap.put("tool", step.get("tool"));
stepResultMap.put("result", stepResult);
executionResults.add(stepResultMap);
// 更新上下文
updateContext(task, step, stepResult);
// 短暂暂停,避免过快执行
Thread.sleep(1000);
}
result.put("execution_results", executionResults);
result.put("status", "completed");
} catch (Exception e) {
System.err.println("❌ 任务执行失败: " + e.getMessage());
result.put("status", "failed");
result.put("error", e.getMessage());
}
result.put("end_time", LocalDateTime.now().toString());
return result;
}
private List<Map<String, String>> parsePlan(String planJson) {
// 简化的计划解析(实际应用中应该使用完整的JSON解析)
List<Map<String, String>> steps = new ArrayList<>();
// 使用正则表达式提取步骤信息
Pattern stepPattern = Pattern.compile(
"\"step_number\":\\s*(\\d+).*?" +
"\"description\":\\s*\"([^\"]+)\".*?" +
"\"tool\":\\s*\"([^\"]+)\"",
Pattern.DOTALL
);
Matcher matcher = stepPattern.matcher(planJson);
while (matcher.find()) {
Map<String, String> step = new HashMap<>();
step.put("step_number", matcher.group(1));
step.put("description", matcher.group(2));
step.put("tool", matcher.group(3));
steps.add(step);
}
// 如果没有匹配到,创建默认步骤
if (steps.isEmpty()) {
Map<String, String> defaultStep = new HashMap<>();
defaultStep.put("step_number", "1");
defaultStep.put("description", "执行任务: " + planJson);
defaultStep.put("tool", "analyzeText");
steps.add(defaultStep);
}
return steps;
}
private String buildStepInstruction(Map<String, String> step) {
return String.format(
"执行步骤 %s:\n" +
"描述: %s\n" +
"工具: %s\n" +
"请使用指定工具完成此步骤。",
step.get("step_number"),
step.get("description"),
step.get("tool")
);
}
private void updateContext(String task, Map<String, String> step, String result) {
// 将步骤结果存入上下文,供后续步骤使用(可选)
String key = MurmurHash.murmur3_32Hash(task) + "_step_" + step.get("step_number") + "_result";
context.put(key, result);
}
public void printContext() {
System.out.println("-".repeat(50) + "\n上下文: ");
context.forEach((key, value) -> System.out.println(key + " => \n" + value + "\n" + "-".repeat(30)));
}
}
代码第 22 行创建规划器。原文已在 Planner 提示词中列出工具,因此这里没有额外绑定工具实例。
完整示例
@SpringBootApplication
public class PlanAndExecuteApplication {
public static void main(String[] args) throws IOException {
ConfigurableApplicationContext context = SpringApplication.run(AgentDesignPatternApplication.class, args);
ChatModel model = context.getBean("ollamaChatModel", ChatModel.class);
SampleTools sampleTools = context.getBean("sampleTools", SampleTools.class);
String[] testTasks = {
"计算 15 加上 27 等于多少?",
"北京现在的天气怎么样?",
"计算半径为5的圆的面积",
"现在是几点?",
"计算长方体的体积,长10,宽5,高3",
"帮我算一下 (25 × 4) ÷ 2 等于多少?",
"快递单123456,现在到哪了?",
"我的订单56789,退款到账了没?"
};
Coordinator coordinator = new Coordinator(model, sampleTools);
for (int i = 0; i < testTasks.length; i++) {
System.out.printf("\n📦 测试用例 %d/%d%n", i + 1, testTasks.length);
Map<String, Object> result = coordinator.executeTask(testTasks[i]);
// 打印总结
System.out.println("\n✅ 任务完成总结:");
System.out.println("-".repeat(40));
System.out.println("任务: " + result.get("task"));
System.out.println("状态: " + result.get("status"));
System.out.println("耗时: " + calculateDuration(
(String) result.get("start_time"),
(String) result.get("end_time")
));
if (result.containsKey("execution_results")) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> executions =
(List<Map<String, Object>>) result.get("execution_results");
System.out.println("执行步骤数: " + executions.size());
}
System.out.println("=".repeat(60));
// 任务间暂停
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
coordinator.printContext();
}
private static String calculateDuration(String start, String end) {
try {
LocalDateTime startTime = LocalDateTime.parse(start);
LocalDateTime endTime = LocalDateTime.parse(end);
Duration duration = Duration.between(startTime, endTime);
return String.format("%d秒", duration.getSeconds());
} catch (Exception e) {
return "未知";
}
}
}
原文运行输出
展开原文日志或请求/响应(未复测)
📦 测试用例 1/8
================================================================================
🎯 任务: 计算 15 加上 27 等于多少?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "加法计算",
"steps": [
{
"step_number": 1,
"description": "使用add工具计算15加27的结果",
"tool": "add",
"parameters": {"a": 15, "b": 27}
}
],
"expected_output": "42"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用add工具计算15加27的结果
[工具调用] 加法: 15.00 + 27.00 = 42.00
执行结果:
步骤 1: add
输入: {"a": 15, "b": 27}
输出: 42.00
状态: 成功
计算完成!15加27的结果是42.00。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 计算 15 加上 27 等于多少?
状态: completed
耗时: 5秒
执行步骤数: 1
============================================================
📦 测试用例 2/8
================================================================================
🎯 任务: 北京现在的天气怎么样?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "查询北京天气",
"steps": [
{
"step_number": 1,
"description": "使用getWeather工具查询北京当前的天气情况",
"tool": "getWeather",
"parameters": {"city": "北京"}
}
],
"expected_output": "北京当前的天气信息,包括温度、湿度、天气状况等"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用getWeather工具查询北京当前的天气情况
[工具调用] 查询天气: 北京
执行结果:
步骤 1: getWeather
输入: {"city": "北京"}
输出: 北京的天气:晴转多云,温度22-28°C,湿度65%,东南风2级
状态: 成功
天气查询完成!北京当前天气为晴转多云,温度22-28°C,湿度65%,东南风2级。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 北京现在的天气怎么样?
状态: completed
耗时: 4秒
执行步骤数: 1
============================================================
📦 测试用例 3/8
================================================================================
🎯 任务: 计算半径为5的圆的面积
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "计算圆的面积",
"steps": [
{
"step_number": 1,
"description": "使用calculateCircleArea工具计算半径为5的圆的面积",
"tool": "calculateCircleArea",
"parameters": {"radius": 5}
}
],
"expected_output": "78.5"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用calculateCircleArea工具计算半径为5的圆的面积
[工具调用] 圆面积计算: 半径=5.00, 面积=78.54
执行结果:
步骤 1: calculateCircleArea
输入: {"radius": 5}
输出: 半径为 5.00 的圆的面积是 78.54
状态: 成功
计算完成!半径为5的圆的面积是78.54。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 计算半径为5的圆的面积
状态: completed
耗时: 5秒
执行步骤数: 1
============================================================
📦 测试用例 4/8
================================================================================
🎯 任务: 现在是几点?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "获取当前时间",
"steps": [
{
"step_number": 1,
"description": "使用getCurrentDateTime工具获取当前时间",
"tool": "getCurrentDateTime",
"parameters": {}
}
],
"expected_output": "当前的时间信息,包括年、月、日、小时、分钟、秒等"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用getCurrentDateTime工具获取当前时间
[工具调用] 当前时间: 2026-02-01 13:43:35
执行结果:
步骤 1: getCurrentDateTime
输入: {}
输出: 2026-02-01 13:43:35
状态: 成功
时间获取完成!当前时间是2026-02-01 13:43:35。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 现在是几点?
状态: completed
耗时: 5秒
执行步骤数: 1
============================================================
📦 测试用例 5/8
================================================================================
🎯 任务: 计算长方体的体积,长10,宽5,高3
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "计算长方体体积",
"steps": [
{
"step_number": 1,
"description": "使用calculateCuboidVolume工具计算长方体的体积,长10,宽5,高3",
"tool": "calculateCuboidVolume",
"parameters": {"length": 10, "width": 5, "height": 3}
}
],
"expected_output": "150"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用calculateCuboidVolume工具计算长方体的体积,长10,宽5,高3
[工具调用] 长方体体积: 10.00×5.00×3.00=150.00
执行结果:
步骤 1: calculateCuboidVolume
输入: {"length": 10, "width": 5, "height": 3}
输出: 长 10.00、宽 5.00、高 3.00 的长方体体积是 150.00
状态: 成功
计算完成!长10、宽5、高3的长方体体积是150.00。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 计算长方体的体积,长10,宽5,高3
状态: completed
耗时: 5秒
执行步骤数: 1
============================================================
📦 测试用例 6/8
================================================================================
🎯 任务: 帮我算一下 (25 × 4) ÷ 2 等于多少?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "复合运算计算",
"steps": [
{
"step_number": 1,
"description": "使用multiply工具计算25乘以4的结果",
"tool": "multiply",
"parameters": {"a": 25, "b": 4}
},
{
"step_number": 2,
"description": "使用divide工具将上一步的结果除以2",
"tool": "divide",
"parameters": {"a": 100, "b": 2}
}
],
"expected_output": "50"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/2: 使用multiply工具计算25乘以4的结果
[工具调用] 乘法: 25.00 × 4.00 = 100.00
执行结果:
步骤 1: multiply
输入: {"a": 25, "b": 4}
输出: 25.00 × 4.00 = 100.00
状态: 成功
计算完成!25乘以4的结果是100.00。
请提供下一步指令。
📝 步骤 2/2: 使用divide工具将上一步的结果除以2
[工具调用] 除法: 100.00 ÷ 2.00 = 50.00
执行结果:
步骤 2: divide
输入: {"a": 100, "b": 2}
输出: 100.00 ÷ 2.00 = 50.00
状态: 成功
计算完成!100除以2的结果是50.00。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 帮我算一下 (25 × 4) ÷ 2 等于多少?
状态: completed
耗时: 9秒
执行步骤数: 2
============================================================
📦 测试用例 7/8
================================================================================
🎯 任务: 快递单123456,现在到哪了?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "查询快递物流状态",
"steps": [
{
"step_number": 1,
"description": "使用queryExpressOrder工具查询快递单号123456的物流状态",
"tool": "queryExpressOrder",
"parameters": {"orderNumber": "123456"}
}
],
"expected_output": "快递单号123456的当前物流状态,包括位置、派送进度等信息"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用queryExpressOrder工具查询快递单号123456的物流状态
[工具调用] 快递单: 123456,已经在运输途中,预订明天送达
执行结果:
步骤 1: queryExpressOrder
输入: {"expressOrderNo": "123456"}
输出: 快递单: 123456,已经在运输途中,预订明天送达
状态: 成功
查询完成!快递单号123456的物流状态显示:已经在运输途中,预订明天送达。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 快递单123456,现在到哪了?
状态: completed
耗时: 4秒
执行步骤数: 1
============================================================
📦 测试用例 8/8
================================================================================
🎯 任务: 我的订单56789,退款到账了没?
================================================================================
📋 阶段1: 任务规划
----------------------------------------
生成的计划:
```json
{
"plan_name": "查询退款进度",
"steps": [
{
"step_number": 1,
"description": "使用queryRefundProgress工具查询订单56789的退款进度",
"tool": "queryRefundProgress",
"parameters": {"orderNumber": "56789"}
}
],
"expected_output": "订单56789的退款进度信息,包括退款状态、预计到账时间等"
}
```
⚡ 阶段2: 执行计划
----------------------------------------
📝 步骤 1/1: 使用queryRefundProgress工具查询订单56789的退款进度
[工具调用] 订单: 56789,退款已审批通过,预计1-3个工作日按原路退回
执行结果:
步骤 1: queryRefundProgress
输入: {"orderNo": "56789"}
输出: 订单: 56789,退款已审批通过,预计1-3个工作日按原路退回
状态: 成功
查询完成!订单56789的退款进度显示:退款已审批通过,预计1-3个工作日按原路退回。
请提供下一步指令。
✅ 任务完成总结:
----------------------------------------
任务: 我的订单56789,退款到账了没?
状态: completed
耗时: 4秒
执行步骤数: 1
时序图(原文标注为 AI 生成)

原文配图:Plan-and-Execute:先规划,再执行:简化时序图。