AI Agent 的规划大脑:从任务分解到自适应执行策略

AI Agent 的规划大脑:从任务分解到自适应执行策略

为什么 ReAct 不够用了?

在之前的 理解AI Agent的大脑:ReAct模式从入门到实战 中,我们聊了 Agent 最基础的思考-行动循环:Thought → Action → Observation → Thought… 这套模式让 Agent 能像人一样”边想边做”,面对简单任务时效果很好。

但你有没有遇到过这种情况——

用户:帮我分析一下我们电商系统最近一个月的退款数据,找出退款率最高的品类,然后生成一份包含趋势图表和改进建议的报告。

ReAct 的本能反应是:看到”分析数据”,立刻行动,调用数据库查询工具。查完了,再想下一步。问题是——它没有全局视角

就像一个人搬家,ReAct 的做法是”看到一个箱子就搬一个”,搬着搬着发现大件家具没先搬进去,小箱子已经把门口堵了。而一个有规划能力的人会先想:先搬大件,再搬中件,最后搬小件和零碎。

这就是 Planning(规划)存在的意义——让 Agent 在行动之前,先想清楚整个任务的路径

规划的本质:把”大问题”变成”小步骤”

规划的核心思想其实我们每天都在用。你早上起床后,脑子里会自动排列今天要做的事:先开会,再写代码,下午 review PR,晚上健身。你不会做完一件事才想下一件——你有一个全局计划,然后按计划执行。

AI Agent 的 Planning 也是同样的道理,只不过它需要更明确、更结构化地做这件事。核心流程是:

1
2
3
4
5
6
7
8
9
10
11
12
用户输入复杂任务

[规划器 Planner]
把任务分解成子任务序列

[执行器 Executor]
逐步执行每个子任务

[监控器 Monitor]
检查执行结果,必要时重新规划

最终结果

这个流程看起来简单,但魔鬼在细节里。下面我们逐个拆解。

策略一:Plan-and-Execute(先规划后执行)

这是最直观的规划策略。核心思路是:先一次性生成完整计划,然后逐步执行

工作原理

想象你是一个项目经理,客户给了你一个大需求。你的第一反应不是马上写代码,而是打开文档,列出所有要做的事情:

  1. 需求分析
  2. 技术方案设计
  3. 数据库设计
  4. 后端开发
  5. 前端开发
  6. 联调测试
  7. 上线部署

Plan-and-Execute 模式就是这么干的。Agent 先调用一个强推理模型(通常是 GPT-4 或 Claude)生成计划,然后用一个快速执行模型逐步完成每个子任务。

Java 实战:用 LangChain4j 实现 Plan-and-Execute

下面我们用 LangChain4j 来实现一个完整的 Plan-and-Execute Agent。场景是:用户输入一个复杂的技术调研需求,Agent 需要先规划调研步骤,然后逐步执行。

首先定义核心组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;

import java.time.Duration;
import java.util.List;

/**
* 规划器:负责将复杂任务分解为有序的子任务列表
*/
public interface Planner {

@SystemMessage("""
你是一个任务规划专家。用户会给你一个复杂任务,你需要:
1. 分析任务的目标和约束条件
2. 将任务分解为 3-7 个可执行的子任务
3. 每个子任务要具体、可衡量、有明确的完成标准
4. 子任务之间要有合理的依赖顺序

输出格式(严格按此格式):
[PLAN]
1. [子任务描述] | [预计复杂度: 低/中/高] | [依赖: 无/子任务N]
2. [子任务描述] | [预计复杂度: 低/中/高] | [依赖: 无/子任务N]
...
[/PLAN]

[SUMMARY]
一句话总结这个计划的核心思路
[/SUMMARY]
""")
String createPlan(@UserMessage String task);
}

