这篇解决什么困惑
给 Agent 一个 modelTokenBudget: 2000,并不等于它最多花 2000 个 Token。预算究竟约束什么?模型供应商没有返回 usage 时怎么办?工具已经执行了一半,预算耗尽后应该继续还是停止?
预算不是一个配置字段,而是 Harness 对时间、步骤、模型和外部副作用施加的共同边界。
当前已经有什么
Agent Loop 目前有三类确定性护栏:最大步骤数、deadline 和模型 Token 预算的存在性检查。每一步开始前都会检查这些边界;达到上限时,系统不会再偷偷向模型请求一次决策。
这已经能防止最直接的失控,但它还不是完整成本控制。当前代码还没有根据每次供应商返回的真实 usage 持续扣减预算,也没有把不同模型、工具时间和重试成本换算成统一费用。
为什么 0/0 不代表零消耗
Spring AI 或供应商适配器可能在响应对象里留下占位用量。若把 inputTokens=0、outputTokens=0 直接累加,报表看起来很准确,实际却只是“没有拿到数据”。
当前 Trace 把用量分为 RECORDED 与 UNAVAILABLE。只有真实元数据存在时才累加 Token;缺失时保留不可用状态,并通过 usageComplete 告诉查询方不能把聚合结果当作完整账单。
可靠系统宁可显示“未知”,也不能把未知伪装成零。
预算应该在哪些边界检查
收到请求
→ 校验最大步骤 / deadline / Token 上限
→ 决策前再次检查
→ 外部调用前检查时间与权限
→ 结果落盘并记录真实 usage
→ 计算剩余预算
→ 继续、降级或停止
至少需要检查四次:请求进入时防止明显超限;模型决策前防止无意义调用;工具执行前防止已经没有时间或权限;外部结果返回后更新实际消耗。
只在循环最外层检查一次,会给工具和重试留下预算盲区。
步骤预算、Token 预算和费用预算
三者不能互相替代:
| 预算 | 约束对象 | 典型停止原因 |
|---|---|---|
| 步骤 | Loop 迭代次数 | 防止原地循环 |
| 时间 | wall-clock deadline | 防止请求无限等待 |
| Token | 模型输入和输出 | 防止上下文或重试膨胀 |
| 费用 | 不同模型与工具的价格 | 防止账单超出授权 |
一个运行可能没有超出步骤数,却因为上下文变长消耗大量 Token;也可能 Token 很少,却因为工具反复重试占满时间。预算要分别记录,再由停止策略综合判断。
预算耗尽后怎么办
停止不是唯一动作,但每种替代方案都需要明确边界:
- 直接停止:最安全,返回当前已验证的部分结果;
- 降级模型:需要预先定义哪些模型可以替代,以及上下文是否兼容;
- 缩小上下文:只能丢弃非必要信息,不能丢掉完成结论所需的证据;
- 转人工:适合已有部分结果但继续执行风险较高的任务。
不能让模型自己决定“为了完成任务再多花一点”。预算授权属于 Harness,不属于被预算约束的模型。
恢复与预算的关系
Checkpoint 恢复时,已经落盘的工具结果不会重复执行;崩溃窗口中的不确定调用可能重放。这个重放本身也要计入预算,否则每次故障都会获得一份隐藏的免费额度。
因此未来的真实扣减应和调用结果、重试次数、模型 usage 一起进入同一个受守卫的事务边界。预算扣减失败时,不能推进 Checkpoint,也不能让 Trace 看起来像调用已经被授权。
还缺哪些能力
当前仍缺少供应商真实 usage 驱动的预算扣减、模型价格表版本化、重试成本核算和费用上限 API。工具执行时间和外部服务费用也还没有统一映射到同一个预算模型。
这些缺口并不影响当前 Loop 的学习和固定集验证,但意味着它还不能承诺“每次运行最多花多少钱”。
最终判断
预算设计的核心不是把数字写大或写小,而是让系统知道自己知道什么:步骤和 deadline 可以确定地约束,Token 和费用只有拿到真实元数据后才能精确扣减,未知用量必须保持未知。
一个成熟的 Agent 不会为了完成任务突破预算;它会在预算耗尽时给出可解释的停止、降级或转人工结果,并把这次决定留下足够的证据。
更新时间:2026-08-30