阻止智能体循环的外部调用预算
外部调用预算为请求次数、时间和支出设置上限,防止自主智能体无控制地重试、轮询和购买。

自主智能体不需要无限访问外部服务,也能发挥作用。它们需要一份明确的权限额度,在尝试、等待、重试和支出达到上限后,把工作交还出来。没有这条边界,一个小小的歧义,例如搜索结果为空或任务延迟,就可能变成数百次请求、重复的副作用,以及一笔无人预料的账单。
外部调用预算是单次智能体运行时的一份契约。它限制请求尝试次数、经过时间和资金敞口,并在任一上限耗尽时强制停止,同时留下清晰可见的停止结果。应把它当作运行限制,而不是智能体提示中的建议。模型为了完成任务,很容易重新解释提示;真正的执行控制必须放在模型之外。
预算必须分别计算尝试次数、时间和金额
单一限制无法覆盖智能体消耗资源的各种方式。请求数量可以发现循环。经过时间限制可以发现缓慢轮询和重试等待。支出限制可以拦住少量但昂贵的操作。每项指标对应一种不同的故障模式,即使另外两项仍有余量,也可以单独结束运行。
统计尝试次数,不要只统计成功响应。一次超时的请求仍可能已经到达服务商。它消耗了连接、服务商的处理能力,也常常会在计量 API 中产生一条记录。如果只统计成功请求,智能体就能不断发出失败请求,直到找到一个自己喜欢的响应。
每次运行至少记录以下值:
attempts_used和attempts_remaining,包括会发起新外部请求的重试和重定向deadline_at,使用单调时钟计算经过时长,而不是使用墙上时钟reserved_spend和settled_spend,以服务商实际使用的最小计费单位记录side_effect_attempts,单独统计写入、发送、创建或购买等操作budget_stop_reason,记录第一个拒绝后续工作的限制
不要给每个请求虚构一个价格,再把请求数和支出合并。元数据查询和模型生成端点可能都只算一次请求,但账单金额可能相差很大。反过来,某个 API 可能不报告请求价格,却会创建一个需要付费的云资源。即使已有成本估算,也应保留请求上限。
对于从一组已知 API 收集数据的智能体,一个有用的初始预算可能是总共 40 次尝试、10 分钟运行时间和一笔较小的固定预留。部署型智能体可能需要更少请求,但应有更严格的副作用额度。会跟随发现的 URL 的研究型智能体,其可触达范围应远小于作者最初提出的范围。这些只是起始假设,不是通用设置。先测量正常运行,再把限制设得足够接近异常行为的边界,以便在问题变成清理工作前中断它。
预算属于一次运行,而不是当天的模型进程。智能体收到新任务时,应获得新的运行标识和新的明确额度。共享的每日计数器会把昂贵的运行隐藏在正常工作中,也会让一个任务消耗原本留给另一个任务的容量。
第一次高成本调用前必须先预留额度
如果系统要等到购买之后才知道价格,支出上限就会失效。调用前先预留最高可能费用,立即减少剩余预算,等服务商返回实际用量后再核对。如果预留金额无法容纳这次调用,就应在请求离开你的边界前拒绝它。
考虑一个可以创建托管构建的智能体。服务商的请求会接受机器类型、区域、超时时间和产物大小,但确切费用要在完成后才显示。智能体网关需要价格表或保守估算,不需要完美的账务系统才能做出安全决策。
预留记录可以写成这样:
{
"run_id": "run_7c1f",
"budget": {
"attempts_remaining": 18,
"deadline_at": "2025-04-21T14:42:00Z",
"spend_remaining_cents": 1200
},
"reservation": {
"action": "create_build",
"maximum_cents": 850,
"provider_reference": "build-request-41"
},
"decision": "allow"
}
完成这次预留后,运行只剩 350 美分可用于其他操作。如果实际账单结算为 620 美分,就释放 230 美分。如果结算为 910 美分,就记录超支、拒绝后续支出,并调查估算为何偏差。不要因为最终数字不方便,就让智能体随意借用未来预算。
有些服务商会在请求中提供价格,或在工作开始前返回用量估算。应使用这些信息。有些服务则不会。对于这类服务,应按风险对操作分类。普通预算下可以允许廉价读取。凡是可能创建无上限资源、发送付费消息、下单,或触发无法限定成本的工作,都应要求明确审批。
团队常因估算不准确而拒绝预留。这混淆了账务精度和控制能力。防火门在关闭前不需要精确计算火灾会产生多少热量。保守预留可能会拒绝一项原本可以完成的任务,但智能体可以说明理由,请求更大的额度。这总比智能体消失后才发现一笔没有上限的费用要好。
把服务商积分和组织级配额放在运行预算之外。它们是最后一道防线,不能替代运行预算。月度配额通常允许单次运行消耗远超任务应得的资源。
重试的额度应小于首次尝试
重试应当少见、有上限,并根据重复请求是否可能重复副作用进行分类。笼统地指示智能体重试错误,正是把临时故障变成昂贵流量模式的方式。
RFC 9110 根据预期的服务器效果定义了 GET、PUT 和 DELETE 等幂等方法。它还指出,除非客户端知道重复请求是安全的,否则不应自动重试非幂等请求。对智能体来说,这一限制比普通应用代码更重要:智能体可能在尝试之间改变载荷,认为之前的响应不完整,然后发出一个看似全新的请求。
RFC 6585 定义了 HTTP 429 Too Many Requests,并指出响应可能包含 Retry-After。如果存在该标头,应遵守它。但不要把它当成可以一直睡到标头到期、之后无限恢复的许可。重试仍会消耗运行的时间预算,原始任务也可能已经不值得继续等待。
使用重试台账,在每次重复之前回答四个问题:发生了什么故障,远程操作是否可能已经发生,应等待多久,以及再次尝试由哪项预算支付。一项实用策略如下:
request_classes:
read:
max_attempts: 3
retry_on: [408, 429, 502, 503, 504]
backoff_seconds: [2, 8]
idempotent_write:
max_attempts: 2
require_idempotency_token: true
retry_on: [408, 429, 503]
non_idempotent_write:
max_attempts: 1
retry_on: []
这项策略可以避免一种常见故障。智能体发送 POST /invoices 后,在收到响应前连接断开。草率重试可能会创建第二张发票。幂等令牌可以让配合的服务商识别重复请求,但前提是智能体重复使用同一个令牌和完全相同的逻辑操作。如果智能体在第二次尝试时改变金额、客户或令牌,这种保护就不再适用。
对于非幂等操作,应优先使用由客户端生成的操作标识查询状态。如果服务商不支持幂等性或状态查询,就应把不明确的超时交给人工审核。只要有人需要撤销重复转账、订单或消息,自动重试看起来更快的好处就会消失。
当许多智能体同时遇到相同故障时,抖动很重要。使用随机退避,避免它们同时重试。时间表也要足够短,让运行截止时间保持实际意义。六小时的指数退避也许能保护服务商,却会让智能体会话在任务已经失去意义后仍然长时间保持打开。
轮询循环需要单独的上限
等待异步工作的智能体应获得明确的轮询额度,因为普通请求限制往往等到模式已经造成高昂成本后才会暴露问题。轮询有合理用途,但必须设置间隔、最大检查次数,以及属于该操作的截止时间。
日志中很容易看出错误的写法:
14:00:03 POST /exports 202 accepted
14:00:04 GET /exports/ea91 202 running
14:00:05 GET /exports/ea91 202 running
14:00:06 GET /exports/ea91 202 running
...
14:11:58 GET /exports/ea91 202 running
智能体把“仍在运行”理解成了“再次询问”。这不是坚持,而是缺少策略。
从服务商公开的指导开始。如果端点返回 Retry-After,就在剩余截止时间内遵守它。如果返回预计完成时间,在该时间之前不要轮询。如果提供 webhook 或回调,就用它代替让智能体运行一直保持打开。外部回调可以稍后恢复受控工作流,但不应使用旧权限复活已经过期的运行。
轮询预算还应区分任务状态和传输故障。202 running 表示任务存在。超时并不能告诉你状态请求是否到达。不要对这两种情况使用同一套重试额度。前者可以等到下一次计划检查,后者可以在普通重试限制内合理地再试一次。
轮询额度到期时,应设置明确的终止动作:记录任务最后已知状态,保留远程操作标识,并返回恢复指令。除非原始任务说明取消是安全的,否则不要自动取消。有些任务在智能体放弃等待后仍会留下可用结果,而某些取消请求本身也会产生副作用。
这种设计会让延迟工作对人保持可见。“导出在六次检查后仍在运行;稍后可以检查操作 ea91”是一份有用的交接。“智能体已完成”如果背后隐藏着后台循环,就不是。
截止时间必须覆盖等待、工具和队列时间
运行截止时间应衡量智能体能够造成外部工作的整个期间。它必须包括调用之间的模型推理、重试等待、本地工具执行、队列延迟、DNS 卡顿,以及等待审批的时间。只在 HTTP 客户端周围设置计时器,会留下很大的空白,让循环继续运行。
使用单调经过时间计时器执行这一限制。笔记本睡眠、恢复或收到时间校正时,墙上时钟可能跳变。审计记录可以保存墙上时钟时间戳,但过期计算应使用不会倒退的经过时间。
把剩余时间传递给每个操作。如果运行只剩 40 秒,就不应启动一个客户端超时为 90 秒的 HTTP 请求,也不应调用完全没有超时设置的 SSH 命令。子操作应取自身上限与运行剩余时间两者中更小的值。
remaining = run_deadline_monotonic - now_monotonic
if remaining \u003c= 0:
deny("run_deadline_exhausted")
else:
operation_timeout = min(remaining, endpoint_timeout)
execute(operation_timeout)
应谨慎处理审批等待。人可能五分钟后返回,但智能体运行可能只剩 30 秒。审批在过期后到达时,应拒绝操作并说明原因。允许审批唤醒已经过期的运行,会制造一个漏洞:智能体可以在截止时间前排队任意数量的高成本操作,等人稍后逐个点击卡片再执行。
长任务需要另一种结构。把它拆成多个检查点,每个检查点都有新的预算和记录在案的状态转换。例如,智能体可以在一次运行中提交导出,之后由另一次运行检查已完成的导出,并决定如何处理。每次运行都有明确目的、小范围权限和清晰的结束时间。这不像一个永生的智能体会话那样神奇,但正因如此,更容易审计。
范围限制可以防止智能体把预算花在错误的地方
预算回答一次运行可以进行多少外部活动。范围回答它可以接触哪里、接触什么。没有范围限制,智能体可以把看似合理的请求额度花在探测无关主机、跟随恶意链接,或选择任务从未要求的高成本端点上。
用具体内容定义范围:获准主机、凭据身份、HTTP 方法、SSH 目标、允许的路径或命令族,以及最大请求体大小。定义应足够窄,让审核者能够理解。不要试图为每种可能条件编写一门微型编程语言。复杂的策略系统会不断积累例外,最终没人能在压力下预测它的结果。
凭据边界和预算边界的区别很重要。凭据边界决定智能体是否可以使用某个秘密调用服务。预算边界决定智能体在获得这项权限后,本次运行是否还能再调用一次。团队常常只安装第一种边界,就以为它能提供第二种保护。事实并非如此。一个保护完善的 API 令牌仍可能为一千次不必要的请求买单。
对于 HTTP,如果重定向离开获准主机集合,就应拒绝它,除非有人明确允许目标地址。重定向很容易被忽略,因为许多客户端会自动跟随。一个从获准 URL 开始的请求,最终可能到达另一台主机,携带请求头,或把预算消耗在任务从未指定的服务上。
对于 SSH,不要仅因为智能体的第一项工作涉及一条命令,就给它一个通用 shell。尽可能限制目标和命令接口。设置命令超时,并统计每次连接尝试。连接失败循环仍然属于外部活动,即使它从未完成身份验证。
把读取范围和写入范围分开。数据收集任务可能需要广泛的读取端点,却完全不需要创建记录的能力。变更任务可能只需要一个写入端点和一次范围很窄的预检读取。这种划分能让预算更有意义,因为单靠请求数量无法区分无害重复和反复产生副作用的操作。
拒绝必须留下足够证据,以便还原循环
预算停止只有在事后能够解释时才有用。外部调用开始前记录每次决策,完成后记录结果,并像记录成功调用一样认真地记录拒绝。否则,缺失事件看起来就和被阻止的请求完全一样。
在模型消息、本地工具、HTTP 请求、SSH 连接、审批和审计记录之间使用同一个不可变运行标识。每次外部尝试都应包含序号。如果发生重试,应将其关联到原始逻辑操作,并说明该操作是幂等的、状态不明确的,还是已确认未发生。
一条紧凑的事件记录应包含足够的调查信息,但不必保存敏感载荷:
{
"run_id": "run_7c1f",
"attempt": 17,
"logical_operation": "fetch_export_status:ea91",
"channel": "http",
"destination_class": "approved-export-api",
"outcome": "denied",
"reason": "poll_allowance_exhausted",
"attempts_remaining": 0,
"elapsed_ms": 598244,
"reserved_spend_cents": 0
}
默认不要记录 bearer 令牌、密码、私钥或完整请求体。预算审计需要标识符、类别、必要时的哈希和决策背景,不需要再建一个装满相同凭据的秘密存储库。
让事件序列具备防篡改能力。哈希链让每条记录都依赖前一条,因此删除或修改记录会破坏验证。条件允许时,应把验证材料存放在运行中的智能体之外。能够写入自身历史的智能体,不能同时编辑证明自己超出预算的记录。
Sallyport 使用一种对写入不可见的加密哈希链审计日志,同时记录智能体会话和单独调用;其 sp audit verify 命令可以在密文上离线验证链。这种设计对预算执行很有用,因为被拒绝的操作会作为证据的一部分保留下来,而不会作为内部决策消失。
人工审批应处理例外,而不是每次读取
人工审批应处理数字预算无法判断的后果或范围变化,不应变成例行调用的橡皮图章。如果每次请求都弹出审批卡片,人们会用处理第一个提示的注意力去批准第十个提示。
当操作新增收款方、支出超过预留、写入生产系统、发送外部可见的通信、改变获准目标集合,或在任务输入发生重大变化后恢复时,应请求审批。审批内容应以通俗语言展示操作、目标、预算影响,以及发起请求的进程身份。
不要把审批当作弥补预算薄弱的办法。分心的人可能批准错误操作,无人值守的运行也可能一直收不到回应。等待审批时,预算仍应过期。如果任务仍值得继续,人可以为新运行授予新的额度。
避免让人审批一个没有上下文的“继续”。继续什么?针对哪项服务?预计费用是多少?运行已经发出多少次调用?好的审批请求会让这些事实清晰可见。含糊的请求会迫使审核者相信智能体的总结,而智能体恰恰有继续工作的动机。
两者可以明确分工。网关负责一致地执行固定限制。人负责判断特殊操作是否值得获得更多权限。保持职责分离,双方都不必假装能够解决对方的问题。
预算耗尽后应生成可恢复的交接信息
限制停止运行时,智能体应返回一份简短的状态记录,让人或后续运行能够安全继续。含糊的失败消息会促使人从头重跑任务,重复已经完成的调用,也可能重复副作用。
交接信息需要包含任务目标、已完成的操作标识、待处理的远程工作、确切的停止原因,以及对下一次额度的建议。它应说明重试是否安全,绝不能隐瞒操作最终处于不明确状态这一事实。
例如:
Stopped: elapsed-time budget exhausted.
Completed: submitted export job ea91; downloaded no files.
Last observed state: running at 14:09:58 UTC.
External attempts: 14 of 14; spend reserved: 0 cents.
Safe resume: check status of ea91 once after 14:20 UTC.
Unsafe action: do not submit another export request.
这条消息能避免最昂贵的恢复方式:因为没人知道发生了什么,只好从头开始。它也让预算调整成为可能。如果许多正常运行在一次状态检查后就停止,说明允许的时长太短。如果运行经常在没有进展的搜索上耗尽全部额度,说明任务拆分或范围设置有问题。
预算耗尽后不要自动补充额度。除非有独立权限决定何时适用,否则补充规则只是把无限预算写成更小的增量。应要求开启新运行或执行明确的人工操作,保留旧记录,并让下一次尝试说明为什么值得获得更多空间。
最值得首先执行的策略很简单:每次自主运行在接触外部服务前,都必须获得有限的请求次数、硬性截止时间和支出预留。随后立即加入范围限制和完善记录。当智能体知道自己不能永远尝试时,失败就会变成受控的工作项,而不是一夜之间演变成事故。
常见问题
智能体的调用预算和速率限制有什么区别?
请求限制是对外部操作尝试次数的上限,包括重试和失败调用。速率限制控制操作发生的速度。通常两者都需要,因为一个运行缓慢的智能体可能不会触及速率限制,却能在几小时内耗尽数千次调用。
重试是否应计入自主智能体的请求预算?
除非服务商明确确认没有收到请求,否则应把重试计为一次新的尝试。即使第一次尝试失败,重试也会消耗服务商容量、时间,而且通常还会产生费用。忽略重试的预算,会给循环留下绕过限制的免费通道。
如何为轮询任务状态的智能体设置预算?
为轮询请求设置专门的预算,每次轮询都从中扣除。更好的做法是,在服务商提供这些能力时,使用 webhook、回调,或带有明确轮询间隔的操作状态端点。轮询很容易产生低价值调用,因为智能体会把等待看成可以通过再次询问来解决的问题。
供应商延迟报告费用时,如何限制支出?
在工作流开始前分配一个最高预留金额,然后在每次收到费用或用量信息后核对实际使用情况。即使最终账单稍后才到,也应在预留额度耗尽时停止。如果服务商无法提供可信的价格信号,就把该操作设为仅限审批,或只给它非常小的调用额度。
幂等键是否让外部调用预算变得不必要?
不能。幂等键可以让配合这一机制的服务商避免重复副作用,但无法阻止重复读取、重复状态检查、令牌成本,或输入已改变的新请求。幂等性和预算应作为两项独立控制保留。
应允许智能体调用外部服务多长时间?
为特定操作设置一个较短的截止时间,再为完整运行设置另一个截止时间。操作截止时间应反映正常服务延迟,并为重试留出适度余量。运行截止时间应由任务对人的价值决定,而不是由无人值守进程能够忍受的最长时长决定。
AI 智能体耗尽预算后应该做什么?
让智能体保存剩余任务状态,报告导致停止的预算,并返回一份可恢复的计划。不要悄悄重置计数器,也不要用新身份启动替代运行。没有有用记录的停止,只会把昂贵的循环变成调查工作。
研究型智能体是否需要比部署型智能体更严格的请求限制?
如果智能体能够发现新目标、跟随链接、广泛搜索,或生成任意请求载荷,就应使用更严格的限制。针对小型允许列表运行的静态工作流可以使用更大的额度,因为其可触达的操作集合是已知的。探索性工作需要更小的预算和更频繁的人工审核。
什么时候应要求人工审批智能体的外部调用?
当操作涉及新增收款方、破坏性动作、大额支出预留或范围扩大时,应要求人工审批。不要让人审批每个无害的读取请求,因为人们最终会不看内容就批准提示。预算负责处理数量,审批负责处理特殊后果。
审计日志应记录智能体预算停止的哪些信息?
使用带签名或哈希链的事件记录,其中包含不可变的运行标识、时间戳、尝试序号、请求类别、预算决策和成本估算。被阻止的调用应与获准调用一样认真地记录。没有拒绝记录,就无法区分安全停止和集成故障。