/**
* 执行器:负责执行单个子任务
*/
public interface Executor {

@SystemMessage("""
你是一个任务执行专家。你需要完成分配给你的子任务。

规则:
1. 专注于当前子任务,不要偏离
2. 如果需要外部信息,明确说明需要什么
3. 输出要结构化,便于后续整合
4. 如果发现子任务无法完成,说明原因和建议的替代方案

当前整体计划背景:{{planContext}}
""")
String executeTask(
@UserMessage String subTask,
@V("planContext") String planContext
);
}

/**
* 评估器:检查子任务执行结果,决定是否需要重新规划
*/
public interface Replanner {

@SystemMessage("""
你是一个执行监控专家。你需要:
1. 评估当前子任务的执行结果是否满足要求
2. 判断是否需要调整后续计划
3. 如果执行结果不符合预期,给出调整建议

输出格式:
[STATUS] PASS / FAIL / NEED_REPLAN [/STATUS]
[FEEDBACK] 具体反馈内容 [/FEEDBACK]
[ADJUSTED_PLAN] 如果需要重新规划,给出调整后的子任务列表;否则写 NONE [/ADJUSTED_PLAN]
""")
String evaluateAndReplan(
@UserMessage String context
);
}

接下来是主控制器,把规划、执行、监控串起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Plan-and-Execute Agent 主控制器
*/
public class PlanAndExecuteAgent {

private final Planner planner;
private final Executor executor;
private final Replanner replanner;
private final int maxReplanAttempts;

public PlanAndExecuteAgent(
ChatLanguageModel plannerModel,
ChatLanguageModel executorModel,
ChatLanguageModel replannerModel,
int maxReplanAttempts) {
this.planner = AiServices.create(Planner.class, plannerModel);
this.executor = AiServices.create(Executor.class, executorModel);
this.replanner = AiServices.create(Replanner.class, replannerModel);
this.maxReplanAttempts = maxReplanAttempts;
}

/**
* 执行完整的 Plan-and-Execute 流程
*/
public ExecutionResult execute(String task) {
// 第一步:生成计划
System.out.println("📋 正在生成计划...");
String planOutput = planner.createPlan(task);
List<SubTask> subTasks = parsePlan(planOutput);
String planSummary = parseSummary(planOutput);

System.out.printf("✅ 计划生成完毕,共 %d 个子任务%n", subTasks.size());
System.out.printf("📌 计划摘要:%s%n%n", planSummary);

// 第二步:逐步执行
List<TaskResult> results = new ArrayList<>();
int replanCount = 0;

for (int i = 0; i < subTasks.size(); i++) {
SubTask subTask = subTasks.get(i);
System.out.printf("🔧 执行子任务 %d/%d:%s%n",
i + 1, subTasks.size(), subTask.description());

// 构建执行上下文:包含计划信息和之前子任务的结果
String context = buildExecutionContext(planSummary, subTasks, results, i);

// 执行子任务
String taskResult = executor.executeTask(subTask.description(), context);
results.add(new TaskResult(subTask, taskResult, TaskStatus.COMPLETED));
System.out.printf("✅ 子任务 %d 完成%n%n", i + 1);

// 评估执行结果(对高复杂度任务做检查)
if (subTask.complexity() == Complexity.HIGH) {
String evalContext = buildEvalContext(planSummary, subTask, taskResult);
String evalResult = replanner.evaluateAndReplan(evalContext);

String status = extractTag(evalResult, "STATUS");
if ("FAIL".equals(status) && replanCount < maxReplanAttempts) {
System.out.println("⚠️ 执行结果不符合预期,正在重新规划...");
String adjustedPlan = extractTag(evalResult, "ADJUSTED_PLAN");
if (!"NONE".equals(adjustedPlan)) {
// 替换剩余子任务
List<SubTask> adjusted = parsePlan(adjustedPlan);
subTasks = mergeRemaining(subTasks, adjusted, i);
replanCount++;
}
}
}
}

// 第三步:整合结果
String finalResult = integrateResults(results);
return new ExecutionResult(planSummary, results, finalResult, replanCount);
}

/**
* 解析计划输出,提取子任务列表
*/
private List<SubTask> parsePlan(String planOutput) {
List<SubTask> tasks = new ArrayList<>();
String planBlock = extractTag(planOutput, "PLAN");
if (planBlock == null) return tasks;

Pattern pattern = Pattern.compile(
"\\d+\\.\\s*(.+?)\\s*\\|\\s*复杂度[::]\\s*(低|中|高)\\s*\\|\\s*依赖[::]\\s*(无|子任务\\d+|\\S+)"
);
Matcher matcher = pattern.matcher(planBlock);
while (matcher.find()) {
String desc = matcher.group(1).trim();
Complexity complexity = Complexity.valueOf(matcher.group(2));
String dependency = matcher.group(3).trim();
tasks.add(new SubTask(desc, complexity, dependency));
}
return tasks;
}

private String extractTag(String text, String tag) {
Pattern pattern = Pattern.compile(
"\\[" + tag + "\\](.+?)\\[/" + tag + "\\]", Pattern.DOTALL
);
Matcher matcher = pattern.matcher(text);
return matcher.find() ? matcher.group(1).trim() : null;
}

private String buildExecutionContext(
String planSummary, List<SubTask> allTasks,
List<TaskResult> completed, int currentIndex) {
StringBuilder sb = new StringBuilder();
sb.append("整体计划:").append(planSummary).append("\n\n");

sb.append("已完成的子任务:\n");
for (TaskResult r : completed) {
sb.append("- ").append(r.subTask().description())
.append(" → 结果:").append(abbreviate(r.result(), 200)).append("\n");
}

sb.append("\n当前待执行:").append(allTasks.get(currentIndex).description());
if (currentIndex + 1 < allTasks.size()) {
sb.append("\n下一步计划:").append(allTasks.get(currentIndex + 1).description());
}
return sb.toString();
}

private String buildEvalContext(
String planSummary, SubTask subTask, String result) {
return String.format(
"整体计划:%s\n子任务:%s\n执行结果:%s",
planSummary, subTask.description(), result
);
}

private List<SubTask> mergeRemaining(
List<SubTask> original, List<SubTask> adjusted, int currentIndex) {
List<SubTask> merged = new ArrayList<>(original.subList(0, currentIndex + 1));
merged.addAll(adjusted);
return merged;
}

private String integrateResults(List<TaskResult> results) {
StringBuilder sb = new StringBuilder();
sb.append("## 执行结果汇总\n\n");
for (int i = 0; i < results.size(); i++) {
TaskResult r = results.get(i);
sb.append("### ").append(i + 1).append(". ").append(r.subTask().description()).append("\n\n");
sb.append(r.result()).append("\n\n");
}
return sb.toString();
}

private String abbreviate(String text, int maxLen) {
return text.length() <= maxLen ? text : text.substring(0, maxLen) + "...";
}
}

