在前两篇文章中,我们分别讨论了 AI Agent 的提示词传输、流式输出与异常处理,以及如何通过 Function Calling 让大模型安全调用业务系统。完成这些基础能力后,一个更现实的问题随之出现:当用户提出的并不是一次查询,而是一个需要多步执行的目标时,Agent 应该怎样工作?
例如,用户说:“分析本月销售下降的原因,生成报告,并通知负责人。”这项任务至少包含查询销售数据、计算同比环比、检索相关事件、形成结论、生成报告和发送通知。单次函数调用无法覆盖整个过程,系统需要具备任务规划、工具选择、状态管理和结果汇总能力。
本文将从工程实践出发,介绍 AI Agent 如何从“会调用工具”进化为“能够可靠完成复杂任务”的执行系统,并给出适合 yudao-cloud、Spring Boot 等 Java 项目的实现思路。

一、Function Calling 与 Agent 编排有什么区别?
Function Calling 解决的是“模型如何表达一次工具调用”。Agent 编排解决的则是“系统如何管理一系列相互关联的调用”。两者之间的关系类似于一块积木与一套完整的搭建流程。
| 能力 | Function Calling | Agent 编排 |
|---|---|---|
| 关注点 | 选择函数并生成参数 | 完成一个多步骤业务目标 |
| 执行次数 | 通常是一轮或少量调用 | 可能包含循环、分支和并行步骤 |
| 状态 | 依赖对话上下文 | 需要持久化任务与步骤状态 |
| 控制方式 | 模型决策为主 | 模型决策与确定性代码结合 |
| 异常恢复 | 返回工具错误 | 重试、补偿、降级或人工接管 |
因此,不应把“模型连续调用了几个函数”直接等同于一个生产级 Agent。真正的编排层还必须回答:当前执行到哪一步?哪些步骤可以并行?失败后能否恢复?是否超过预算?写操作是否经过确认?
二、复杂任务是如何被拆解的?
任务规划的输入是用户目标,输出是一组可以执行和验证的步骤。一个合格的计划,不应该只有模糊的自然语言,而应包含步骤编号、依赖关系、候选工具、预期结果和完成条件。
{
"goal": "分析本月销售下降原因并通知负责人",
"steps": [
{
"id": "s1",
"tool": "query_sales_metrics",
"depends_on": [],
"success_condition": "返回本月及对比周期的销售指标"
},
{
"id": "s2",
"tool": "analyze_sales_change",
"depends_on": ["s1"],
"success_condition": "输出按影响程度排序的原因"
},
{
"id": "s3",
"tool": "create_report",
"depends_on": ["s2"]
},
{
"id": "s4",
"tool": "send_notification",
"depends_on": ["s3"],
"requires_confirmation": true
}
]
}
常见规划方式主要有三种。第一种是固定工作流,由代码预先定义执行顺序,适合审批、退款和工单等规则明确的业务;第二种是动态规划,由模型根据目标生成步骤,适合研究、分析和开放式任务;第三种是混合编排,由代码规定安全边界和关键节点,模型只在允许的范围内选择工具或补充步骤。
企业系统通常更适合混合编排。完全固定的流程不够灵活,完全交给模型又难以预测。可以把登录校验、数据权限、人工确认和写操作固化在代码中,把查询顺序、分析方法和内容组织交给模型。
三、用“规划—执行—观察”循环完成任务
一个常见的 Agent 执行循环由四个阶段组成:
- 规划:根据用户目标和已有信息,决定下一步动作。
- 执行:应用程序校验参数和权限,然后调用工具。
- 观察:把结构化结果或错误写回任务上下文。
- 判断:检查目标是否完成;未完成则继续下一轮。
这个循环必须由应用程序控制,而不是让模型无限运行。下面是一段简化的 Java 伪代码:
public AgentResult run(AgentContext context) {
for (int round = 0; round < context.getMaxRounds(); round++) {
AgentDecision decision = planner.next(context);
if (decision.isFinished()) {
return AgentResult.success(decision.getAnswer());
}
ToolDefinition tool = toolRegistry.get(decision.getToolName());
policyService.check(context.getUser(), tool, decision.getArguments());
ToolResult result;
try {
result = toolExecutor.execute(tool, decision.getArguments());
} catch (RetryableToolException ex) {
result = retryExecutor.execute(tool, decision.getArguments());
}
context.appendObservation(decision.getCallId(), result);
context.consumeBudget(result.getCost());
}
return AgentResult.failed("MAX_ROUNDS_EXCEEDED", "任务步骤超过限制");
}
在 yudao-cloud 中,可以将这些职责拆分到独立组件:Planner 负责请求模型,ToolRegistry 管理工具元数据,PolicyService 执行权限与风险检查,ToolExecutor 调用现有 Service,AgentTaskService 保存任务状态。这样可以避免把模型调用、业务代码和安全判断堆在同一个类中。
四、工具注册表是编排系统的核心
当工具数量从三个增长到几十个时,把全部工具描述都放进提示词会增加 Token 成本,也容易让模型选错。工具注册表应保存名称、描述、参数 Schema、风险等级、超时时间、幂等属性和所需权限。
public record AgentToolMeta(
String name,
String description,
String permission,
RiskLevel riskLevel,
boolean idempotent,
Duration timeout
) {}
运行时可以先根据用户意图筛选候选工具,再把少量相关工具交给模型。例如,“查询订单物流”只暴露订单和物流工具,不应同时暴露删除用户、修改角色等后台能力。工具越少、边界越清晰,选择准确率和安全性通常越高。
工具层还应保持业务语义清晰。不要提供 execute_sql、request_any_url 这类能力过宽的工具,而应提供 get_my_order、search_product_inventory、create_refund_application 等范围明确的操作。
五、串行、并行与条件分支怎么选择?
多工具并不意味着必须依次调用。如果两个步骤之间没有依赖关系,就可以并行执行。例如,在制作竞品报告时,搜索新闻、查询内部销售数据和读取用户反馈可以同时进行;但“生成报告”必须等待这些结果全部返回。
- 串行:后一步依赖前一步结果,适合查询订单后申请售后。
- 并行:多个只读任务相互独立,适合多来源检索与指标查询。
- 条件分支:根据工具结果选择不同路径,例如库存不足时转为创建补货单。
- 人工节点:高风险动作暂停执行,等待用户确认后继续。
并行能够降低总耗时,但不能只看速度。数据库压力、接口限流、结果顺序和写操作冲突都需要考虑。默认只并行执行无副作用的查询工具;对于扣款、发券、删除和通知等操作,应按业务顺序执行并使用幂等键。
六、任务状态不能只存在模型上下文里
长任务可能跨越几十秒甚至数分钟,也可能因为网络中断或服务重启而暂停。如果状态只保存在内存或对话消息中,系统就无法可靠恢复。建议至少保存任务表和步骤表。
| 对象 | 关键字段 | 作用 |
|---|---|---|
| Agent 任务 | task_id、user_id、goal、status、budget | 记录总体目标和生命周期 |
| 执行步骤 | step_id、tool、arguments、result、status | 记录每一次工具执行 |
| 确认记录 | action、operator、confirmed_at | 证明高风险操作已获授权 |
| 调用日志 | call_id、latency、tokens、error_code | 用于监控、审计和成本统计 |
任务状态可以设计为 PENDING、RUNNING、WAITING_CONFIRMATION、SUCCEEDED、FAILED 和 CANCELLED。步骤状态则包括待执行、执行中、成功、可重试失败和终止失败。服务重启后,调度器可以扫描未完成任务并按照状态恢复,而不是让模型从头猜测。
七、如何防止 Agent 陷入死循环?
模型可能重复调用同一个工具,也可能在缺少关键参数时不断尝试。因此,编排层必须设置硬性预算:
- 最大模型轮次和最大工具调用次数;
- 单个工具与整个任务的超时时间;
- Token、费用和并发上限;
- 同一工具加相同参数的重复次数;
- 允许读取和写入的数据范围。
如果 Agent 连续两次使用相同参数调用同一工具并得到相同结果,通常意味着规划没有取得进展。系统应终止循环或请求人工补充信息,而不是继续消耗资源。预算耗尽时,还应保留已完成的步骤和中间结果,让用户知道任务做到哪里、为什么停止以及如何继续。
八、写操作必须经过确定性安全控制
模型生成的计划只是建议,不是授权。即使模型正确选择了“发送通知”工具,应用仍需检查当前用户是否有权限、接收人是否在允许范围、内容是否包含敏感信息,以及用户是否确认了发送动作。
可以按风险把工具分为三级:
- 低风险:读取公开信息、查询本人数据,可以自动执行。
- 中风险:创建草稿、生成待办、修改非关键配置,应展示结果并允许撤销。
- 高风险:转账、退款、删除、对外发送和权限变更,必须二次确认。
二次确认不能只是让模型问一句“是否继续”。应用应生成不可篡改的确认对象,明确展示动作、目标、关键参数和影响范围。用户确认后,由服务端校验确认记录与当前请求完全匹配,再执行操作。
九、可观测性决定 Agent 是否能上线
传统接口通常关注状态码与耗时,Agent 还需要观察决策链路。一次完整任务应能追踪用户目标、模型轮次、工具选择、参数校验、执行结果、Token 消耗和最终状态。
建议重点统计以下指标:
- 任务成功率、平均完成时间和人工接管率;
- 工具选择正确率与参数校验失败率;
- 每类工具的耗时、错误率和重试次数;
- 单任务模型调用次数、Token 与费用;
- 重复调用率、预算耗尽率和用户取消率。
日志中应使用统一的 task_id、step_id 和 call_id 串联全链路,同时对提示词、参数和工具结果进行脱敏。生产环境不要直接记录密码、密钥、身份证号或完整业务数据。
十、上线前检查清单
- 复杂任务是否被拆成可执行、可验证的步骤?
- 固定流程与模型动态决策的边界是否清晰?
- 工具是否有准确描述、参数 Schema、权限和风险等级?
- 是否只向模型暴露当前任务需要的候选工具?
- 任务和步骤状态是否可以持久化与恢复?
- 是否限制轮次、调用数、超时、Token 与费用?
- 并行执行是否仅用于相互独立且安全的步骤?
- 写操作是否具备幂等、权限校验、确认和审计?
- 失败后能否重试、降级、补偿或转人工?
- 是否使用真实任务集评估完成率,而不只是观察演示效果?
结语
Function Calling 让模型获得调用工具的能力,任务规划与多工具编排则让这种能力形成可控的执行闭环。一个可靠的 Agent,不仅要知道“下一步调用什么”,还要知道为什么调用、依赖什么、怎样判断成功,以及失败后如何安全退出。
在 yudao-cloud 等企业级项目中,最稳妥的设计不是让大模型接管整个系统,而是让它负责理解意图、选择候选动作和组织结果;应用代码继续负责身份、权限、事务、状态、预算和审计。模型提供灵活性,确定性代码守住业务边界,两者结合才能构建真正可上线的 AI Agent。
下一篇可以继续讨论 Agent 的长期记忆与 RAG:哪些信息应该进入对话上下文,哪些应该持久化,以及如何通过检索让 Agent 在长期任务中保持准确、一致和低成本。
本文来自投稿,不代表知派立场,如若转载,请注明出处:https://www.zinpai.com/news/4608.html