27-Agent设计与停止机制
什么时候需要 Agent?工作流、循环预算与多 Agent 分工
固定流程越来越复杂时,很容易把所有步骤交给 Agent 决定。但自主选择增加灵活性,也增加循环、错误调用和恢复成本。先确定哪些步骤必须固定,才能划出 Agent 可以探索的空间。
Spring AI 接入片段使用 Java 17、Spring Boot 3.5.3 与 Spring AI 1.1.8;协议与第三方平台示例另行标明适用范围。
1. 把 Workflow、RAG 和 Agent 放回职责
资料获取与路径控制是不同问题。
Workflow 固定路径,Agent 决定下一步
Workflow 是事先定义好的执行流程。例如:校验登录用户,查询订单,再根据订单状态选择回复模板。遇到哪些分支、每个分支调用什么,开发者提前确定。
Agent 则由模型根据当前任务和工具结果决定下一步:先查订单,发现需要核对物流,再查询物流,最后形成回答。它的灵活性来自运行时选择,也因此增加了不确定性。
二者的边界不是“是否用了大模型”。固定的摘要流程里可以包含模型节点;Agent 也可以被放在一个固定流程的某个节点中。判断时看控制权:下一步由程序规则决定,还是由模型提出再由执行器审核?
对于必经的身份校验、审批和写入事务,固定流程更容易表达约束。对于输入变化较大、需要多次调查的问题,可以让 Agent 在受控范围内选择只读工具。
RAG 解决资料问题,不承担业务事务
RAG 的作用是把检索到的资料加入生成上下文。比如从售后制度中找出“预售商品的发货承诺如何计算”。它提供的是知识依据,不自动提供订单的实时状态。
订单是否发货应从业务系统查询;售后规则可以从经过授权的知识库检索。两者合起来才能回答完整问题。仅检索历史订单说明,不能证明今天这张订单仍在等待发货。
同样,检索出“可申请退款”的规则,不代表退款已经发生。任何写操作仍应经过明确的业务接口、授权和状态校验。Spring AI 将检索增强与工具调用分别提供为应用能力,组合方式由应用定义。RAG 文档、工具调用文档。
从一个最小流程开始
先限定需求:只能查询当前用户的订单和公开给该用户的售后规则,不能取消订单或自动退款。流程可以表示为:
登录身份 → 订单归属校验 → 查询订单快照
↓
检索适用版本的规则
↓
生成回答 → 核对引用 → 返回
这个流程可以全部由程序控制。模型仅负责把已经取得的事实和规则组织成自然语言。若订单不存在或不属于用户,流程直接停止,不把该订单内容送给模型。
订单快照应含查询时间、状态和业务编号;规则片段应含文档版本和来源标识。回答里明确区分“截至查询时的状态”和“制度规定”。否则用户容易把制度推导误认成已经执行的业务操作。
什么时候增加 Agent 节点?
如果用户可能询问订单、物流、发票和售后进度,固定分支不断增多,可以在身份校验之后增加一个调查节点。这个节点最多调用三类只读工具,最多进行四轮模型决策,最终返回结构化事实。
下面是应用层设计,不是某个框架的内置配置:
{
"task": "investigate_order",
"allowedTools": ["getMyOrder", "getMyShipment", "getMyInvoice"],
"maxModelTurns": 4,
"maxToolCalls": 6,
"deadlineMillis": 15000,
"writeOperationsAllowed": false
}
工具调用参数里可以有订单编号,但用户和租户由服务器的登录上下文确定。模型生成一个看起来合法的 userId,也不应该改变实际身份。
预期行为是:普通订单问题只调用订单工具;确实需要物流证据时才追加查询;预算耗尽时返回已确认事实和未完成项,不能编造调查结果。
失败路径决定流程是否可维护
检索没有命中时,应说明没有找到适用规则;订单服务超时时,应说明状态暂时无法确认。这两类失败需要分别记录,因为修复方法不同。
还要区分业务失败与技术失败。“订单尚未发货”是有效业务结果,不应触发技术重试;“下游连接超时”才可能需要受预算控制的重试。
观测记录至少包括请求标识、选中的路径、工具名、检索文档版本、停止原因和耗时。不要只保存模型最终回答;一段回答无法解释系统究竟做过什么。
比较固定流程和 Agent 时,用同一组问题、同一权限条件和同一工具集合,观察任务完成率、错误调用、延迟及费用。没有这样的基线,很难判断 Agent 的灵活性是否值得增加的维护成本。
2. 执行器怎样强制停止
模型轮次、工具次数、截止时间和费用独立计数。
先区分几个不同的计数
一次模型请求可能提出多个工具调用;一次工具调用又可能产生下游重试。所以“最多十步”如果没有定义,无法准确控制负载。
建议分别记录模型请求次数、工具调用次数、下游尝试次数、已使用 Token、累计费用估计和整体截止时间。一个预算耗尽,就停止相关后续操作。
对于并行工具,要在提交任务之前预留额度。若三个线程都先看见“还剩一次”,再同时发起调用,计数就失效了。额度更新需要原子操作;多实例共同执行同一任务时,还需要共享且有一致性保证的任务记录。
截止时间应贯穿整条链路
给每个工具设置十秒超时,并不能保证任务十秒内结束。连续五次调用可能耗时接近五十秒,队列等待、重试和生成回答还会继续占用时间。
应先确定整个任务的截止时间,每次调用根据剩余时间决定是否还能开始,以及允许该调用等待多久。进程内测量耗时可用单调时钟,避免系统校时改变持续时间判断。跨机器保存的截止时刻则需要明确时钟与误差处理。
下面是 Java 17 的独立预算示例,可放在 AgentBudget.java。它只管理单进程内的次数与截止时间,不执行模型或工具,也不计费:
import java.time.Duration;
public final class AgentBudget {
private final long deadlineNanos;
private final int maxCalls;
private int calls;
public AgentBudget(Duration timeout, int maxCalls) {
long nanos = timeout.toNanos();
if (nanos <= 0 || nanos > Duration.ofHours(1).toNanos()
|| maxCalls <= 0) {
throw new IllegalArgumentException("invalid budget");
}
this.deadlineNanos = System.nanoTime() + nanos;
this.maxCalls = maxCalls;
}
public synchronized long reserveToolCall() {
long remaining = deadlineNanos - System.nanoTime();
if (remaining <= 0) {
throw new IllegalStateException("DEADLINE_EXCEEDED");
}
if (calls >= maxCalls) {
throw new IllegalStateException("TOOL_LIMIT_REACHED");
}
calls++;
return remaining;
}
}
调用方取得剩余纳秒数后,仍要传给 HTTP 客户端、Future 等实际等待机制。只计算剩余时间而不约束执行,并不能实现超时。
费用预算要包含正在执行的请求
模型请求完成后才能得到准确用量,但等到完成后才判断费用,可能已经超出预算。因此发起请求之前需要根据输入规模、输出上限和模型价格预估并预留额度,完成后用实际用量结算。
预估必须承认误差。不同模型的 Token 计算、缓存费用和价格可能变化,价格表应带版本与生效时间。预算示例中的数字属于业务配置,不是模型服务的固定能力。
多请求并行时,预算可以用“已结算金额 + 正在执行的预留金额”判断。失败后是否退回预留,要看供应商是否仍有计费,不能默认 HTTP 失败就没有成本。
正常完成、主动停止和失败要分开
任务停止至少包含 COMPLETED、NEEDS_USER_INPUT、BUDGET_EXCEEDED、CANCELLED、FAILED。这些状态决定界面怎样提示,以及任务能否恢复。
预算耗尽时,不应让模型为了凑出答案再发起一次不受预算控制的总结请求。可以提前预留总结额度,或者用程序生成已确认事实与未完成项。
取消请求需要向下游传播,但不能承诺撤销已经完成的写入。执行器可以停止提交新任务;对于已经发送的业务操作,要查询其结果并记录最终状态。
识别循环,但不要误杀合理查询
同一工具、同一规范化参数连续出现,可以成为循环信号。例如订单状态没有变化,Agent 仍反复查询。记录最近调用签名与结果摘要,有助于触发停止或要求用户补充信息。
不过重复调用也可能有合理原因,例如等待异步任务结束。因此规则要结合工具语义:轮询工具设定固定轮询间隔和次数;普通查询重复若干次仍无新信息,则停止。
审计中记录停止原因、已使用预算、最后一次有效事实以及未完成操作。这样面试时可以说明“为什么停下”和“停下后系统知道什么”,而不只是说设置了最大迭代次数。
3. SubAgent 分工何时值得
建立单 Agent 基线,再比较独立任务与协调成本。
什么工作适合拆开?
独立的信息收集比较适合拆分。例如制度查询和物流查询彼此不依赖,都能返回明确事实。需要连续状态判断、共享大量上下文的任务,拆开后容易出现信息损失。
可以先问三个问题:子任务能否独立完成,结果是否有清晰结构,失败是否能局部处理?如果必须把全部对话与全部工具结果传给每个子任务,分工带来的上下文收益通常很有限。
SubAgent 也不是一定由另一个模型扮演。固定的检索组件、规则函数或普通服务可能已经足够。只需要确定性查询时,增加一个规划模型往往没有必要。
单 Agent 基线怎样建立?
保持相同模型、工具集合、业务权限和测试问题,先记录单 Agent 的任务完成情况。样本要包含正常问题、缺少编号、权限不足、下游超时以及资料冲突。
观察完成率、错误工具调用、依据完整度、整体延迟、费用和停止原因。不要只比较最终回答的长度。更长的回答可能只是重复了多个子任务的内容。
再运行分工方案,比较它在哪类问题上改善、在哪类问题上退化。没有真实评估之前,可以说“设计上允许并行查询”,不能写“性能提高了某个百分比”。
汇总时怎样处理冲突?
假设订单子任务返回“普通商品”,规则子任务却引用“预售商品”的规定。父任务不能仅根据语言表达更流畅的结果进行选择,需要核对适用条件和版本。
可以定义优先级:实时业务系统负责当前事实,正式发布且仍有效的制度负责规则,模型推断只能作为待确认结论。事实矛盾时返回冲突说明,或触发一个受限复核步骤。
子任务超时后也不能自动用其他子任务的结果替代。例如物流服务不可用,制度资料只能解释可能的处理方式,不能证明物流已经停滞。
汇总最好保留原始证据标识和查询时间。过早把证据压缩成自然语言,可能丢掉日期、金额单位和例外条款。
并行收益与运维成本要一起计算
并行执行可能缩短关键路径,却增加瞬时模型请求数和下游负载。模型账户存在并发限制时,多 Agent 可能排队更久;共享工具有低速率限制时,也未必能并行获益。
还要处理子任务取消、部分完成、结果过期和重复委派。父任务失联时,子任务是否继续运行?恢复后能否重用已经取得的事实?这些都需要持久化任务状态,而不是依赖模型“记得之前做了什么”。
观测上记录父子任务关系、各自预算、执行路径和最终采纳的证据。指标按问题类别比较,避免一个平均延迟掩盖少量非常昂贵的长尾任务。
如果分工只增加调用次数,没有改善任务完成质量,就保留单 Agent 或固定流程。设计的目标是可靠完成业务,而不是让架构图看起来更复杂。
4. 设计判断与适用边界
先用最简单的固定链路覆盖正常需求,确需运行时调查才增加 Agent 节点。认证、预算和写操作确认保持确定性;模型提出下一步,执行器检查是否允许。任务停止后返回已确认事实与未完成项,不通过猜测填满回答。
SubAgent 的价值在于有清楚边界的独立任务,而不是角色数量。总预算由父任务分配,子任务返回结构化证据与不确定项。对于只有固定查询的工作,普通服务函数可能比一个额外模型更清楚。
这篇 Agent 工程文章提供了工作流与自主执行的设计参照。本文用固定约束、工具预算和单 Agent 基线决定增加多少自主性。 Anthropic:Building effective agents。
5. 问答
问:Workflow 比 Agent 落后吗?
答:不是。流程确定、约束强的业务更适合固定编排。能力强弱应通过需求覆盖与失败行为评价。
问:用了 RAG 就是 Agent 吗?
答:不是。一次固定的检索再生成就可以实现 RAG,不需要模型自主规划。
问:提示词写最多五步够吗?
答:不够。执行器必须在调用前检查计数和剩余预算。
问:一个工具超时后可以无限重试吗?
答:重试消耗整体时间和尝试额度,还可能造成重复执行,需要明确上限。
问:SubAgent 越多,回答越准确吗?
答:没有这样的保证。共享同一误解的多个模型可能一致地产生错误。
问:父 Agent 可以完全相信子 Agent 吗?
答:需要检查证据、格式和权限边界,子任务输出不是授权依据。