// 数据模型
record SubTask(String description, Complexity complexity, String dependency) {}
record TaskResult(SubTask subTask, String result, TaskStatus status) {}
record ExecutionResult(String planSummary, List<TaskResult> results,
String finalOutput, int replanCount) {}

enum Complexity { 低, 中, 高 }
enum TaskStatus { PENDING, IN_PROGRESS, COMPLETED, FAILED }

为什么 Plan-and-Execute 有效?

关键在于推理和执行的分离。在 ReAct 模式中,Agent 用同一个上下文窗口做推理和执行,随着对话变长,”注意力”会被之前的操作细节稀释。而 Plan-and-Execute 把”想”和”做”分开了:

  • Planner 只看任务描述,不受执行细节干扰,能给出更宏观的计划
  • Executor 只关注当前子任务,上下文更聚焦,执行更精准
  • Replanner 监控执行质量,必要时调整计划

这就像一个公司里 CEO 定战略、员工做执行、中层管理者监控进度——各司其职,效率最高。

策略二:Tree of Thoughts(思维树)

Plan-and-Execute 是线性规划——A → B → C,一步一步来。但现实中很多问题需要探索性思考:走不通就换条路。

Tree of Thoughts(ToT)就是为这种场景设计的。它的核心思想是:

在每个决策点,不只走一条路,而是同时探索多条可能的路径,评估每条路的前景,选最好的那条继续。

