你的 AI 预算不是一个数字:更好的 AI 成本管理方式

你的 AI 预算不是一个数字:更好的 AI 成本管理方式

那笔账单,不是来自一次天价请求

第一次觉得 AI 账单「涨得不讲道理」时,我本能去找一个罪魁祸首请求。

你懂那种:一不小心让模型写了篇小说;重试循环停不下来;有人把 staging 指到了生产密钥。这些故事都真实,而且复盘起来很干净。

但我找到的东西更安静,也更糟:几千个单独看都正常的请求。主模型很健康。短时故障时备份模型接了手。改了一版 prompt 之后缓存命中率悄悄下滑。同一个项目里还有第二个工作流,没人盯,因为「它只跑客服工单」。

系统没有坏。花费照样疼。

这才是生产环境 AI 里让人不舒服的模式。成本很少因为一次灾难性调用爆炸,而是靠重复流量、重试、故障转移、更长的 prompt、更大的输出,以及你在供应商仪表盘里填那个「每月数字」时根本没建模进去的用法,慢慢长出来。

如果你的应用只打一个供应商、一个模型,单一预算也许够用。一旦加上故障转移、多个模型,或多个功能共用同一个账户,那个数字就不再描述你真正在跑的系统。

为什么供应商仪表盘看起来精确,却仍会漏掉重点

供应商仪表盘通常擅长一件事:告诉你在那个供应商花了多少钱。

一旦 AI 功能离开原型阶段,团队真正会问的问题,它就弱了:

  • 是哪个产品功能烧掉了钱?
  • 花费来自主模型,还是事故期间的 fallback 流量?
  • 重试有没有把成本算两遍,还是只有成功那次上了账单?
  • 客服工作流还好,聊天工作流却快冲破月度计划了吗?
  • 如果模型 A 超预算,模型 B 还应不应该继续回答,还是整次请求直接死掉?

这些是应用层问题。供应商计费是账户形状的。你的架构却是功能形状、工作流形状,而且常常是多供应商形状。

于是团队发明自己的控制手段:表格告警、抓发票的 cron、硬编码熔断、「下午四点后别用 GPT-4o」的 Slack 消息。有些管用。大多数是在第一张惊喜账单之后才出现。

更深的问题是概念性的。预算是一个控制面。 如果控制面和请求在系统里流动的方式对不上,预算要么粗到护不住你,要么钝到把产品一起关掉。

多模型工作流会让「一个数字」显得不诚实

想象一个工单分类器。主模型强、贵。备份 1 是另一家更便宜的替代。备份 2 是留给故障的最后手段。

安静的日子里,几乎每个请求都打主模型。嘈杂的日子——限流、区域抖动、供应商事故——流量溢到备份。你的「每月 AI 预算」还是一个数字。花费的形状却完全不同。

再加上常见的生产现实:超时后的重试、本应零成本的 ModelRiver 缓存命中、同一个生产工作流被 playground 打到、同项目里还有第二个工作流,或事件驱动流水线把一次用户动作扇出成多次模型调用。

单一上限表达不了你大概真正想要的策略:贵模型太烫时产品还能活着,但整个项目绝不能失控。

AI 请求经过主模型、备份 1、备份 2;主模型槽位因超预算被跳过,备份继续执行

AI 成本控制的两层

和这个问题相处久了,最终让我记住的心智模型是「层」。

两层预算控制——项目级上限与每槽位成本护栏——加上跳过超预算主模型的故障转移链

项目级上限。 覆盖项目内合格花费的项目级后盾。这是「别把公司卡刷穿」的数字。一旦已观察到的花费达到上限,新的线上请求会被阻断,且不尝试故障转移。项目预算用尽了,请求也就没运气了。

模型级护栏。 挂在工作流里主模型或某一个备份模型上的上限。这是「这个模型在这个周期内可以花这么多」的数字。槽位超预算时,跳过它,尝试下一个已配置的备份。工作流仍可能成功。贵模型只是在周期重置前不再接流量。

不是:「整个工作流一共多少钱,不管哪个模型答的。」在故障转移设计里,这个总数是含糊的。你想停的是功能,还是某一个模型?那是不同的策略。当成同一种策略,很容易在你专门为事故准备备份的时候,把产品一起弄下线。

上限触达时行为
项目花费上限该周期内项目花费已达或超过上限阻断请求。不尝试故障转移。
模型成本护栏该工作流的供应商/模型槽位已达或超过上限跳过该槽位。尝试下一个备份。
所有尝试过的模型都超预算因预算原因故障转移链耗尽请求以预算错误失败。

