重试放大。
一个用户任务不会只产生一个请求。它产生的是「步数上限 × 并行工具调用 × 子智能体扇出 × 技术栈里每一层重试策略」的乘积——而正是这个乘积、不是你的 token 单价,定下了你的账单,决定你是在 40 个并发用户还是 400 个并发用户时撞上供应方的限流,也决定你碰到的那些系统是把你当成一个客户端、还是当成一台扫描器。这个数通常在 10 倍到 100 倍之间,几乎没人测过它,也没有人选择过它:它是「四层各自重试三次」之后剩下的那个意外。
这个倍数是一个乘积,而它是由四个团队各自拼出来的。
先给一个做着寻常工作的单个智能体算一遍老实账。一个跑十八步、平均每步两点五次工具调用的任务,在出任何问题之前就已经发出约四十五个对外请求。这一部分在轨迹里是看得见的,多数团队对它也大致有感。他们没有感觉的,是压在上面的那些。
# one user task, four layers that each look reasonable alone steps x tool calls 18 x 2.5 = 45 logical calls provider SDK retries x 3 (built in) = 135 your own wrapper x 2 ("be safe") = 270 gateway retry policy x 2 = 540 subagent fan-out x 4 (own budgets) = 2,160 worst-case requests # the layer nobody counts: the model retrying by itself. # a reformulated request is new work to the harness, not a retry.
这张表里有两条性质比它的确切数字更要紧。第一,重试是相乘地叠加的——SDK 不知道你的包装层存在,包装层不知道网关也会重试,而每一层都是由某个只对一层做过推理的人配置的。第二,最大的那一项通常不在任何配置文件里:智能体自己决定「换个方式再试一次」。对 harness 来说那是一次全新的工具调用、带着全新的重试预算——这也是为什么步数上限是一个很弱的界,而调用预算才是一个真的界。这个问题的「停下来」那一侧见规划与终止。
最坏情况那个数字不是你该在设计评审上引用的——多数调用第一次就成功,所以现实中的倍数远低于天花板。请两个都引用:p50 的放大倍数告诉你账单,而天花板告诉你一个糟糕的下午会对别人的服务做什么。并行工具调用把两者都抬高,而且把天花板抬得更快,因为并发拿走了顺序执行本来免费提供的那种「意外限流」。
从另一侧看,放大与探测无从区分。
那些请求里的每一个,都会落到某个人的基础设施上——一个你在付费的供应方、另一个团队负责的内部服务,或者一个陌生人的网站。接收端看到的是速率、并发与模式,而它没有办法知道那四百个请求来自一个人问的一个问题。有三条后果值得点名:
- 一次改变了请求的重试,是搜索,不是重试。退避之后重发同一个调用,是有礼貌的。被拒之后重发一个改过的调用,是枚举——而这正是把智能体变成不可归属扫描器的那个行为:2026 年 9 月那起事件背后就是它,一个做研究的智能体被澳大利亚政府的统计门户拒绝,尝试了各种变体,最终读到了并未公开的文件。
- 「可重试」与「终态」是两个不同的类别。429、503 与超时可以配指数退避加抖动重试;400、401、403 与 404 对那条路径是终态,它们属于结果,不属于重试循环。这个区分必须住在 harness 里、住在模型无法重新权衡它的地方,而不是住在提示词的一句话里。
- 除非你说出自己是谁,你就是不可归属的。一个 user agent 字符串、一个联系 URL,以及——在可用之处——一个签名请求,能把你的流量变成对端可以有意限流或有意放行的东西,而不是靠猜来拦。见机器人核验与智能体访问。
这件事的内部版本没那么戏剧化,却更常见:你的智能体机群成了某个按人类流量做容量规划的服务的最大客户,在最糟的时刻发现了它的限流,而由此产生的事件被归档到那个服务名下,而不是归到那个造成它的放大倍数名下。限流与供应方容量是它的运维处理;拒钱包服务则是有人故意放大你时会发生的事。
预算属于请求树,而不属于调用点。
按调用点配置重试解决不了这件事,原因是结构性的:一个调用点看不见它的兄弟。真正的修法是一个单一的预算对象,在用户任务开始时创建,并被穿到每一个代表该任务行动的东西里去,使得任何一处的花销都会减少所有地方的余量。
- 一个池子,所有分支都从里面取。对外调用数、token 与墙钟时间都住在同一个对象里。子智能体继承父级余量的一个切片,而不是拿到一份全新的——四路扇出应当是把预算切开,而不是把它乘开,而这恰是多数框架默认做错的部分。见子智能体。
- 截止时间要往下传,并且在开工前检查。如果还剩八秒、而这个调用通常要十二秒,正确的动作是现在就带着一个有用的部分结果失败,而不是开了工再被砍断。超时与截止预算是它的机制;而纪律在于:截止时间是一个参数,永远不是一个全局量。
- 要有按目的地的上限,而不只是一个全局上限。「500 次调用」的预算,对那个收到其中 480 次的主机毫无安慰。请按主机、按凭据限制并发,这同时也能防止一个慢依赖把整个任务的额度吃光。
- 幂等键让你保留下来的那些重试是安全的。当被放大的操作带有副作用时,放大会伤两次:同一笔支付被尝试三次,与同一次搜索被尝试三次,是完全不同的失败。幂等与重试讲了取键的规则。
然后把多余的层删掉。对任何一类失败,重试都只应存在于恰好一层——通常是能看见错误类别的最内那一层,而它上面的一切都配置为把失败往上抛。两层重试是一个只会在账单里、以及在别人的仪表盘上现形的缺陷。
把这个倍数变成一个你在看的数字,而不是一个你发现的数字。
放大很容易埋点,却几乎从来没被埋点——这就是这周值得合上的那道缝。请把它定义为「每完成一个用户任务的对外调用数」,按目的地发出,并且当成一个分布来读、而不是一个均值。
- p50 与 p99 分开跟踪。p50 告诉你单位经济,它该与「每完成一个任务的成本」并列摆在成本控制里。p99 告诉你最糟的那些任务对你的依赖做了什么,而它才是预测事件的那个数字。
- 在比值上报警,不在计数上报警。绝对调用量随着采用率上升,什么也说明不了;而「每任务调用数」在一次发布后上升 30%,是循环里的一次回退,通常是在第二层加了重试,或是某个工具开始返回看起来可重试的错误。
- 把拒绝当成一等指标来计。每个任务收到的拒绝数,以及一个事件:当一个被拒的请求在同一任务内被一个成功的变体跟上时。第一个数字是你如何注意到某个集成已经悄悄开始靠猜;第二个是你如何在别人告诉你之前,先知道你的智能体越过了一道边界。
- 拿它去除你的限流额度。放大倍数 × 并发用户数才是你真实的负载。这道算术会告诉你在多少用户时撞上配额,而答案通常比产品团队假定的低一个数量级。
请在你量最大的那个工作负载上做一次:拉一百条轨迹,数出每完成一个任务的对外请求数,按目的地拆开,把 p50 与 p99 写下来。然后找出每一个会重试的层,对每一类失败只留一层、其余关掉;在 harness 里把 401/403/404 变成终态;并给任务一个单一的预算对象,让子智能体去切分它而不是复制它。回报不只是账单——而是你的智能体在公司之外的所有人看来,终于不再像一个「被说了不、却还一直试」的东西。