生活化类比

想象你在迷宫里。ReAct 的做法是:看到岔路就选一条走,走不通就回头。ToT 的做法是:在每个岔路口,派出三个人分别走三条路,各走几步后互相汇报”这条路前面是死胡同”还是”这条路通向出口”,然后所有人都走最有希望的那条。

实现思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
         初始问题
/ | \
思路A 思路B 思路C
/ \ | |
A1 A2 B1 C1
↓ ↓ ↓
评估 评估 评估
(0.3) (0.8) (0.6)

继续展开 B1
/ | \
B1a B1b B1c

评估选择

在 Java 中,我们可以用递归 + 评分机制来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import java.util.*;
import java.util.stream.Collectors;

/**
* Tree of Thoughts 实现
* 在每个决策节点生成多个候选思路,评估后选择最优路径继续展开
*/
public class TreeOfThoughts {

private final ChatLanguageModel model;
private final int branchingFactor; // 每个节点生成几个候选
private final int maxDepth; // 最大搜索深度
private final double pruneThreshold; // 剪枝阈值

public TreeOfThoughts(
ChatLanguageModel model,
int branchingFactor,
int maxDepth,
double pruneThreshold) {
this.model = model;
this.branchingFactor = branchingFactor;
this.maxDepth = maxDepth;
this.pruneThreshold = pruneThreshold;
}

/**
* 从初始问题出发,搜索最优思考路径
*/
public ThoughtPath search(String problem) {
ThoughtNode root = new ThoughtNode(problem, null, 0, 0.0);
return dfs(root, 0);
}

/**
* 深度优先搜索 + 剪枝
*/
private ThoughtPath dfs(ThoughtNode node, int depth) {
if (depth >= maxDepth) {
return new ThoughtPath(node);
}

// 在当前节点生成多个候选思路
List<String> candidates = generateCandidates(node);

// 评估每个候选思路的前景
List<ScoredCandidate> scored = candidates.stream()
.map(c -> {
double score = evaluateCandidate(node, c, depth);
return new ScoredCandidate(c, score);
})
.sorted((a, b) -> Double.compare(b.score, a.score))
.collect(Collectors.toList());

// 剪枝:只保留评分高于阈值的候选
List<ScoredCandidate> pruned = scored.stream()
.filter(s -> s.score >= pruneThreshold)
.limit(branchingFactor)
.collect(Collectors.toList());

if (pruned.isEmpty()) {
return new ThoughtPath(node);
}

// 对每个存活的候选,递归展开
ThoughtPath bestPath = null;
double bestScore = Double.NEGATIVE_INFINITY;

for (ScoredCandidate candidate : pruned) {
ThoughtNode child = new ThoughtNode(
candidate.content, node, depth + 1, candidate.score
);
ThoughtPath path = dfs(child, depth + 1);

if (path.totalScore() > bestScore) {
bestScore = path.totalScore();
bestPath = path;
}
}

return bestPath != null ? bestPath : new ThoughtPath(node);
}

/**
* 生成候选思路:让模型从当前节点出发,提出下一步的多种可能
*/
private List<String> generateCandidates(ThoughtNode node) {
String prompt = String.format("""
当前问题:%s
已有思考路径:%s

请提出 %d 种不同的下一步思路。每种思路要方向不同,不要重复。
每条思路用一句话概括核心方向。

输出格式(每行一条):
1. 思路描述
2. 思路描述
...
""", node.content, reconstructPath(node), branchingFactor);

String response = model.generate(prompt);
return Arrays.stream(response.split("\n"))
.map(line -> line.replaceFirst("^\\d+\\.\\s*", "").trim())
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
}

/**
* 评估某个候选思路的前景
*/
private double evaluateCandidate(
ThoughtNode parent, String candidate, int depth) {
String prompt = String.format("""
问题:%s
当前思路方向:%s
候选下一步:%s

请评估这个思路的有效性,给出 0.0 到 1.0 的评分。
考虑因素:
- 是否能有效推进问题的解决(权重 50%%)
- 是否有实际可操作性(权重 30%%)
- 是否考虑了潜在风险(权重 20%%)

只输出一个数字,不要解释。
""", parent.content, reconstructPath(parent), candidate);

String response = model.generate(prompt).trim();
try {
return Math.max(0.0, Math.min(1.0, Double.parseDouble(response)));
} catch (NumberFormatException e) {
return 0.5; // 解析失败时给一个中性分数
}
}

private String reconstructPath(ThoughtNode node) {
List<String> path = new ArrayList<>();
ThoughtNode current = node;
while (current != null) {
path.add(0, current.content);
current = current.parent;
}
return String.join(" → ", path);
}
}

