loading

Loading

探索与发现

首页 AI 前沿

请求级 Token 预算熔断:超限后拒绝哪一段

分类:AI 前沿
字数: (2827)
阅读: (6)
0
摘要:账户级预算超限就拒,拦不住一次死循环请求把白天额度提前烧光。把预算做到请求粒度,就需要在入口预检、解码步进、工具回合三个检查点做扣减,并想清楚熔断后返回什么、和模型分层路由怎么对接。

账户级预算拦不住失控的请求

大多数团队第一次做 LLM 成本治理,都是先按账户或项目设一个月度预算,超 80% 告警,超 100% 拒绝。AWS 那套 Cost Sentry 的做法就是这个思路:调用 Bedrock 之前查一次当月用量,对着预算上限判断,超了直接拒。这类方案适合防凭证被盗刷——攻击者很难凭空绕过一个账户级阈值。

但它对正常业务里的问题几乎无效。一个 Agent 会话如果落进死循环,反复调工具、反复把工具返回值塞回上下文,单次请求的 Token 会是正常请求的几十倍。等到月度预算触发,账单早就超了八成,而真正做事的请求跟着一起被拒。账户级熔断的粒度和故障粒度根本对不上:故障发生在一次请求内部,熔断却发生在全体请求之上。

所以我更倾向把熔断做到请求粒度:每一个请求进来时带一个 Token 预算,执行过程中按实际消耗扣减,触到上限就终止这一个请求,并返回一个可解释的状态。它和限流、模型降级解决的不是同一类问题,不能互相替代。

预算从哪来,别用固定值

请求级预算最容易犯的错,是所有请求共用一个常数。写死 4000 tokens 看着很整齐,实际上面向代码补全的请求带 200 输入、50 输出就够,而一次深度推理请求常见 1000 输入对 1500 输出。前者被限得太松,后者一到第三轮工具调用就被掐掉,用户看到的是“AI 突然不说话”。

可行的是按业务入口给基准,再用历史分位数校准。计量层跑一周之后,你会拿到每个 project、每个功能的输入输出分布;把预算定在 p95 附近,超过这个数的请求本来就属于少数,掐断的代价可控。预算值要和学习率一样被当成配置来管理,能按版本号回滚,而不是散在代码里的字面量。

还有一层是渐进式扣减。用户输入进来时就按 tokenizer 实际编码结果扣掉输入部分,这是确定值,不需要估计。输出侧只能按已生成的片段逐步扣,并且在请求的开场就预留一个最低完成额度。否则一个长输入把预算吃光,模型连一句完整的回答都吐不出来,返回的兜底信息对用户没有任何意义。

三道闸门,只有一道在生成过程里

工程上我会把预算校验拆成三个检查点,每个检查点承担的职责不同。

入口预检

请求到达时先编码、算输入 Token、和该请求的预算做减法。这一步不放行也别直接 429,更好的做法是把超预算部分截断——RAG 场景里输入往往占 Token 消耗的大头,把检索片段按相关度裁掉尾部,多数请求仍然能给出可用答案。截断要留下标记写进响应元数据,便于后面归因:多少比例的请求是在入口被动过手脚的。

生成中的步进扣减

真正的执行期熔断在解码循环里做,而不是包在 API 调用外面等它返回。常见的 SaaS 接口不给你“生成到一半叫停”的控制权,这样 budget 就退化成一个事后检测器:超了才发现,钱已经花掉。自建 vLLM 或 TensorRT-LLM 的服务在这点上有优势,可以在 token 级调度处读预算状态,到达阈值直接结束该序列并从 batch 里释放槽位,不让它继续占用 KV Cache 和算力份额。要注意和张量并行的 batch 边界配合:一个异常序列留在 batch 里,等于让同一批里的所有请求分摊它的开销。

工具回合间的再检查

在 Agent 场景里,输出长度不是崩溃源,回合数是。每次工具调用返回都要把新内容重新塞进上下文,第 N 轮请求的输入可能已经比第 1 轮大了十倍。这个检查点顺带承担识别重复模式的责任,但触发动作应当是降级继续而不是直接断:中断状态附上“因预算耗尽,仅完成 3/7 步”,交回给上层编排决定是否换小模型重试。

熔断之后的行为,比触发本身更常投诉

账户级熔断只面对两个状态:开与关。请求级要处理更多细碎的分支,哪些能做、哪些不能做需要提前写清楚。

  • 已生成的完整句子保留,兜底说明追加在后,不要退化成一句“服务繁忙”把前面内容也抹掉;
  • 流式请求中途结束,需要一个显式的终止信号,之前线上出过迟迟不返回结束帧、客户端一直挂着重连的问题;
  • 计数口径按输入加输出计算,和对账口径保持一致,否则看板上不会出现独立差异;
  • 同一个预算在不同 provider 上的货币化可能有折扣或阶梯,熔断状态里要能带上更小的摘要,而不是只有成功和拒绝两种。

还有成本的准确性:返回时里的 usage 信息应该同时携带两笔 token 数及每笔折算后的当日支出,运营看“因为资源而少调用几次”只看这个数很方便。除了请求维度的预算,队列维度上的问题也要分开:Agent 的步进扣减每轮真正算出实际增量后落到 local 缓存,不能只是累加错失的整轮计算。

和分层路由的连接处最容易漏风

请求级预算非常适合和“请求-服务-项目”的分层路由配合:多轮循环先接到小模型,命中预算时尝试换更便宜的模型,把消费额顺延给同一步里将要解析更多参数的目标预算上限附近。三月份 Anthropic 推 Claude Sonnet 4 的时候提到过供应商侧也提供 input budget,这些限制在提示 API 那边直接解析就会有相同效果。但接口背后要小心的还是模型切换带来的计价差价。团队要决定的是:换模型这个过程是否也扣减预算,差额算在谁头上。

一个副作用是重试雪花。开了熔断之后,被限的请求如果被重试队列反复尝试,挂起令牌也重新排队,对 budget 的聚合判断带来了巨大错误。看过线上一次追因,某个入口的整批请求都在尝试切模型、但重试循环每一轮的原始预算单位导致实际花销被重复扣除——到最后整个团队的每日预算在重置前去向了一个它原本够不着的夜晚低谷值。这件事的教训是:预算状态必须以请求为键在服务端持久化(DynamoDB 那种),仅按内存中的内聚预算管理;每次重试都读同一条记录。否则日志里那个完整的请求轮次永远拼不起来。

还有负反馈规避做得过头的情况:排查过的每一个报错闭环里都对请求打标签、把结论推给事件簿。看几次日志就明白,开发人员会被塞满无法从月报里甩锅、而且不会被要求改动任何系统。这里的一句话似乎容易被视为惯例,但仍值得说:不建议所有请求都无成本做这个闭环操作。我们后来按抽样把命中率灌进内部指标里,上线后就再也没有看过那几行 jq 统计文件了。

要守住的那条数量线

之所以在工具链不断变长的前提下仍愿意上请求级熔断,是一种很具体的防御收益转向:不论成本还是渲染安全风险的逃逸行为被落到网络事件里区分本来就够困扰,但要定量拦住虚假的调用确实该有一条请求粒度的预算线,尤其在夜间自动活动里。如果有人想量化这笔收益,成本账其实是次要的——一整个白天里异常日下的月度水平也可能降低,恢复代价则是“半夜有人发现某个入口一直在保持额度未复。”日成本曲线的上升来自未被注意到的夜间自动活动占用的更大比例。看起来太小的预算每天磨掉了部分错用风险的实际面积。无论白天遇到的风控是不是一回事,量数对表也有价值。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/ai-frontier/647.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图