在真实控制台里,这两层对应不同的控件。模型级上限在工作流编辑器里,显示为每个主/备模型下的 Cost guardrail(成本护栏);项目级上限在项目 Settings(设置) 里,显示为 Spending limit(花费上限)

工作流编辑器中,主模型和备份模型槽位上的 Cost guardrail 预算与重置控件

什么应该算花费(什么不该)

在相信任何上限之前,先定义电表。一条能干净映射到请求日志的实用规则:

只统计成功且有价格的请求。

也就是说:

  • 成功的供应商调用,带真实估算价格,计入。
  • 失败的供应商尝试不计入护栏计算,因为电表统计的是成功且价格为正的请求日志。实际供应商计费在某些失败场景下可能不同。
  • 预算跳过,不计入。什么都没发给供应商。
  • ModelRiver 缓存命中(prompt cache 或 response cache)价格为零时不计入,甚至不该需要预算检查。这不同于供应商侧的 prompt caching——后者不一定免费。
  • 故障转移过程中产生的子跳过/错误行,不该只因为请求吵闹就抬高总额。

如果你跑事件驱动流水线,项目总额还有一层补充:属于另一个工作流的关联 AI 步骤,对项目来说仍然是真金白银。父请求扇出到子 AI 工作时,项目上限也应该看见这笔花费。那笔子工作的槽位护栏属于被关联的工作流,不属于父流水线。

目录定价也很重要。如果网关用 USD 模型价格目录估算成本,预算的可信度就取决于那个目录。未知定价不是小细节。对已设上限的模型槽位,诚实的行为是跳过该模型,而不是假装没有价格也能执行美元上限。对项目上限来说,可路由模型缺少定价是配置错误,应该阻断请求——不该被悄悄忽略。

周期是策略,不是装饰

按小时、按天、按周、按月编码不同的恐惧程度:

  • 按小时 适合止血——调新功能或追失控循环时很有用。
  • 按天 是仍在摸流量形状时的实用默认。
  • 按周 贴合产品节奏和 on-call 轮转。
  • 按月 对应财务对话和发票恐慌。

它们都需要同一套时钟。UTC 日历边界无聊且正确:整点、UTC 午夜、按周为周一 00:00 UTC、月初第一天。无论选哪个周期,都要在 UI 和错误载荷里露出 reset_at。没有可见重置时间的预算,只是一面没有钟的墙。

项目设置页中的 Spending limit:最大 USD 花费与按月 UTC 重置周期

故障转移中途,模型超预算时会发生什么

这正是分层预算值得那点复杂度的时刻。

请求进来。项目仍在上限之下。主模型已经触达当日护栏。

正确行为不是「让用户失败」。正确行为是:

  1. 记录主模型因预算被跳过。
  2. 转到备份 1。
  3. 检查备份 1 自己的护栏。
  4. 仅当该槽位仍在预算内时才调用供应商。

如果备份 1 也耗尽、备份 2 还有空间,就继续。如果每个可用模型都因预算被跳过,再用清晰的预算错误失败——不是泛泛的 500,也不是伪装成供应商超时。

在请求日志里,这应该事后可重建。ModelRiver 在尝试下一个备份之前就会写入预算跳过记录,所以即使备份最终成功,那些跳过行也仍然可见。若请求最终失败,跳过记录会关联到父错误。运维应能回答「我们是花了钱,还是拒绝花钱?」而不必考古。

请求日志时间线:主模型因成本护栏触顶被跳过,备份模型成功

重叠上限:真正会发生的场景

两层都启用后,几个经常出现的情况:

  • 主模型超日上限,备份仍开放。 工作流继续活着,流量转到仍有预算的下一个模型。
  • 工作流看起来还好,项目已经结束。 每个模型槽位都还能显示剩余空间,但项目上限会阻断请求且不故障转移。后盾先赢。
  • 一个工作流有上限,另一个没有。 合法设计。项目上限仍会汇总两者的成功计价花费。无上限不等于隐形,只是槽位层没有保护。
  • 你已经付过钱的 prompt 命中 ModelRiver 缓存。 零价格,不需要预算检查。这是系统少数几次可以显得慷慨的时候。
  • 主模型失败,备份成功。 失败尝试不计入护栏电表。成功的备份计入那个备份的槽位和项目总额。
  • 链上每个模型都超预算。 请求以工作流预算错误失败,没有供应商调用。

还有一个很多人栽过的情况:测试模式。 如果工作流模拟或绕过线上供应商花费,预算执行就该让路。生产工作流在线上 playground 里是另一回事:能花真钱,就该尊重真上限。

软执行是诚实的取舍

下面这部分,营销文案喜欢跳过。