// 数据模型
record ThoughtNode(
String content,
ThoughtNode parent,
int depth,
double score
) {}

record ScoredCandidate(String content, double score) {}

record ThoughtPath(ThoughtNode leaf) {
double totalScore() {
double sum = 0;
ThoughtNode current = leaf;
while (current != null) {
sum += current.score;
current = current.parent;
}
return sum;
}
}

ToT vs Plan-and-Execute:怎么选?

维度 Plan-and-Execute Tree of Thoughts
适用场景 任务步骤明确、流程可预期 探索性问题、需要创造性解法
Token 消耗 中等(规划 + 逐步执行) 较高(多分支探索 + 评估)
可控性 强,计划可审查 弱,搜索过程不确定
典型场景 数据分析、报告生成、系统对接 算法设计、方案选型、疑难 bug 排查
失败模式 计划不合理导致全盘返工 分支过多导致 token 爆炸

实战建议:大部分企业应用场景用 Plan-and-Execute 就够了。ToT 适合那些”答案不是唯一的,需要权衡多个方向”的问题。

策略三:动态重规划(Adaptive Replanning)

真正的生产环境不会按计划完美运行。API 超时、数据格式变了、权限不够——意外无处不在。所以一个好的规划系统必须能动态调整

核心机制

动态重规划的关键是反馈回路

1
2
3
4
5
执行子任务 → 检查结果 → 结果 OK? 
├─ 是 → 继续下一个
└─ 否 → 分析失败原因
├─ 可修复 → 调整当前子任务的执行方式
└─ 不可修复 → 重新规划后续任务

这和软件开发中的敏捷迭代是一样的:不是制定完美计划然后盲执行,而是”小步快跑,快速反馈,及时调整”。

实现要点

