那个直到触发时你才察觉的 Token 预算
你的团队与推理提供商协商了月度 Token 配额。合同规定了上限。提供商门户的仪表板显示昨天的使用情况,存在一天的延迟。API 本身返回每分钟速率限制头——anthropic-ratelimit-tokens-remaining、x-ratelimit-remaining-requests——而对于你实际需要规划的月度配额桶却只字未提。你的智能体集群没有机制在预算耗尽时减速,因为实时到达的唯一信号是 429 错误——而这个信号在预算已经用完后才出现,且伪装成重试逻辑通常会忽略的瞬时错误。
这是一个与速率限制(rate limiting)性质不同的问题。速率限制是一个快速波动的节流阀,消费者必须在几秒钟内做出反应;响应头告诉你桶里还剩一千个 Token,并在 40 秒内补满,一个编写良好的客户端会退避并重试。月度配额则是一个缓慢变化的预算,消费者必须以周为单位进行规划。这两者之所以容易混淆,是因为它们共享错误代码,有时甚至共享同一个仪表板,但它们需要不同的控制手段——而提供商公开的信息与消费者需求之间的差距,正是本月最严重事故的导火索。
这种不对称性值得关注。单次调用的遥测数据 告诉你刚刚花了多少。每分钟的响应头告诉你即时的突发预算。延迟一天的仪表板告诉你昨天花了多少。在这一连串信息中,没有任何地方能回答规划者真正需要的问题:“如果我保持这个速度,什么时候会用完?”消费者必须根据自己集群发出的数据自行构建答案,因为提供商的协议并未将其表面化。
毁掉预算的四个假设
这些失败案例通常围绕着四个错误的假设展开,每一个在出问题前听起来都很合理。
第一个是假设提供商会在耗尽前发出信号。云账单就是这样运作的——预算、警报、异常检测、80% 和 95% 时的软限制。Token 配额目前还没有那么成熟。大多数提供商公开一个汇总昨天数据的用量 API、一个延迟数小时的仪表板以及一个按月更新的计费门户。这些都不在请求路径中。财务部门假设基础设施团队有信号。基础设施团队假设 API 会公开它。没有人管理计量器,因为大家都假设计量器已经存在。
第二个是假设团队的客户端估算与提供商的账务会一致。团队通过在本地对 Prompt 进行分词(tokenize)并汇总计数来构建用量估算器。在单个 SDK 版本内,估算大致稳定。但随着 SDK 升级、模型版本更迭,以及输入 Token 与缓存读取 Token 之间的界限,估算会产生偏差——有时是几个百分点,偶尔会达到 20%。当这种偏差在一个月的流量中累积时,本地计量器显示“已用 70%”,而提供商的账单却说“你超支了 8%”。客户端计数对分析相对趋势很有用,但在预算执行方面不具备权威性。
第三个是假设 429 错误是瞬时的,值得重试。在每分钟的时间窗口内,这是正确的:等待几秒钟,桶会补满,请求就会成功。但在月度预算耗尽的情况下,这完全是错误的:每一次重试都是对一个在日历翻篇前都不会补充的桶进行的又一次失败调用。带有指数退避的幼稚重试逻辑会将配额中断演变成对一个已经拒绝请求的 API 的“惊群效应”(thundering herd)。对速率限制起防御作用的重试策略,在面对配额耗尽时反而变成了攻击手段,而大多数客户端并不区分这两者。
第四个是假设预算是一个单一的桶。在实践中,团队拥有一支“舰队”——后台摄取作业、面向用户的聊天界面、内部编码助手、每晚的评估运行、客户成功 Copilot——它们都从同一个池子中抽取资源。哪项工作负载扩展最快,哪项就会耗尽资源池,而用光预算的工作负载未必是最重要的。当面向用户的界面在第 26 天开始返回 429 时,原因可能是评估套件在第 24 天出现了峰值,值班人员没有任何工具可以下令“停止评估,保住客户”。桶是共享的,可见性不是,优先级划分发生在请求层而不是预算层。
“影子计量器”长什么样
弥补这一差距的模式是“影子计量器”(shadow meter)——一个客户端账务系统,它近乎实时地汇总集群中每个工作节点的 Token 用量,并发布一个剩余预算信号,每个智能体在决定是否进行下一次调用前都可以读取该信号。
其机制并不神秘。每个工作节点将每个提供商响应中的 usage 块(输入 Token、输出 Token、缓存读取、缓存写 入,按模型区分)发送到共享存储。该存储按天、周、月以及合同上限进行汇总。一个独立的进程发布一个“预算压力”指标:还剩多少,消耗速度如何,按当前速度预计多少天耗尽。智能体在执行昂贵操作前轮询该指标,并相应地调整其行为。
难点不在技术。难点在于决定“调整”意味着什么。后台批处理任务可以在计量器发出压力信号时推迟。交互式智能体如果推迟就会破坏用户体验。处于多步任务中间的编码智能体无法轻易放弃计划。团队必须为每个工作负载定义优雅降级是什么样的——而为了定义这一点,团队必须致力于一种层级抽象,按关键程度对工作负载进行排名,并制定一个预算分配方案,给每个层级一个子上限。
影子计量器也是唯一能消除提供商账务与客户端估算之间差距的工具。因为它消耗的是提供商返回的 usage 块,而不是本地的分词器,所以它在提供商自身分类账产生后的几百毫秒内就具有权威性。本地分词器估算可以放在计量器上游进行预检,但计量器本身信任的是线路上传输的数据,而不是猜测。
预算层级与饥饿规则
一旦建立了计量器,最具影响力的设计决策就是确定哪些工作负载最先被“饿死”。
一个实用的框架是将其分为三个层级。核心层(critical tier)涵盖了任何会导致 429 响应并最终演变成客户工单的业务——例如面向用户的聊天、认证流程助手、付费客户 IDE 中的实时编程代理。标准层(standard tier)涵盖内部工具、仪表板、支持辅助工具(copilots)— —即性能下降对员工可见但对客户不可见的工作负载。批处理层(batch tier)则涵盖所有按计划运行或针对历史数据运行的任务——如评估(evals)、数据摄入、定期重新摘要、数据集构建。
饥饿规则非常简单:当计量接近上限时,首先停掉批处理层,然后是标准层,最后永远不要停掉核心层。难点在于如何在没有协调开销的情况下执行这一规则。一种可扩展的模式是发布按层级的子上限,这些上限以剩余每月预算的比例表示,并每天重新计算。例如,批处理层在月初可能被分配 40% 的每月预算,但如果消耗率(burn rate)超标,在最后一周这一比例可能降至 0%。标准层也类似地进行缩放,但设有更高的保底线。核心层直到全局预算真正耗尽前都不会看到任何上限,而到那个时候,计量器触发的是合同升级,而不是优雅降级,因为届时团队面临的是采购谈判,而非工程问题。
饥饿规则有一个推论,这往往会让那些在事故发生后才考虑它的团队吃苦头:工作负载无法追溯性地重新分层。一个已经按标准层优先级运行了六个月的批处理评估任务,在月中第一次被降级时,会触发一个没人设计过的异常路径。层级分配必须在工作负载创建时就确定并执行,且计量器必须从一开始就强制实施。否则,第一次真正的预算压力到来之日,也是每个工作负载同时重新发现其自身降级行为之时。
没人向工程团队简述过的采购视角
在每个续约周期,财务部门都会要求 AI 团队 预测明年的 token 支出。预测是一个数字——通常带有置信区间,但在产品团队实际规划的使用增长面前,这个区间往往弱不禁风——而这个数字锚定了下一份合同。
这里有两个值得注意的误区。首先是预测建立在团队无法完全信任的遥测数据(telemetry)之上。如果客户端计量器与供应商的账单出现偏差,预测就会继承这种偏差。那些基于供应商 usage 数据块构建计量器的团队拥有可用的基准;而那些基于本地分词器(tokenizer)构建计量器的团队,其预测的数字从结构上就与最终账单不符。
其次是合同结构本身塑造了工程上的选择。一些供应商提供带有硬截断的真实每月上限。另一些则提供带有超量计费的软上限(soft cap)——这消除了“断崖”,但引入了另一种 bug:团队的消耗率每月都悄悄超过预算,由于请求一直成功,没人会注意到。带有超量费用的固定月费比硬上限更难监测,因为其故障模式是下个月发票上的一个条目,而不是生产环境中的 429。工程团队应该参与合同结构的制定,因为合同决定了优雅降级到底能以什么形式存在。
更深层的采购问题是供应商在合同义务上有义务提供什么样的透明度。在响应中包含一个显示剩余每月配额的 header,或者在消费者请求时选择拒绝而非限流,这类条款是没人会写进工作说明书(SOW)的小要求,因为读 SOW 的人通常不是那个凌晨 3 点会被叫起来处理故障的工程师。谈判观测能力的最佳时机是在签署合同时,而不是在第一次事故发生时。