如果每次请求都无缓存地从数据库汇总日志来查预算,你就在热路径上加延迟。如果把花费缓存大约十秒,并发请求可能在缓存刷新前溜过去。突发流量下,实际花费可能略高于配置上限。

这就是软执行。它不等于「保证精确到分的硬停」。

对多数产品团队,软执行是正确的取舍。你要的是护栏,不是和每一次推理抢分布式锁。你该要求的是诚实:

  • 说清楚执行是软的。
  • 在成功的计价日志写入后失效花费缓存。
  • 在 UI 里显示实际花费,哪怕短暂超一点。
  • 基于已经观察到的花费阻断,而不是预测当前请求未来的 token 数。

最后一点很重要。估算「还没做」的请求成本是另一个更难的问题。只往后看的上限更简单,仍然能抓住真正伤人的模式:持续超支。

可观测性是功能的一半

看不见的花费上限,只是神秘中断生成器。

运维至少需要:项目已花 vs 上限与重置时间、每模型槽位进度、接近上限或已触顶的清晰信号、能区分预算跳过与供应商失败的请求日志,以及 project_budget_exceededworkflow_budget_exceeded 这类错误类型,而不是「出了点问题」。缺少目录定价也需要单独的配置错误——不同诊断,不同修复。

没有这些,on-call 会把预算阻断当成供应商事故。有了这些,他们会把它当成它本来的样子:策略在生效。

Overview 的 Spending limits 区域:项目已花对比上限,以及各工作流主/备成本护栏进度

你可能还会喜欢:不烧 token 测试 AI 工作流

如果你在设计 AI 成本控制:实用建议

无论是自建,还是用已有网关,这些设计教训都能迁移。

  1. 把「停某个模型」和「停整个项目」分开。 不同失败模式需要不同行为。
  2. 把对故障转移友好的上限放在模型槽位上。 否则你的可靠性功能会在事故期间变成成本放大器。
  3. 加一层项目后盾。 只有槽位上限,救不了五个无上限工作流在凌晨两点互相发现对方。
  4. 用电表定义成「成功且有价格的日志」。 数钱,不数感觉。
  5. 学习阶段用短周期,流量稳定后再拉长。
  6. 把未知定价当成配置错误。 没有价格的美元上限是演戏。
  7. 把跳过记成一等公民事件。 未来的你会需要解释主模型为什么从未运行。
  8. 别把花费上限和限流或内容护栏搞混。 不同工具,不同告警。

我们最后怎么实现的

我们把 ModelRiver 建成 AI 路由网关:项目、工作流、主备模型、请求日志、缓存——一旦「直接调供应商 SDK」不再可爱,你就需要这些不风光的机器。故障转移一旦存在,单一预算数字在我们自己的产品里就显得不对。于是我们加了分层花费控制。

在控制台里它们显示为项目 Spending limit(花费上限),以及主模型、备份 1、备份 2 上的每槽位 Cost guardrail(成本护栏)——就是前文截图里的同一套控件。线上请求按顺序检查:测试模式工作流跳过上限;零价格的 ModelRiver 缓存命中跳过上限;用「阻断请求且不故障转移」执行项目级上限;然后是带故障转移的模型级护栏。Overview 显示利用率。请求日志用 budget_guardrail 作为 response source 记录预算跳过。软执行是有意且有文档的。

如果你要操作手册,在 花费上限文档。产品页是 AI spending limits

这不承诺什么

几条边界,说清楚:

  • 不保证并发下零超支。
  • 不逐行读取供应商最终发票。它使用 USD 目录估算价格。
  • 不替代财务告警、采购审批,或对 API 密钥的成年人监管。
  • 不替你决定产品策略。

这些不是脚注。它们是护栏和幻想的区别。

真正有帮助的转变

有用的变化不是「加一个更大的预算字段」。而是把预算当成策略:模型失败时、流量飙升时、某个功能开始吃掉其他功能的午餐时,花费被允许往哪里移动。

比「我们怎么再也不超支」更好的问题:

  • 如果主模型太烫,用户还应不应该得到答案?
  • 如果项目耗尽,任何工作流还应不应该继续花钱?
  • 哪些成功调用计入,哪些吵闹的失败不该计入?
  • 墙撞上时,明天的 on-call 能不能看懂为什么?

那些问题是设计工作。控制只是你如何把答案编码进去。


如果你想在运行中的控制台里看到这种形状,ModelRiver 有免费档,可以设项目花费上限、加每模型成本护栏,并在请求日志里观察故障转移行为。有意思的不是表单字段。而是看着主模型因预算被跳过,备份悄悄让功能继续活着。

相关阅读