在前面的 Plan-and-Execute 代码中,我们已经有了 Replanner 组件。这里再补充一个关键的重试策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* 带指数退避的子任务执行器
* 遇到临时性失败时自动重试,非临时性失败触发重规划
*/
public class ResilientTaskExecutor {

private final Executor executor;
private final int maxRetries;
private final Duration baseDelay;

public ResilientTaskExecutor(Executor executor, int maxRetries, Duration baseDelay) {
this.executor = executor;
this.maxRetries = maxRetries;
this.baseDelay = baseDelay;
}

public TaskExecutionResult executeWithRetry(
String subTask, String context, int attempt) {
try {
String result = executor.executeTask(subTask, context);

// 简单的结果质量检查
if (isLowQualityResult(result)) {
if (attempt < maxRetries) {
System.out.printf(
"⚠️ 结果质量不佳,第 %d 次重试...%n", attempt + 1);
Thread.sleep(baseDelay.toMillis() * (long) Math.pow(2, attempt));
// 重试时给更明确的提示
String refinedContext = context
+ "\n\n注意:上次执行结果不够完整,请更仔细地完成任务。";
return executeWithRetry(subTask, refinedContext, attempt + 1);
}
return new TaskExecutionResult(result, ExecutionStatus.NEEDS_REPLAN);
}

return new TaskExecutionResult(result, ExecutionStatus.SUCCESS);

} catch (Exception e) {
if (isTransientError(e) && attempt < maxRetries) {
System.out.printf(
"⚠️ 遇到临时错误:%s,第 %d 次重试...%n",
e.getMessage(), attempt + 1);
try {
Thread.sleep(baseDelay.toMillis() * (long) Math.pow(2, attempt));
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
return executeWithRetry(subTask, context, attempt + 1);
}
return new TaskExecutionResult(
"执行失败:" + e.getMessage(), ExecutionStatus.FAILED);
}
}

private boolean isLowQualityResult(String result) {
// 结果过短、包含"无法完成"等关键词时判定为低质量
if (result.length() < 50) return true;
String lower = result.toLowerCase();
return lower.contains("无法完成")
|| lower.contains("cannot complete")
|| lower.contains("信息不足");
}

private boolean isTransientError(Exception e) {
return e instanceof java.net.SocketTimeoutException
|| e instanceof java.net.ConnectException
|| (e.getMessage() != null
&& (e.getMessage().contains("429")
|| e.getMessage().contains("503")));
}
}

record TaskExecutionResult(String result, ExecutionStatus status) {}
enum ExecutionStatus { SUCCESS, NEEDS_REPLAN, FAILED }

实战案例:技术方案调研 Agent

让我们把上面的组件组合起来,做一个完整的案例。场景是:

用户输入:”调研 2026 年主流的 Java 向量数据库方案,对比 Milvus、Qdrant、Weaviate、PgVector,给出选型建议。”

Agent 的执行过程:

第一步:生成计划

1
2
3
4
5
6
7
8
9
📋 正在生成计划...
✅ 计划生成完毕,共 5 个子任务
📌 计划摘要:从功能、性能、Java 生态适配性、运维成本四个维度系统对比四款向量数据库

1. 收集四款向量数据库的核心特性与 2026 年最新版本信息 | 复杂度: 中 | 依赖: 无
2. 对比 Java SDK 成熟度与 Spring AI/LangChain4j 集成支持 | 复杂度: 高 | 依赖: 子任务1
3. 收集性能基准测试数据(写入速度、查询延迟、召回率) | 复杂度: 高 | 依赖: 子任务1
4. 分析运维复杂度与部署方案(Docker/K8s/云托管) | 复杂度: 中 | 依赖: 子任务1
5. 综合评估,输出选型建议矩阵与推荐方案 | 复杂度: 高 | 依赖: 子任务2,3,4

第二步:逐步执行

每个子任务由 Executor 独立完成,每个任务的输出都结构化存储。到了高复杂度的子任务(2、3、5),Replanner 会介入检查。

比如子任务 2 执行后,Replanner 发现 LangChain4j 对 Qdrant 的支持不是官方的,需要补充信息——于是触发一次重规划,增加了一个补充调研子任务。

第三步:整合输出

最终 Agent 输出一份完整的对比报告,包含特性矩阵、性能数据、Java 代码示例和选型建议。

关键洞察

这个案例体现了 Planning 的三个核心价值:

  1. 全局视野:Agent 不会只看一个数据库就下结论,而是系统性地覆盖所有维度
  2. 依赖管理:子任务 5 依赖前三个任务的结果,规划器确保了正确的执行顺序
  3. 自适应性:发现信息缺失时,能自动补充调研而不是硬凑结论

规划的边界与陷阱

规划很强大,但不是万能的。以下是实践中总结的常见问题:

陷阱一:过度规划

有些人觉得”计划越详细越好”,于是让 Agent 把任务拆成 15 个子任务。问题是——子任务越多,误差累积越严重。前 3 个子任务可能执行得很好,到第 8 个时已经偏离原始目标很远了。

最佳实践:子任务控制在 3-7 个。如果一个任务需要 10+ 步,先把它分成 2-3 个阶段,每个阶段内再做规划。

陷阱二:计划不可执行

Planner 有时会生成”理论上正确但实操中不可行”的计划。比如”获取竞品的完整用户数据”——你怎么获取?爬虫?API?买了数据?

最佳实践:在 Planner 的 System Prompt 中明确约束:”每个子任务必须基于已有工具和可获取的数据源来设计。”

陷阱三:忽视执行反馈

有些实现中,Planner 生成计划后就”消失”了,Executor 机械地执行。遇到问题时没有人能修改计划。

最佳实践:必须有 Replanner 或类似的监控机制。这是生产环境和 demo 的本质区别。

陷阱四:Token 预算失控

ToT 尤其容易出现这个问题——每个节点生成 N 个候选、评估 N 次、展开 N 个分支,Token 消耗是指数级的。

最佳实践

