成本归因与预算

B3
运维 · 经济性与投资回报

归因不了的成本你管不了,没有熔断器的预算只是一个愿望。

一张笼统的月度模型账单在运营上毫无用处:它告诉你你花了钱,却没告诉你是哪个功能、哪个租户、哪个用户花的,而且它在花钱决策已不可逆的一个月后才到。成本归因是这样一门纪律:给每次模型与工具调用打上你据以做决策的维度标签,让花费变成按功能、按租户的信号——而预算只有在超出时会在运行时做点什么、而非只在报表里做点什么,才能保护你。

STEP 1

厂商账单的粒度无法据以行动。

发票按 API key 与月份聚合——这正是你拿它没办法的两个维度。驱动决策的问题是:哪个功能不赚钱、哪个租户在被其他租户补贴、哪个用户是成本离群点、哪条代码路径在上次部署后回退了。这些都无法从账单回答。归因意味着把你据以决策的维度贯穿每次调用,让花费可沿功能、租户、用户、版本——你实际决策的那些轴——被查询。

STEP 2

给每次调用盖上成本上下文,并让它穿过扇出传播。

一个成本上下文对象在任务开始时创建,并随任务流动——包括进入每个子智能体与工具调用。每次模型响应的用量都记到那个上下文上。难的是传播:一个丢了上下文的子智能体把它的花费归给了无人,而扇出恰恰是昂贵花费藏身之处。

# cost/attribution.py — context travels with the task, into children
class CostContext:
    def __init__(self, tenant, feature, user, version):
        self.tenant, self.feature = tenant, feature
        self.user, self.version   = user, version

    def record(self, call):
        emit("spend", tenant=self.tenant, feature=self.feature,
             user=self.user, version=self.version, usd=call.cost_usd)

def spawn_child(ctx):
    return ctx          # SAME context — unattributed spend is the bug

经典的归因漏洞:子智能体与异步工具调用丢掉上下文,于是 30–60% 的花费落进一个"未知"桶——而那个未知桶里不成比例地是昂贵的深度工作。未打标签的花费不是舍入误差;它通常正是你最需要看见的那部分。

STEP 3

归因到你能据以行动的维度,而非好记录的那个。

记录模型名很容易,对决策却几乎无用。沿着能映射到杠杆的轴归因:功能(砍掉或重新定价)、租户(重新谈判或限流)、用户群(识别滥用,或一个值得学习的高级用户)、版本(抓住一次让成本翻倍的部署)。一个有用归因维度的检验标准:它上面一个明确的坏读数会在本周触发一个具体行动。如果什么都不会发生,这个标签就是遥测表演。

STEP 4

预算要么是运行时控制,要么只是个愿望。

一个你在仪表盘里拿来对实际值的月度预算什么也不做——等仪表盘红了,钱已经没了。真正的预算是一个计数器,按租户或功能划定范围,在请求路径上被扣减,越线时改变行为。是那个强制,不是那个数字,才是预算。

  • 按租户上限,让一个客户的失控无法吞掉共享池、把其他人饿死。
  • 先软后硬阈值——到软线时降级(更便宜的模型、更小的上下文、停用可选工具);到硬线时 fail-closed 并告警。
  • 在请求路径上强制,在昂贵调用之前检查,而非之后对账。
STEP 5

熔断器止血;告警只是在旁白。

告警把一桩正在进行的成本事故讲给一个人听;等有人读到时,一个无界循环已经计了一小时的费。一个熔断器——按花费速率跳闸、按租户上限跳闸、按每任务成本超过历史中位数 N 倍跳闸——自动停下花费,并让请求降级或 fail-closed。告警与熔断互补:熔断器框住损失,告警解释损失。一个只有告警的系统,已经选择了为每一桩事故付全款。

STEP 6

什么时候细粒度归因省下的不如它花掉的。

按调用归因与热路径上的预算检查会增加延迟、存储与代码面;对一个低流量、预算固定的内部工具,这套埋点可能比它治理的花费还贵。按你决策实际用到的粒度归因,不要更细。始终运行按租户的熔断器——它便宜且框住灾难——但把按用户、按调用的归因留给那些定价与毛利决策确实依赖它的产品。