第七篇:AI Agent 如何自主规划任务?ReAct、Plan-and-Execute 与状态机实战
系列:《2026 可验证企业级 AI Agent 工程》
本文关键词:AI Agent、ReAct、Plan-and-Execute、状态机、任务规划、Spring AI
文章目录
- 第七篇:AI Agent 如何自主规划任务?ReAct、Plan-and-Execute 与状态机实战
- 三、ReAct:边推理边行动
- 六、Plan-and-Execute:先规划,再逐步执行
- 十一、为什么需要 Evaluator?
- 十三、状态机:把关键流程控制权交还给程序
- 十五、三种模式应该如何选择?
- 《A2A 多智能体协作实战:让能源分析、设备诊断与工单 Agent 协同工作》
前言
前面我们已经为 Agent 提供了 RAG、Memory 和 MCP 工具。
现在用户提出:
分析一号车间昨天能耗升高的原因。如果确认存在设备异常,就创建检修工单。
Agent 需要完成的并不是一次模型问答,而是一组相互依赖的任务:
识别“一号车间”
→ 查询车间能耗
→ 定位异常设备
→ 查询设备告警
→ 检索历史案例
→ 分析异常原因
→ 判断证据是否充分
→ 请求用户确认
→ 创建检修工单
这里会遇到几个关键问题:
- Agent 如何拆解任务?
- 如何选择下一步工具?
- 工具失败后如何调整计划?
- 如何避免无限循环?
- 哪些步骤允许自主执行?
- 如何从中断位置恢复?
- 如何证明任务确实完成?
这就是 Agent Planning,即智能体任务规划。
一、Tool Calling 不等于 Agent
单次 Tool Calling 的流程通常是:
用户问题
→ 模型选择工具
→ 执行工具
→ 模型生成答案
例如:
用户:查询设备AC-001昨天的能耗。
模型:调用queryDeviceEnergy。
这是一次相对明确的工具调用。
复杂 Agent 则需要执行多轮决策:
用户目标
→ 拆解任务
→ 选择工具
→ 获取结果
→ 判断信息是否充分
→ 修改计划
→ 执行下一步
→ 验证最终结果
因此,一个完整 Agent 至少包含:
Model:负责理解和决策
Tools:负责执行外部操作
State:保存当前任务状态
Planner:制定计划
Executor:执行步骤
Evaluator:验证结果
Policy:约束权限与预算
大模型只是 Agent 的决策组件,而不是整个 Agent 系统。
二、三种常见规划模式
企业 Agent 通常采用三类规划模式:
| 模式 | 特点 | 适用场景 |
|---|---|---|
| ReAct | 边思考边执行 | 开放式查询、探索任务 |
| Plan-and-Execute | 先规划再执行 | 多步骤分析任务 |
| 状态机 | 流程和转移明确 | 审批、工单、高风险操作 |
它们并不是互斥关系。
生产系统更常见的组合是:
状态机控制主流程
+ Plan-and-Execute 拆解复杂目标
+ ReAct 完成局部探索
三、ReAct:边推理边行动
ReAct 来自 Reasoning and Acting。
它的基本循环是:
观察当前状态
→ 判断下一步行动
→ 调用工具
→ 获取观察结果
→ 再次判断
典型过程如下:
目标:
分析空压机AC-001昨天能耗升高的原因。
行动:
调用queryDeviceEnergy。
观察:
能耗比历史基线高23.7%。
行动:
调用queryAlarmHistory。
观察:
14:32出现排气温度高告警。
行动:
查询历史维修案例。
观察:
历史相似案例原因为冷却器堵塞。
结论:
当前异常可能与冷却系统效率下降有关。
1. ReAct 的优势
ReAct 不要求 Agent 一开始就掌握完整计划。
它可以根据工具返回结果动态调整:
如果能耗正常
→ 不再查询告警
如果能耗异常
→ 查询异常设备
如果存在告警
→ 检索告警相关故障案例
如果证据冲突
→ 补充查询实时运行数据
适合以下场景:
- 问题路径不确定;
- 需要探索多个数据源;
- 工具结果会影响下一步;
- 无法预先确定完整步骤。
2. ReAct 的问题
ReAct 也容易出现失控:
重复调用同一个工具
无意义地更换查询条件
检索轮数不断增加
忘记原始任务目标
在证据不足时提前结束
执行了不应自动执行的写操作
因此,ReAct 循环必须受到预算约束。
四、实现一个受控的 ReAct 循环
先定义 Agent 动作:
public enum ActionType {
CALL_TOOL,
ASK_USER,
FINISH,
ABORT
}
定义模型每轮必须返回的决策:
public record AgentDecision(
ActionType actionType,
String toolName,
Map<String, Object> arguments,
String userQuestion,
String decisionSummary,
String finalAnswer
) {
}
这里保存的是面向审计的简短决策摘要,而不是要求模型暴露完整内部推理过程。
定义执行状态:
public record AgentState(
String taskId,
String goal,
List<ToolObservation> observations,
Set<String> executedActionKeys,
int currentStep,
int maxSteps,
long startedAt,
long timeoutMillis,
AgentStatus status
) {
}
执行循环:
public AgentResult run(AgentState state) {
while (state.currentStep() < state.maxSteps()) {
checkTimeout(state);
AgentDecision decision =
decisionService.decide(state);
if (decision.actionType() == ActionType.FINISH) {
return AgentResult.completed(
decision.finalAnswer()
);
}
if (decision.actionType() == ActionType.ASK_USER) {
return AgentResult.inputRequired(
decision.userQuestion(),
state
);
}
if (decision.actionType() == ActionType.ABORT) {
return AgentResult.failed(
decision.decisionSummary()
);
}
String actionKey =
generateActionKey(decision);
if (state.executedActionKeys().contains(actionKey)) {
return AgentResult.failed(
"检测到重复工具调用,任务已停止"
);
}
ToolObservation observation =
toolExecutor.execute(decision);
state = stateUpdater.append(
state,
actionKey,
observation
);
checkpointRepository.save(state);
}
return AgentResult.failed(
"超过最大执行步骤:" + state.maxSteps()
);
}
这里加入了四个关键限制:
最大执行步数
任务超时时间
重复动作检测
每步状态持久化
五、不要把完整思维链保存到数据库
Agent 系统需要可解释性,但可解释性不等于保存模型完整的 Chain of Thought。
更适合记录的是:
{
"step": 3,
"decision": "查询设备告警",
"reasonSummary": "能耗较基线升高23.7%,需要确认是否存在同期故障",
"tool": "queryAlarmHistory",
"resultSummary": "发现1条排气温度高告警",
"evidenceIds": [
"ALARM-20260921-001"
]
}
这样既能支持审计,也能避免:
- 保存冗长、不稳定的模型推理文本;
- 将内部推理误认为业务事实;
- 大量占用存储;
- 泄露无关上下文和敏感信息。
企业需要的是可验证的决策轨迹:
输入是什么
选择了什么动作
调用了什么工具
获得了什么证据
最终结论是什么
六、Plan-and-Execute:先规划,再逐步执行
ReAct 每轮只决定下一步,容易缺少全局目标。
Plan-and-Execute 会先生成完整计划:
目标:分析一号车间昨日能耗异常并处理。
计划:
1. 查询车间昨日总能耗;
2. 与近七个工作日基线比较;
3. 定位贡献度最高的异常设备;
4. 查询异常设备同期告警;
5. 检索历史相似故障;
6. 形成诊断结论;
7. 请求用户确认;
8. 创建检修工单。
然后由 Executor 按步骤执行。
七、定义结构化计划
不要让 Planner 返回一段不可解析的自然语言。
定义计划对象:
public record AgentPlan(
String goal,
List<PlanStep> steps,
String successCriteria
) {
}
每个步骤:
public record PlanStep(
String stepId,
String description,
String toolName,
Map<String, Object> arguments,
List<String> dependencies,
StepRiskLevel riskLevel,
StepStatus status
) {
}
风险等级:
public enum StepRiskLevel {
READ_ONLY,
ANALYSIS,
WRITE,
HIGH_RISK
}
计划示例:
{
"goal": "分析一号车间昨日能耗异常",
"successCriteria": "找到主要异常设备并给出有数据依据的原因",
"steps": [
{
"stepId": "S1",
"description": "查询车间总能耗",
"toolName": "queryWorkshopEnergy",
"dependencies": [],
"riskLevel": "READ_ONLY",
"status": "PENDING"
},
{
"stepId": "S2",
"description": "定位异常设备",
"toolName": "queryDeviceEnergyContribution",
"dependencies": ["S1"],
"riskLevel": "ANALYSIS",
"status": "PENDING"
},
{
"stepId": "S3",
"description": "查询异常设备告警",
"toolName": "queryAlarmHistory",
"dependencies": ["S2"],
"riskLevel": "READ_ONLY",
"status": "PENDING"
}
]
}
结构化计划便于:
- 校验工具是否存在;
- 判断依赖是否满足;
- 执行风险审查;
- 计算成本预算;
- 展示任务进度;
- 中断后恢复。
八、使用 Spring AI 生成结构化计划
下面是一个简化的 Planner:
@Service
public class AgentPlanner {
private final ChatClient chatClient;
private final ToolRegistry toolRegistry;
public AgentPlanner(
ChatClient.Builder builder,
ToolRegistry toolRegistry) {
this.chatClient = builder.build();
this.toolRegistry = toolRegistry;
}
public AgentPlan createPlan(
String goal,
UserContext userContext) {
String availableTools =
toolRegistry.describeAllowedTools(
userContext
);
return chatClient.prompt()
.system("""
你是企业任务规划器。
请将用户目标拆分为可执行步骤。
规则:
1. 只能使用提供的工具;
2. 每一步只能执行一个明确动作;
3. 明确步骤依赖关系;
4. 写操作必须标记为WRITE;
5. 高风险操作必须标记为HIGH_RISK;
6. 不得将未知参数编造成确定值;
7. 信息不足时生成询问用户的步骤。
""")
.user("""
用户目标:
{goal}
当前用户权限:
{permissions}
可用工具:
{tools}
""")
.param("goal", goal)
.param(
"permissions",
userContext.permissions()
)
.param("tools", availableTools)
.call()
.entity(AgentPlan.class);
}
}
不同 Spring AI 版本的结构化输出 API 可能有所差异,应结合当前版本配置转换器和 Schema。
即使模型返回了 AgentPlan,也不能立即执行,必须先校验。
九、计划校验比计划生成更重要
模型生成的计划可能包含:
- 不存在的工具;
- 用户无权调用的工具;
- 缺失参数;
- 循环依赖;
- 未经确认的写操作;
- 超出预算的步骤;
- 与用户目标无关的动作。
计划验证器:
@Component
public class PlanValidator {
private static final int MAX_PLAN_STEPS = 12;
private final ToolRegistry toolRegistry;
private final PermissionService permissionService;
public void validate(
AgentPlan plan,
UserContext userContext) {
if (plan.steps().isEmpty()) {
throw new IllegalArgumentException(
"执行计划不能为空"
);
}
if (plan.steps().size() > MAX_PLAN_STEPS) {
throw new IllegalArgumentException(
"计划步骤超过限制"
);
}
Set<String> stepIds = plan.steps()
.stream()
.map(PlanStep::stepId)
.collect(Collectors.toSet());
for (PlanStep step : plan.steps()) {
if (!toolRegistry.exists(step.toolName())) {
throw new IllegalArgumentException(
"工具不存在:" + step.toolName()
);
}
if (!permissionService.canUse(
userContext,
step.toolName())) {
throw new AccessDeniedException(
"无权调用工具:" + step.toolName()
);
}
if (!stepIds.containsAll(step.dependencies())) {
throw new IllegalArgumentException(
"步骤依赖不存在:" + step.stepId()
);
}
}
detectCircularDependencies(plan);
}
}
核心原则是:
模型负责提出计划,程序负责决定计划是否允许执行。
十、Executor 如何执行计划?
Executor 每次只查找依赖已经完成的步骤。
public Optional<PlanStep> findNextStep(
AgentPlan plan) {
Set<String> completedIds = plan.steps()
.stream()
.filter(step ->
step.status() == StepStatus.COMPLETED)
.map(PlanStep::stepId)
.collect(Collectors.toSet());
return plan.steps()
.stream()
.filter(step ->
step.status() == StepStatus.PENDING)
.filter(step ->
completedIds.containsAll(
step.dependencies()
))
.findFirst();
}
执行前进行风险判断:
public StepExecutionResult execute(
PlanStep step,
ExecutionContext context) {
if (step.riskLevel() == StepRiskLevel.HIGH_RISK) {
return StepExecutionResult.rejected(
"高风险操作禁止Agent自动执行"
);
}
if (step.riskLevel() == StepRiskLevel.WRITE
&& !context.hasValidConfirmation(step.stepId())) {
return StepExecutionResult.inputRequired(
"该步骤将修改业务数据,是否继续?"
);
}
return toolExecutor.execute(
step.toolName(),
step.arguments(),
context
);
}
十一、为什么需要 Evaluator?
工具调用成功,不代表任务完成。
例如:
目标:找出能耗升高原因
工具结果:查询成功,返回100条能耗记录
技术上查询成功了,但仍未证明已经找到原因。
Evaluator 需要判断:
- 当前步骤的目标是否完成;
- 工具结果是否有效;
- 数据质量是否合格;
- 证据是否足够;
- 是否出现冲突;
- 是否需要补充查询;
- 是否应该调整计划。
定义评估结果:
public record EvaluationResult(
boolean stepCompleted,
boolean evidenceSufficient,
boolean replanRequired,
List<String> missingInformation,
List<String> evidenceIds,
String summary
) {
}
评估时应尽量结合确定性规则。
例如:
public EvaluationResult evaluateEnergyQuery(
DeviceEnergyResult result) {
if (!"GOOD".equals(result.dataQuality())) {
return new EvaluationResult(
false,
false,
true,
List.of("高质量能耗数据"),
List.of(),
"数据质量不足,需要更换数据源"
);
}
return new EvaluationResult(
true,
true,
false,
List.of(),
List.of(result.deviceId()),
"能耗数据查询完成"
);
}
可以通过代码判断的问题,不要全部交给大模型判断。
十二、什么时候需要重新规划?
计划执行过程中可能出现:
设备不存在
查询结果为空
数据质量不足
工具调用超时
用户拒绝写操作
发现新的异常设备
原计划假设不成立
此时 Replanner 根据当前状态调整剩余计划:
public AgentPlan replan(
AgentPlan originalPlan,
List<StepExecutionResult> results,
List<String> missingInformation) {
return chatClient.prompt()
.system("""
你是任务重规划器。
只允许修改尚未执行的步骤。
已完成步骤及其结果不可伪造或覆盖。
不得增加用户无权调用的工具。
如果目标无法完成,应明确终止原因。
""")
.user("""
原始计划:
{plan}
已完成结果:
{results}
缺失信息:
{missing}
请输出调整后的计划。
""")
.param("plan", originalPlan)
.param("results", results)
.param("missing", missingInformation)
.call()
.entity(AgentPlan.class);
}
重新规划也必须经过 PlanValidator。
十三、状态机:把关键流程控制权交还给程序
对于创建工单、设备控制和审批流程,不应该完全依赖大模型自由规划。
可以使用有限状态机:
定义任务状态:
public enum EnergyTaskStatus {
RECEIVED,
ANALYZING,
EVIDENCE_INSUFFICIENT,
WAITING_CONFIRMATION,
CREATING_ORDER,
COMPLETED,
CANCELLED,
FAILED
}
定义允许的状态转移:
private static final Map<
EnergyTaskStatus,
Set<EnergyTaskStatus>> ALLOWED_TRANSITIONS =
Map.of(
EnergyTaskStatus.RECEIVED,
Set.of(EnergyTaskStatus.ANALYZING),
EnergyTaskStatus.ANALYZING,
Set.of(
EnergyTaskStatus.EVIDENCE_INSUFFICIENT,
EnergyTaskStatus.WAITING_CONFIRMATION,
EnergyTaskStatus.FAILED
),
EnergyTaskStatus.EVIDENCE_INSUFFICIENT,
Set.of(
EnergyTaskStatus.ANALYZING,
EnergyTaskStatus.FAILED
),
EnergyTaskStatus.WAITING_CONFIRMATION,
Set.of(
EnergyTaskStatus.CREATING_ORDER,
EnergyTaskStatus.CANCELLED
),
EnergyTaskStatus.CREATING_ORDER,
Set.of(
EnergyTaskStatus.COMPLETED,
EnergyTaskStatus.FAILED
)
);
状态转移校验:
public void transition(
EnergyTask task,
EnergyTaskStatus target) {
Set<EnergyTaskStatus> allowed =
ALLOWED_TRANSITIONS.getOrDefault(
task.status(),
Set.of()
);
if (!allowed.contains(target)) {
throw new IllegalStateException(
"不允许从%s转换到%s"
.formatted(task.status(), target)
);
}
task.changeStatus(target);
taskRepository.save(task);
}
即使模型要求跳过确认,状态机也不会允许:
ANALYZING → CREATING_ORDER
因为合法路径必须经过:
ANALYZING
→ WAITING_CONFIRMATION
→ CREATING_ORDER
十四、状态机中的 Agent 应该负责什么?
状态机负责确定:
现在处于哪个阶段
允许进入哪些状态
哪些操作必须审批
失败后如何恢复
Agent 负责处理状态内部的非确定性任务:
如何拆解分析问题
选择哪些查询工具
如何解释查询结果
是否需要补充证据
如何生成自然语言报告
职责划分:
| 能力 | Agent | 状态机 |
|---|---|---|
| 理解自然语言 | 是 | 否 |
| 动态选择查询工具 | 是 | 部分 |
| 分析复杂数据 | 是 | 否 |
| 控制合法状态转移 | 否 | 是 |
| 强制人工审批 | 否 | 是 |
| 限制重试次数 | 否 | 是 |
| 中断恢复 | 部分 | 是 |
| 业务事务一致性 | 否 | 是 |
十五、三种模式应该如何选择?
场景一:简单查询
查询设备AC-001昨天的能耗。
使用:
单次 Tool Calling
没有必要引入复杂规划。
场景二:探索性诊断
分析空压机能耗异常的可能原因。
使用:
受控 ReAct
因为下一步取决于查询结果。
场景三:多步骤研究
分析全厂过去一个月的异常耗能,并生成改进方案。
使用:
Plan-and-Execute
先拆分任务,再分步执行。
场景四:包含写操作
分析异常并创建检修工单。
使用:
状态机
+ Plan-and-Execute
+ Human-in-the-Loop
场景五:设备控制
判断异常后自动关闭设备。
仅靠 Agent 自主规划风险过高,必须采用:
固定工作流
+ 硬编码安全策略
+ 多级审批
+ 可回滚机制
十六、能源诊断 Agent 的混合架构
最终可以采用以下结构:
这套架构并没有把所有控制权交给模型。
它采用的是:
LLM处理不确定性
代码控制确定性
状态机控制安全边界
人工处理高风险决策
十七、检查点与中断恢复
长任务可能因为以下原因中断:
- 服务重启;
- 模型调用超时;
- MCP Server 不可用;
- 等待用户确认;
- 数据查询任务尚未完成;
- 达到当前执行时间限制。
因此,每一步执行后都应该保存检查点:
public record AgentCheckpoint(
String taskId,
EnergyTaskStatus status,
AgentPlan currentPlan,
List<StepExecutionResult> completedSteps,
int modelCallCount,
int toolCallCount,
BigDecimal estimatedCost,
Instant updatedAt,
long version
) {
}
恢复任务时:
加载最后检查点
→ 验证任务状态
→ 检查已完成步骤
→ 检查外部操作是否已提交
→ 从下一个安全步骤继续
写操作恢复时必须检查幂等键,不能直接重新执行。
十八、Agent 执行预算
每个任务都应该配置执行预算:
public record AgentBudget(
int maxPlanningRounds,
int maxToolCalls,
int maxSameToolCalls,
int maxReplanCount,
int maxInputTokens,
int maxOutputTokens,
Duration timeout,
BigDecimal maxCost
) {
}
参考限制:
最大规划次数:2
最大工具调用:12
同一工具最大调用:3
最大重新规划次数:2
任务超时:120秒
写操作自动执行次数:0
预算耗尽后,Agent 应返回:
任务尚未完成。
已确认:
1. 一号车间能耗增长18.6%;
2. 空压机AC-001贡献了67%的增量。
尚未确认:
1. 冷却系统是否发生故障。
停止原因:
已达到本次任务的最大工具调用次数。
不能为了给出完整答案而编造缺失结论。
十九、如何防止 Agent 无限循环?
可以从五个层面控制。
1. 步骤上限
currentStep < maxSteps
2. 重复调用检测
将工具名称和规范化参数生成指纹:
String actionKey =
toolName + ":" + hash(normalize(arguments));
相同调用连续出现时停止或要求重新规划。
3. 无进展检测
如果连续多轮没有新增证据:
newEvidenceCount == 0
则认为 Agent 停滞。
4. 工具级重试策略
参数错误:不重试,重新生成参数
网络超时:最多重试2次
权限不足:禁止重试
数据不存在:调整计划
业务冲突:请求用户处理
5. 终止条件
任务必须具备明确的成功和失败条件。
不能只让模型自行判断“什么时候觉得完成”。
二十、Agent 规划的安全规则
生产系统应至少遵循以下原则:
1. 模型不能扩大自己的权限
Agent 不能因为计划需要某个工具,就自动获得该工具权限。
2. 模型不能修改安全策略
检索文档、用户输入和工具结果都不能改变系统级规则。
3. 写操作必须独立确认
不能把用户最初提出的模糊目标视为对所有后续操作的授权。
4. 高风险操作不能仅由模型批准
例如:
停止设备
删除数据
修改告警阈值
批量关闭工单
必须经过确定性的审批流程。
5. 工具结果视为不可信输入
外部系统返回的文本也可能包含 Prompt Injection,不能将其作为更高优先级指令。
二十一、如何评测 Agent 的规划能力?
不能只看最终回答是否流畅。
| 指标 | 说明 |
|---|---|
| Plan Validity | 计划是否可执行 |
| Tool Selection Accuracy | 是否选择正确工具 |
| Parameter Accuracy | 参数是否正确 |
| Step Efficiency | 是否存在冗余步骤 |
| Goal Completion Rate | 是否真正完成目标 |
| Evidence Coverage | 是否获得必要证据 |
| Replan Accuracy | 失败后是否正确调整 |
| Loop Rate | 是否出现重复调用 |
| Recovery Rate | 中断后能否恢复 |
| Unauthorized Action Rate | 是否尝试越权操作 |
| Human Confirmation Accuracy | 是否在正确时机请求确认 |
| Cost per Successful Task | 每个成功任务的平均成本 |
测试用例应该覆盖:
正常流程
工具超时
空结果
错误参数
权限不足
证据冲突
用户拒绝确认
任务中断恢复
重复执行
恶意提示注入
二十二、常见反模式
1. 用一个巨大 Prompt 描述完整流程
随着规则增加,Prompt 会越来越难以维护。
应该将状态、权限、预算和转移规则放入代码。
2. 每一步都调用最强模型
简单参数提取和状态判断不需要使用高成本模型。
可以使用模型路由:
意图分类 → 小模型
复杂规划 → 强推理模型
内容总结 → 普通模型
确定性校验 → Java代码
3. Planner 生成计划后直接执行
计划必须经过工具、权限、依赖和预算校验。
4. 只保存最终答案
如果不保存步骤状态,系统无法审计和恢复。
5. 允许模型自由决定高风险操作
模型可以提出建议,但不能成为最终授权者。
总结
AI Agent 的自主规划不是让模型不受限制地循环调用工具。
三种规划模式分别解决不同问题:
ReAct
→ 解决动态探索问题
Plan-and-Execute
→ 解决复杂目标拆解问题
状态机
→ 解决流程控制和安全约束问题
生产级架构通常采用三者结合:
让大模型负责理解、规划和处理不确定性,让 Java 代码负责权限、状态、预算、事务和安全边界。
一个可靠 Agent 的关键,不是它能够自主执行多少操作,而是:
每个步骤可验证
每次调用可追踪
每个状态可恢复
每项权限不可绕过
每个任务能够终止
下一篇:
《A2A 多智能体协作实战:让能源分析、设备诊断与工单 Agent 协同工作》
参考资料:
469



被折叠的 条评论
为什么被折叠?