  • 设置分支因子上限(通常 3 就够了)
  • 设置最大搜索深度(通常 3-5 层)
  • 用轻量模型做初筛,重量模型做最终决策
  • 在 System Prompt 中限制输出长度

与现有 Agent 能力的关系

规划不是独立存在的,它和其他 Agent 能力紧密协作:

  • ReAct 的关系:ReAct 是”战术层面”的思考-行动循环,Planning 是”战略层面”的任务分解。好的 Agent 两者兼备——用 Planning 确定大方向,用 ReAct 处理每个子任务的细节执行。

  • Tool Use 的关系:规划决定了”什么时候用什么工具”。没有规划的 Tool Use 是盲目的——Agent 可能反复调用同一个工具而想不到换个工具试试。

  • Memory 的关系:规划器需要记忆来避免重复。如果 Agent 记得”上次分析数据时发现某个 API 有频率限制”,就能在新计划中提前规避这个问题。

  • RAG 的关系:RAG 可以增强规划的质量。当 Agent 需要制定一个技术方案时,先用 RAG 检索相关文档,再基于检索结果制定计划,比纯靠 LLM 的内部知识更可靠。

选型建议:Spring AI vs LangChain4j 的规划实现

在 Java 生态中,两个主流框架对 Planning 的支持方式不同:

维度 Spring AI LangChain4j
规划抽象 没有内置 Planning 抽象,需要自行组合 ChatClient + Advisor AiServices + @SystemMessage 可以快速定义 Planner/Executor
状态管理 自带 ChatMemory Advisor,天然支持多轮状态 需要手动管理或使用 ChatMemoryProvider
工具调用 Function Calling 原生支持,与规划结合紧密 Tool 规范清晰,但多工具编排需手动处理
灵活度 底层抽象偏 Spring 风格,扩展性强但需要更多胶水代码 接口驱动,定义角色很直观,但复杂编排能力弱一些
适合场景 已有 Spring 生态的项目、需要深度定制 快速原型、接口清晰的场景

我的建议:如果你是 Spring 项目,用 Spring AI 的 ChatClient + Advisor 组合来做规划,灵活度最高。如果你想要更简洁的接口定义,LangChain4j 的 AiServices 更直观。两者都不提供开箱即用的 Planning 框架,都需要自己组合——这也说明 Planning 目前还是一个”需要工程化”的领域。

总结

AI Agent 的规划能力是把 Agent 从”能做事”提升到”能做复杂事”的关键。核心要点:

  1. Plan-and-Execute 适合步骤明确的任务,实现简单、可控性强
  2. Tree of Thoughts 适合探索性问题,但 Token 消耗大,需要做好剪枝
  3. 动态重规划 是生产环境的必备能力,没有它 Agent 就是个”按剧本演戏的演员”
  4. 规划有边界:过度规划、计划不可执行、忽视反馈、Token 失控是四大陷阱
  5. 规划与 ReAct、Tool Use、Memory、RAG 是协作关系,不是替代关系

下次当你面对一个复杂需求时,不妨先问自己:这个任务需要几步完成?每步依赖什么?哪些地方可能出问题?——这其实就是你在做 Planning。AI Agent 要学的,正是这种”先想后做”的能力。


本文是 AI Agent 系列的第七篇。该系列从 ReAct 模式 出发,覆盖了 Tool Use记忆系统RAGMCP 协议结构化输出 等核心能力。Planning 作为 Agent 的”战略层”,把这些能力串联成了一个完整的智能体。