成本工程的定义:把"月底看账单"变成日常运营指标

大多数团队第一次意识到 AI 成本问题,是在供应商的月度账单寄到财务邮箱的那一天。那张单子上只有一个总数:上个月花了多少钱。它能回答"我们是不是变贵了",却回答不了"为什么变贵"、"哪条业务线变贵了"、"再涨一倍会先死在哪条业务线上"。于是接下来发生的事情几乎千篇一律:开一个会,猜几个原因,散会,等下个月再看一次。这个循环可以持续半年而没有任何改善,因为账单这个数据结构本身就不支持归因。

成本工程要做的第一件事不是省钱,而是换数据单位。把"钱"降维成"可归因的成本事件",把"月度总额"重构成"实时累计 + 维度切片 + 速率"。只有当成本和功能、租户、模型、渠道这些组织真正关心的对象建立了映射关系,成本才从一个财务结果变成一个运营信号。而运营信号的特征是:它可以每天看、可以设阈值、可以触发动作、可以归责到人。

这里有一个容易混淆的边界要先划清楚。成本工程不等于推理优化。推理优化关心的是同样的负载下怎么用更少的算力把同样的活干完,它的作用域在引擎、量化、批处理调度这一层;成本工程关心的是产品的商业形态是否与推理开销匹配,它的作用域在账务、归因、定价、预算、产品设计这一层。两者互为条件:没有成本工程,你无法判断一次推理优化到底省了多少钱,因为分母是模糊的;没有推理优化,成本工程只能做分配和刹车,做不出结构性改善。本文只谈前者,但会反复用到后者的结论。

1.1 账单里藏着七种不同的钱

把供应商账单拆开看,一个看起来简单的 LLM 账单至少包含七类性质完全不同的开销,它们的成本动因、归因方式和优化手段都不一样:

  1. 未命中缓存的输入 token。这部分随业务量线性增长,也随上下文长度增长,是最容易失控的一块。
  2. 命中前缀缓存的输入 token。单价通常显著低于普通输入,它的成本取决于你的稳定前缀有多长、TTL 有多宽、请求顺序有多"聚集"。
  3. 输出 token。它同时受"回答长度"和"模型自回归解码成本"两个因素影响,且它对时延的贡献远大于输入。
  4. 推理/思考 token。带思维链的模型会额外产生一大段不被用户看到、但照常计费的 token。这部分在传统 BI 报表里经常被错误地并入输出,导致所有关于"输出太贵"的分析都失真。
  5. 工具与外部服务成本。搜索、代码执行、数据库查询、第三方 API,这些不来自模型供应商,却同样计入一次任务的真实成本。单位经济模型如果不把它们算进去,得到的毛利率是虚高的。
  6. 重试与回退成本。超时后重试、限流后换供应商、结构化解析失败后重新生成,都是静默的成本乘数。
  7. 非生成类推理成本。向量化、重排、意图分类、护栏模型、图像与语音,这些和主对话模型共享同一个"推理预算"概念,但单价结构和优化手段完全不同。

把这七类混在一个总数里,就等于把完全不同的成本动因压成一个标量。成本工程的第一条纪律就是永远不维护一个叫"AI 成本"的数字,只维护分项累计。任何试图用一个总数做管理的做法,都会在第一次成本异常时失效,因为你没有下钻路径。

1.2 成本的两个来源:流量与单价

总成本 = 单价 × 用量。这句话看起来是废话,却是成本工程的核心框架,因为它决定了所有优化只有两条路径:

  • 压单价:换更便宜的模型、做量化、争取缓存折扣、选择更适合任务规模的模型规模。这是一次性或低频的工程动作,效果可预期,但对质量有影响。
  • 压用量:缩短上下文、少打几轮、提高缓存命中率、把能离线做的批处理化、按需路由到小模型、把大模型换下来的任务重新设计掉。这是持续性的产品动作,理论上不损失质量,但需要长期治理。

值得注意的是,压用量通常比压单价更值钱,而且副作用更小。很多团队一上来就想着换模型,其实真正的问题在于一次任务本来就不需要那么多 token:多注入了三份重叠的检索结果、历史消息没有做结构化裁剪、工具定义每轮重复注入、把不确定的任务反复重问。这些是纯粹的浪费,换模型解决不了。

1.3 为什么"月底看账单"必然失败

月度复盘模式有三个结构性缺陷:

  • 滞后性。成本超支是在第一周发生的,你在月底才知道。没有任何纠偏窗口。
  • 无归因。总额上涨可能来自单个大客户用量增长、一个被灰度放量的新功能、一次爬虫式的流量异常,或者某个新上线的循环调用。这些在总额里是同一个数字。
  • 无分母。"这个月贵了"是绝对值描述。你需要的是单位成本——每个会话、每个成功任务、每个活跃租户的成本——才能判断趋势是不是"变贵了"而不是"用得多了"。

成本工程的目标可以概括为一句话:在任何时刻,都能在两分钟内回答"上周哪个功能、哪个租户、哪个模型的单位成本在上升,上升是因为单价变了还是用量结构变了"。达不到这个响应速度,说明账本还没建对。

先把账本建对:成本事件模型

账本是成本工程的地基。很多实现失败不是因为算法不对,而是因为在最底层就犯了一个建模错误:把"一次 HTTP 请求"当成一次成本事件。这个假设在有重试、有路由回退、有工具循环的场景下必然崩掉,因为一次用户动作背后往往是多次计费调用。

2.1 最小可用的成本事件 schema

成本事件(cost event)应当是账本的原子单位。它的定义是:一次对计费方可见的、可以独立归因的模型调用。一次用户动作(trace)由若干个成本事件组成;一个会话(session)由若干个 trace 组成。这个分层让你既能回答"这次交互花了多少"(trace 层),也能回答"这个用户这个月花了多少"(session 层)。

下面是推荐的最小字段集。关键点在于三段式 token 拆分和价格快照:

from dataclasses import dataclass, field
from decimal import Decimal
import time, hashlib

# 成本事件:一次"可归因的计费事实",而不是一次 HTTP 请求。
# 一次用户动作可能触发多次上游调用(重试、路由回退、工具循环),
# 所以账本粒度必须比请求更细,否则所有重试成本都会被静默吞掉。
@dataclass(frozen=True)
class CostEvent:
    event_id: str            # 幂等键:hash(trace_id, attempt, call_seq)
    trace_id: str            # 贯穿一次用户动作
    session_id: str
    tenant_id: str
    feature: str             # 功能维度:chat / summarize / embed / rerank ...
    route: str               # 逻辑路由名,如 "chat.default"
    model: str               # 实际服务的模型(可能被路由改写)
    provider: str

    # 三段式 token,比单一 total 更适合归因
    prompt_tokens: int
    cached_prompt_tokens: int   # 命中前缀缓存的输入部分
    output_tokens: int
    reasoning_tokens: int      # 思考型模型产生、用户看不到但照常计费的部分

    # 价格是"当次生效"的快照,而不是当前配置。
    # 供应商调价后,历史事件仍按当时价格计算,避免账目被追溯改写。
    price_snapshot: dict        # {"in": Decimal, "cached_in": ..., "out": ...}
    unit: str = "1k_token"

    attempt: int = 1            # 第几次尝试,>1 即为重试
    ts_ms: int = field(default_factory=lambda: int(time.time() * 1000))
    env: str = "prod"           # prod / staging / eval / replay
    labels: dict = field(default_factory=dict)

    @property
    def billable_input(self) -> int:
        # 未命中缓存的输入部分
        return max(self.prompt_tokens - self.cached_prompt_tokens, 0)

    @property
    def cost(self) -> Decimal:
        p = self.price_snapshot
        total = (
            Decimal(self.billable_input) * p["in"]
            + Decimal(self.cached_prompt_tokens) * p["cached_in"]
            + Decimal(self.output_tokens) * p["out"]
        )
        return total / Decimal(1000)

def make_event_id(trace_id: str, attempt: int, call_seq: int) -> str:
    """幂等键必须由业务侧稳定字段构成,不能用自增主键或时间戳,
    否则上游重投、消费端 at-least-once 都会造成重复计账。"""
    raw = f"{trace_id}|{attempt}|{call_seq}".encode()
    return hashlib.sha256(raw).hexdigest()[:24]

三个设计决策值得单独说明。

其一,token 必须三段拆分而不是一个 total。绝大多数模型 API 返回的 usage 里,输入、缓存输入、输出、推理 token 是分开的,但很多团队在入库时就把它们加总成了一个数字。这样做的直接后果是:缓存命中率算不出来,重试放大量算不出来,推理成本被当成输出成本,所有后续分析都建立在错误的底座上。

其二,价格必须是快照而不是引用。如果事件里只存 model="xxx",那么供应商一旦调价,你所有历史成本都会被重新计算,昨天和上个月的对比瞬间失去意义。存快照是一个很小的额外成本,但它保证了账目的不可变性和可审计性,这在财务对账时是硬要求。

其三,attempt 字段必须是一等公民。重试率是成本健康度的核心指标之一:正常情况下重试占比应该是很小的,一旦某个模型或某个时段的重试率升高,通常意味着上游质量下降、你的超时设置过紧,或者出现了某个特定类型的坏请求在反复触发。它应该是第一等可查询字段,而不是藏在日志里。

2.2 维度设计:功能、租户、模型、渠道

维度的设计原则是:每个维度都要对应一个真实的决策动作。没有对应动作的维度不要加,因为维度爆炸会让聚合查询变得昂贵且没人用。

  • feature(功能):这是最重要的维度,也是最容易设计错的一个。它必须对应产品导航或用户可见的功能单元,而不是代码模块名。最常见的错误是按服务名归类——"chat-service"里既有免费试玩也有付费深度分析,全部混在一起。这种维度设计下,永远无法回答"哪个功能在亏钱",而这恰恰是产品经理唯一关心的问题。
  • tenant_id(租户):单位经济模型的唯一入口。没有租户维度,你只能算总收入减总成本这种没有决策价值的平均数。租户维度也必须来自真实的鉴权上下文,不能靠请求体里一个可伪造的字段。
  • model / route(模型与路由):注意区分两者。route 是你配置的逻辑路由名,model 是实际服务的模型。分开存的必要性在于:当你把"复杂任务"路由到强模型后,路由维度的成本变化反映的是业务结构变化,模型维度的变化反映的是价格或行为变化。混在一起,你连"这次调价到底影响了谁"都查不出来。
  • provider(渠道):多供应商场景下必备,用于评估切换收益和故障期的成本异常。
  • env(环境):这一项在很多团队被忽略,结果是评测流量、离线回放、内部 dogfooding 全部混进生产成本。有 env 标签的成本报表,其"生产成本"和"供应商账单"之间的差额应该可以用一两个查询解释清楚;没有这个标签,这个差额就是一个永远查不出来的黑箱。

2.3 计费精度与幂等

账本和供应商账单之间必然有差额,处理方式取决于用途:

  • 对外财务口径:使用供应商账单作为准绳,账本只做分摊。这要求账本在"总量"维度上尽量接近账单,否则分摊系数是错的,分摊结果就都是错的。
  • 对内运营口径:使用账本作为准绳,因为它有维度。差额用一个显式的"未归集余额"科目承载,而不是把它摊到各个功能上假装不存在。

实现上要守住两条工程纪律。第一,事件写入必须幂等。模型调用在重试场景下可能产生重复上报,消息队列通常给的是至少一次投递,浮点累加在长时间运行后会漂移。可行的做法是用业务幂等键 + 精确小数(如 Decimal)或整数化的最小计价单位累加,而不是每次重算历史总和。第二,账本要有"补账"通道。有些成本在调用发生的那一刻是不知道的:流式生成被中途取消、批量任务事后统计、异步工具回调产生的后续调用。这类事件需要支持延迟入账并标记补账时间,否则你会看到一条成本曲线在月末突然跳一段。

拆解口径:同一笔开销的三种读法

账本建好之后,同一笔开销至少要用三种口径去读。搞混口径是成本沟通中最高频的错误来源:产品说"我们的功能很便宜",财务说"成本涨了",两边都没说错,只是各自在看不同的切面。

3.1 请求级真值:一次调用实际花了多少

这是最底层、最不容易出错的口径,直接来自供应商返回的 usage。它的作用是校准:所有上层口径都应该能向下收敛到它。如果某个功能维度的成本汇总对不上请求级真值,说明有事件丢失或重复,必须先修数据再谈分析。

3.2 功能级摊销:把 trace 拆给功能

一次用户动作往往不是纯生成本身:它可能触发了意图判断(一次小模型调用)、向量检索(一次 embedding 批次)、重排(一次 rerank 调用)、主生成(一次大模型调用)、以及输出后的护栏检查。功能级摊销要回答的是:"用户点了'生成周报'这个按钮,这次点击的总成本是多少,包含所有中间步骤?"

摊销有两条路线:

  • 直接归集:每个子调用在发起时就带上最终的功能标签。最准确,但要求调用链能把上下文标签可靠地透传下去,实现上需要把标签放进请求头或上下文对象并防止被中间件丢失。
  • 事后分摊:按 trace 聚合所有事件,再按某种权重分摊。实现简单,但需要明确的分摊规则,而且规则本身会成为误差来源。

实践建议是:主路径用直接归集,兜底用事后分摊,并且把"兜底分摊的比例"本身作为一个监控指标。这个比例如果持续上升,说明标签透传在某个环节断了,通常是新加的中间件或异步任务忘了带标签。

3.3 租户级与账期对齐

租户级汇总的正确性有一个容易被忽略的前提:归集窗口必须包含尚未闭环的会话。流式输出在用户关掉页面后可能仍然在服务端跑完最后几个 token;异步任务、批量回填、webhook 触发的后续调用可能延迟数小时甚至数天才产生成本。如果按自然月硬切分,月度租户毛利会有系统性的时间错配。

两个处理方式:其一是引入"未结账会话"科目,把窗口末尾未完成的会话成本单独挂账,下期再结算;其二是把账期定义为会话结束时间而不是请求开始时间,对长会话更公平。做对外财务汇报时前者更常见,做内部分析时后者更合理——关键是不要在同一张报表里混用两种口径。

单次会话成本:口径、生命周期与计算公式

"一次会话多少钱"是最常被问、也最容易被答错的问题。答错的原因几乎总是口径没说清楚:谁在算、算到哪一步、算不算缓存、不算不算推理 token。

4.1 会话不是一次请求

一次会话的真实成本至少包含五个部分:

  • 本次对话所有轮次中的全部模型调用(含被重试和被回退的那些);
  • 会话中触发的所有检索、embedding、rerank、意图识别等辅助调用;
  • 工具调用本身的外部成本(搜索 API、数据库、代码沙箱);
  • 工具返回后触发的"继续生成"调用——这一项经常被整体漏掉,因为它的 trace 和原始请求不在同一个链路里;
  • 如果会话产生副作用(写入向量库、生成并存储长文档),还有非 token 的存储与索引成本。

只算"主对话那几次调用"是最常见的口径错误,它会系统性低估涉及工具的复杂会话,从而让你在设计产品时做出错误判断:以为 Agent 场景和普通对话成本差不多,实际上工具循环会让成本成倍甚至数量级地放大。

4.2 有效 token 与账单 token

建议同时维护两个指标,它们的用途完全不同:

  • 账单 token:真实产生并计费的数量,用于对账和支出控制。它包含重试、包含思考 token、包含被丢弃的中间输出。
  • 有效 token:真正对用户交付结果有贡献的数量,通常指最终可见输出加上被模型实际消费而未缓存的输入。它排除了重试放大、思考 token、以及因为系统 bug 产生的无效生成。

两者的比值(放大系数)本身就是最有价值的健康指标。如果账单 token / 有效 token 长期偏高,说明系统在无效地生成;如果思考 token 占比过高,说明任务可能不需要那么深的推理;如果是重试 token 占比升高,那是稳定性问题而不是成本策略问题,需要工程侧介入。把"放大系数"做进监控面板,比盯着总成本有用得多,因为它对异常更敏感——总成本的变化有业务量这个巨大的噪声项,而放大系数是归一化之后的信号。

4.3 会话状态机与补账

把会话建模为状态机,是正确计算单次会话成本的前提。典型状态:

会话状态机(简化)——每个状态迁移都是一个可能产生成本事件的点

CREATED
  └─> CLASSIFYING      意图/路由判断(小模型,可能多次)
        └─> RETRIEVING   embedding + 向量库查询 + (可选)rerank
              └─> GENERATING   主模型流式生成
                    ├─> TOOL_CALL     模型请求调用工具 → 暂停
                    │     └─> TOOL_RESULT  工具返回 → 回到 GENERATING
                    │           (每次回路都是一次新的 GENERATING,成本叠加)
                    ├─> GUARDRAIL     输出后检查
                    └─> COMPLETED

异常分支:
  GENERATING --timeout--> RETRYING --> GENERATING      (attempt+1,成本叠加)
  GENERATING --client abort--> CANCELLED                 (已生成的 token 仍计费)
  GENERATING --overload--> FALLBACK_MODEL --> GENERATING(不同模型,价格快照不同)

状态机带来的直接工程结论是:成本事件的写入点应该挂在状态迁移上,而不是挂在 HTTP handler 上。只有挂在状态迁移上,工具回路、超时重试、客户端中断这三类成本才不会被漏记。同时,每个会话需要一个显式的 cost_settled 标记;没有这个标记的会话就是"未结账",必须在报表中单独可见。

单位经济模型:客单价、推理成本与毛利

如果前面的账本回答了"钱花在哪了",单位经济模型回答的是更尖锐的问题:这个产品赚钱吗,赚的是谁的钱。这是成本工程从技术职能升级为业务职能的地方,也是最容易被做错的一环。

5.1 从总额思维切换到分布思维

最常见的错误做法是:月收入减月成本,得到毛利率,然后拿它开经营会。这个数字的问题在于它对用户分布完全敏感。如果收入是均匀分布的、成本是幂律分布的(少数重度用户消耗大部分 token),那么总额毛利率会在两个完全不同的业务形态之间给出同一个答案:一个是健康的订阅制,一个是正在被几个大客户拖垮的边缘业务。

正确做法是至少把用户按某种分位数分层,分别计算每层的单位成本。常用的分层维度是消费档位(轻、中、重)、套餐档位、以及功能使用画像(是否启用工具、是否长上下文、是否使用批处理接口)。每一层都要能独立回答"这一层的毛利是多少"。

这里必须提一个统计学陷阱:不要用总收入除以总 token 来代表"典型用户"的成本。总 token 的均值被长尾用户严重拉高,而典型用户的成本应该用中位数或分位数来刻画。成本工程里所有涉及"平均用户"的表述,都应该明确是算术平均还是分位数——这两者在幂律分布下可能相差一个数量级。

5.2 贡献毛利与分层账单

推荐的成本结构模型是三层,从外到内逐层收窄:

  • 第一层:收入 - 直接推理成本 = 推理毛利。这一层最粗,包含了所有租户所有功能的全部成本。它的意义是提供总量约束,不适合做决策。
  • 第二层:按租户分层后的推理毛利。核心指标是"每租户贡献毛利",即该租户收入减去归属到它的全部推理成本(含检索、重排、护栏、工具)。这一层是定价和套餐设计的基础。
  • 第三层:按功能/场景分层后的任务毛利。核心指标是"每个成功任务的成本"。这一层是产品设计的输入——它告诉你哪个功能该继续投入、哪个该限量、哪个该下线重做。

三层之间必须自洽:第二层各租户之和应当等于第一层,第三层各功能之和应当等于第二层。如果对不上,差额要显式暴露为一个"未归集"科目,不要用按比例摊平的方式抹平。抹平会让所有下游分析都建立在一个不可信的基数上,而这种错误往往在几个月后才被发现。

5.3 三条不能混用的比值

在给非技术方做汇报时,下面三个比值最常被混用,混用之后会得出完全相反的结论,必须在每张图上标注清楚:

  • 每活跃用户成本:分母是活跃用户数,受留存波动影响很大。用户增长期它会自然下降,容易被误读为"优化见效"。
  • 每会话成本:分母是会话数,受产品交互设计影响很大。如果把一轮能完成的任务拆成三轮,每会话成本会大幅下降,但每任务成本可能上升。
  • 每成功任务成本:分母是"成功"的任务数。这个指标最接近真实经济性,因为它把失败重试的成本也压在了分子上。但它需要一套明确的成功判定标准,而定义"成功"本身是产品问题而非技术问题。

实践中的建议是:内部日常看板用每成功任务成本,管理层汇报用贡献毛利绝对值加趋势,并明确说明分母口径。两者都要,但不要混着讲。

5.4 成本地板:定价必须覆盖的最坏情况

有了每成功任务成本之后,定价就有了明确的约束。关键判断是:一个功能的价格应该对齐它的成本中位数还是成本分位数。答案是分位数,具体来说是较高分位数。原因是成本分布的长尾不是异常,而是产品形态的一部分——长上下文任务、复杂多轮任务、启用工具的任务,本来就是这类功能价值最高的形态,它们的高成本里有一部分是价值创造本身,不能靠压制。

推论是:成本优化应该优先针对分布的主体,而不是长尾。如果 p50 成本很高,说明大多数用户都在被低效消耗,这是提示结构、路由策略或轮次设计有系统性问题,应当优先修;如果 p50 还行但 p95 很高,说明是某类特定用法吃掉了预算,那么更合适的手段是产品侧分层(把重负载用法做成独立的高价档位或专门的功能),而不是继续优化平均值。这两种诊断对应的动作完全不同,混淆它们会导致优化方向完全错误。

另一个必须显式处理的问题是免费流量的成本地板。免费试用、公开演示、内部 dogfooding 都要消耗推理资源,它们的成本最终由付费用户承担。有三种处理方式:给免费额度设硬上限、按功能区分免费与付费的成本口径、或者干脆接受它作为获客成本的一部分计入 CAC。不接受它、也不把它算进任何科目,才是错误——那会让毛利率虚高,并让免费策略的决策失去依据。

降本手段与收益量化框架

降本手段很多,但绝大多数团队在评估它们时犯同一个错误:只算成本变化,不算质量变化。省了 token 但让任务成功率从九成掉到七成,真实的单位成本可能反而上升。因此需要一个统一的评估框架,先把框架立起来,再逐个套用具体手段。

6.1 通用框架:成本与质量的交换比

评估任何降本手段的核心指标不是"省了百分之多少成本",而是单位成功任务的成本变化:

from dataclasses import dataclass

@dataclass
class ArmResult:
    arm: str               # 方案名,如 "baseline" / "gpt-mini-routed" / "cached-v2"
    n: int                 # 评测样本数
    cost_total: float      # 方案总成本(绝对值,评测集固定时才可跨方案比)
    success: float         # 端到端任务成功率,不是 BLEU、不是人工主观分
    p95_latency: float
    escalation: float      # 转人工 / 二次求助 / 任务放弃率

def unit_cost_per_success(r: ArmResult) -> float:
    """单位成功任务成本 = 每次尝试成本 / 成功率。
    这是唯一可以跨方案、跨模型、跨缓存策略比较的指标。"""
    per_attempt = r.cost_total / max(r.n, 1)
    return per_attempt / max(r.success, 1e-6)

def marginal_change(base: ArmResult, cand: ArmResult) -> float:
    """返回相对基线的单位成本变化率,负数表示变便宜。"""
    b = unit_cost_per_success(base)
    c = unit_cost_per_success(cand)
    return (c - b) / b

def pareto_front(arms: list[ArmResult]) -> list[ArmResult]:
    """在"单位成本 - 成功率"平面上做帕累托筛选。
    一个方案只有同时更便宜且不掉成功率,才值得上线;
    只更便宜会掉成功率的方案,应当以质量损失为代价单独做产品决策。"""
    pts = sorted(arms, key=unit_cost_per_success)
    front, best = [], None
    for a in pts:
        if best is None or a.success > best:
            front.append(a)
            best = a.success
    return front

这个框架的三个使用要点:

  • 评测集必须固定且分层。总体成功率会掩盖结构变化——一个方案可能在简单任务上大幅提升成功率、在困难任务上大幅下降,而净值看起来没变。分层评测才能看出这种"用一个任务集换另一个任务集"的假优化。
  • 必须包含最难的尾部样本。如果评测集里全是中等难度任务,你永远不会知道某个降本方案在极端输入(超长文档、复杂多步推理)上会崩掉,而这恰恰是成本最容易失控的地方。
  • 延迟不能作为免费代价。批处理、深度思考、更长的提示压缩前置处理,都会用时延换成本。批处理把交互场景的时延从秒级推到小时级,这在很多产品里是不可接受的。p95_latency 和 escalation 必须和成本一起进决策表。

6.2 提示缓存:先修正命中率口径,再谈收益

前缀缓存是收益最直接的手段之一,但它也是最容易被误用的。很多团队看到账单上"缓存输入"那一栏有金额,就认为缓存在起作用。实际上,大部分团队真正的问题不是缓存价格太高,而是命中率太低,而命中率低的原因通常在系统设计而不在配置。

要把这件事讲清楚,必须先把命中率的定义钉死:

# 命中率的三种口径,必须在报表里注明用的是哪一种
hit_token_rate = cached_prompt_tokens / prompt_tokens
#   "token 口径":被缓存命中的输入 token 占比。适合衡量提示工程效果。

hit_call_rate = count(events with cached_prompt_tokens > 0) / count(events)
#   "调用口径":有多少次调用吃到了任何缓存。适合衡量系统设计的聚集性。

hit_prefix_len = min(P, ...)   # 稳定前缀长度(token 数)
#   "前缀口径":真正被复用的稳定前缀有多长。
#   这个口径最接近收益来源:只有稳定前缀才产生可观的复用率。

# 三者的关系:
#  token 口径 ≈ 调用口径 × 单次命中的平均覆盖比例
# 所以"调用口径很高但 token 口径很低"= 缓存系统在工作,
# 但你的提示里绝大部分内容仍然在缓存边界之外被重算。

命中率低的常见根因,按实际出现频率排序:

  • 稳定前缀被破坏。在系统提示之前插入了时间戳、请求 ID、随机数、用户昵称等每次都变的内容。缓存只在"完全相同的前缀"上命中,任何一个字节的差异都会让它失效到该位置为止。规范做法是把所有动态内容放到前缀之后,并把这条规则做成 lint 检查或模板层强制约束。
  • 请求顺序不聚集。缓存有容量和时间窗口,不同租户、不同功能的请求相互驱逐。如果你的流量是长尾且均匀的,命中率天然很低。此时"按租户/按功能分池"往往比任何缓存参数调优都有效。
  • 多轮对话把前缀越推越长。多轮场景下历史消息在不断变化,导致每轮的前缀都与上一轮不同。处理方式是缓存锚点分段:把不变的长期部分(系统提示、工具定义、长期记忆)作为一个独立前缀固定下来,会话内的短期历史放在其后,让长期部分始终保持可命中。
  • TTL 与会话节奏不匹配。缓存的生存时间如果短于两次相同前缀请求的间隔,命中率就会趋近于零。这需要用真实的请求间隔分布去反推 TTL 需求,而不是照抄默认值。

需要明确的边界:缓存优化的是"重复的输入",不是"多余的输入"。如果你的问题是一次任务注入了三万 token 的重复资料,那么正确的做法是先把不必要的内容删掉——删掉的内容既不产生缓存成本,也不产生命中收益。缓存是在正确的提示结构之上做优化,不是它的替代品。

还要警惕缓存收益的归因造假。一个常见的错误做法是:上线缓存功能之后,看账单下降了多少,就把差额全部记为缓存的功劳。这在三种情况下会明显夸大收益:同期还做了提示精简(输入 token 本来就变少了)、同期业务量结构变化(短请求占比下降也会降成本)、以及同期改用批量接口(批量本身有独立的折扣)。正确的归因方式是做一次受控对比:固定评测集、同一时间窗口、只切换缓存开关,比较同一批任务在两种配置下的单位成功任务成本。没有这组对照,任何关于"缓存省了多少"的数字都只能当作宣传材料,不能进入决策。

最后一个容易忽略的点是缓存的容量与并发之间存在权衡。缓存占用的显存或主机内存与推理资源是竞争关系,在 GPU 显存紧张的场景下,扩大缓存可能直接降低并发度,进而推高单位成本。所以缓存调参必须在"命中率"和"吞吐"两个指标上同时观察,单看命中率可能会做出损害整体成本的选择。

6.3 提示压缩:收益必须用任务成功率校正

提示压缩(摘要历史、抽取结构化状态、检索结果裁剪、去掉冗余示例)看起来是纯收益:输入 token 少了,成本自然降。但它有一个隐含代价:被压缩掉的信息可能正是任务需要的。而且这个代价的显现有延迟——压缩后的一周内可能一切正常,某个需要长链条信息的边缘用例开始出错,而那个用例在指标上只是成功率缓慢下滑,很容易被归因为"其他原因"。

因此提示压缩的验证纪律是:

  • 压缩率要单独报,不要和成本节省混为一谈。输入 token 减少的比例是压缩率;成本节省还取决于缓存命中情况——如果被压缩的正好是稳定前缀,压缩反而可能降低命中收益。
  • 必须做成功率回归,且要延迟观察。压缩的效果要在压缩生效之后的会话上评估,不能用压缩前的历史数据对比。评估窗口至少覆盖压缩策略的几个典型失效周期。
  • 对"信息保全型"任务要保守。如果任务本身需要完整回忆多轮细节,摘要就是错的,应该做的是结构化存储加按需检索,而不是摘要。压缩的正确形态是"改变信息的获取方式",而不是"减少信息量"。

6.4 量化:精度损失如何传导到业务指标

量化(降低权重和激活的数值精度)属于"压单价"路径。它的经济学很清晰:更小的位宽意味着更少的显存带宽占用和更高的有效吞吐,进而在同样的硬件上承载更多并发。它对成本的传导链条是:量化 → 单 token 边际成本下降 → 在给定延迟目标下的最大并发上升 → 单位成本下降。

但精度的评估必须沿着正确的链路做,否则结论不可靠。常见的错误是直接看困惑度或基准榜单的分数变化,然后外推到业务效果。困惑度是平均意义的语言建模指标,它对业务最关心的那些能力(长指令遵循、结构化输出的合法性、指令约束的遵守、工具调用的参数正确率)并不敏感。

正确的评估面向应该是任务级能力指标的集合,并且要看不同难度分层下的差异。一个常见模式是:简单任务的指标几乎不动,困难任务的指标明显下滑,而总体平均看起来只有微小变化。这意味着量化的收益很可能是"用长尾能力的显著退化换取边际成本的线性下降",值不值得完全是一个产品决策。决策的正确输入是分层数据 + 分层收入分布,而不是一个总体均值。

6.5 小模型分流:路由器的误判代价是不对称的

模型路由(简单任务走小模型,复杂任务走大模型)是结构性的降本手段,因为它降低的是"平均单价"而不是所有任务的单价。它的经济性可以用一个很清晰的框架描述:

# 模型路由的经济学(用变量描述口径,不依赖任何具体价格)
#
# 设:
#   p        = 判定"任务简单"的概率
#   q        = 判定为简单、且小模型确实能答对的概率(判定的精度)
#   r        = 大模型单独完成该任务的正确率
#   s        = 小模型单独完成该任务的正确率(s < r)
#   C_small, C_large = 两种模型的单次尝试成本
#   C_route  = 路由器判定本身的成本(可用一个极小模型,通常接近 0)
#
# 期望成本:
#   E[cost] = C_route + p * (q * C_small + (1 - q) * C_large)
#                    + (1 - p) * C_large
#
# 期望正确率:
#   E[acc]  = p * (q * s + (1 - q) * r)   # 判错时回退大模型,acc 接近 r
#            + (1 - p) * r
#
# 关键结论 1:误判代价是不对称的。
#   简单任务被判为复杂 -> 多花钱,但结果正确(只是浪费)
#   复杂任务被判为简单 -> 省钱,但结果错误(业务损失 + 可能引发重试,
#                                    重试会再次调用大模型,总成本可能反而更高)
#   所以路由器的评估指标应该是"漏判率"(把复杂判成简单的比例),
#   而不是"总体分类准确率"。
#
# 关键结论 2:当 q 很低时,路由几乎不省成本。
#   因为每次判错都要再走一遍大模型,等于做了两次大模型调用。
#   评估路由方案时,漏判率必须换算成"期望额外调用次数"来看。

由这个框架可以推出几条实操判断:

  • 路由器的漏判率是硬指标,要单独监控并设置告警。它在系统中的表现更像一个可靠性组件而非优化组件。
  • 可以设计成级联而非二选一:小模型先答,用规则和置信度判断是否放行,不确定时回退大模型。这把问题从"分类准确率"转成了"置信度校准",通常更容易做对,也更容易通过调整阈值来交换成本与质量。
  • 保守阈值是合理的起点。在成本压力出现之前,路由阈值应该偏保守(少分流),因为把误判造成的业务损失算进来,回本门槛比直觉高得多。等账本积累了几周真实数据、且漏判率可控之后,再逐步放宽。

6.6 批处理与异步化:时延换成本的前提条件

批处理接口的核心价值是把"低时延单请求"变成"高时延批量请求",从而换到更低的单位成本。它的收益来源是:批内请求共享权重与 KV 空间、减少调度开销、更充分地占满计算单元。这类优化不改变任何模型行为,所以质量是中性的——这是它相对其他手段的一个显著优点。

适用边界必须说清楚:

  • 只适用于用户不等待的场景:离线摘要、批量分类、评测回放、夜间索引、报表生成。交互式对话、实时客服、任何用户盯着屏幕等结果的场景都不适用。
  • 需要队列与背压设计。批处理接口的配额通常独立于在线配额,如果把在线流量改道到批处理队列,要注意配额和延迟承诺的变化。队列必须有明确的优先级、积压告警和超时丢弃策略,否则会出现"某个积压任务把整批拖死"的情况。
  • 收益是否兑现取决于批量窗口和任务到达率的匹配。如果流量稀疏,凑不满批次就退化成低效的单请求。所以正确的顺序是:先看流量特征(是否周期性集中),再决定要不要上批处理,而不是看到"批处理更便宜"就直接切。

6.7 减少轮次:最被低估的一类降本

前面几节讲的都是"每次调用更便宜",但最大的杠杆往往在调用次数上。一个需要五轮才完成的用户任务,如果能在两轮内完成,成本下降不止一半,而且用户满意度通常还提升了(少打字、少等待)。

可操作的方向包括:把信息一次性收集齐而不是逐轮追问;从历史会话中推断用户意图而不是每次重新确认;用结构化表单或选项代替开放式提问;把"确认后再执行"改成"给出可撤销的执行";以及在系统提示里明确约束"回答前先检查是否具备全部必要信息"。

衡量这个杠杆的指标是每任务轮次分布,而不是平均值。同样,轮次的幂律特征很明显,绝大多数任务一两轮就结束,少数任务陷入很长的循环。只看平均值会同时掩盖"大多数任务太啰嗦"和"少数任务在打转"这两个相反的问题,应该看分位数,并且单独监控长尾循环的次数与终止条件。

预算、限流与告警

账本和单位经济是"看得见",预算与告警是"管得住"。这一层的目标不是精确控制每一个请求的开销,而是在失控发生之前,以可控的方式把它截断。

7.1 预算是分配问题,不是总量问题

给 AI 业务设一个"月度成本上限"几乎没有价值,因为没有人会因为总额接近上限而做出正确决策。预算是分配机制,常见的三种分配维度:

  • 按租户分配:每个租户有独立额度或软额度。适用于按量计费的场景,也是唯一能防止单个租户拖垮整体的方式。硬额度会在客户毫无预警的情况下中断服务,体验很差;更好的做法是阶梯式策略:达到软阈值时降级(切换到更省的模型、减少上下文长度、关闭非核心工具),达到硬阈值时才限流,并在产品侧明确告知。
  • 按功能分配:每个功能有自己的预算和单位成本上限。适用于功能之间成本差异极大的产品,比如一个免费的试用对话和一个付费的深度报告。
  • 按模型/渠道分配:防止某条路由在异常情况下吃掉全部预算。这条经常被忽略,但它恰恰是最容易发生突发事故的地方。

三种分配可以叠加,实践中按"渠道 → 功能 → 租户"的顺序逐层收敛,粒度由粗到细。这样任何一层触发时,都知道是谁在花、为什么在花。

7.2 限流的五个维度

只做 RPM 限流是不够的。至少需要覆盖:

  • 速率:每分钟请求数、每分钟 token 数(输入与输出分别设,因为它们的成本结构不同)。
  • 并发:同时在途的会话数。并发是决定延迟和成本的耦合变量,限制并发比限制速率更能保护时延承诺。
  • 周期性配额:每租户每日/每月的 token 上限。这是控制月度成本的主要抓手。
  • 单次上限:单请求的输入长度上限、单会话的累计 token 上限、单会话的最大轮次。这三个上限是防止"单点爆炸"的关键,也是防止提示注入导致的资源耗尽攻击的必要防线。
  • 优先级:当全局预算吃紧时,保留哪些流量。这要求成本系统与调度系统打通,而不是各自为政。

限流触发后的降级链设计比限流本身更重要。理想的降级顺序是:切换到缓存命中率更高的简化提示 → 切换到更小的模型 → 减少检索结果数量 → 关闭非核心工具 → 排队等待而不是直接失败。每一级降级都应在事前用压测验证过质量与时延,而不是在事故当天临时决定。

7.3 告警:预算烧穿速率

固定阈值的告警有两个问题:设低了噪音,设高了来不及。成本告警应该基于烧穿速率(burn rate)——实际花钱进度与时间进度的比值。这个指标与预算绝对值无关,可以自适应不同的业务规模:

def burn_rate(used: float, budget: float, elapsed_fraction: float) -> float:
    """烧穿速率 = 花费进度 / 时间进度。
    = 1.0  : 完全按计划花钱(正常)
    > 1.0 : 花得比计划快,1.5 表示按当前速度会在周期的 2/3 处花完全部预算
    < 1.0 : 花得比计划慢(业务量不足,或用量被优化掉了)
    """
    if elapsed_fraction <= 0:
        return float("inf")
    return (used / max(budget, 1e-9)) / elapsed_fraction


def alert_level(used: float, budget: float, elapsed: float) -> str:
    r = burn_rate(used, budget, elapsed)
    if r >= 2.0:
        return "P0: 已确定会超支,立即定位并处置"
    if r >= 1.5:
        return "P1: 高概率超支,当日内定位"
    if r >= 1.2:
        return "P2: 偏离计划,纳入观察"
    if r <= 0.5 and elapsed > 0.5:
        return "P3: 花得太慢,检查用量上报是否丢失或路由是否被误降级"
    return "ok"


# 告警必须按维度分别计算,而不是只看全局:
#   全局 burn rate 正常 + 某租户 burn rate = 5.0  →  必须告警
#   全局 burn rate 正常 + 某模型单位成本突增    →  必须告警
# 建议的最小告警集:
#   1) 周期总预算烧穿速率(P0/P1/P2)
#   2) 按租户的烧穿速率 top-N(单租户拖垮)
#   3) 单位成功任务成本的同比/环比突变(效率退化,与总量无关)
#   4) 成本放大系数(账单 token / 有效 token)异常(重试/思考膨胀)
#   5) 账本与账单的差额率(数据管道故障的早期信号)
#   6) 缓存 token 命中率的突降(配置或流量结构变化)

第 5 条值得强调。数据管道的故障会伪装成成本下降:如果上报链路挂了,总成本会骤降,告警不会响,然后月底对账时才发现全月数据缺失。把"账本与账单的差额率"作为一等监控指标,是成本工程里少数几个能提前发现基础设施故障的信号。

第 3 条同样重要。单位成本的突变是比总额更早的信号,因为总额混入了业务量的巨大噪声。业务量增长导致的总额上涨是健康的,成本效率退化导致的上涨是危险的,两者必须分开告警。

7.4 硬约束的实现位置

预算的硬约束最终必须落到网关或调度层,而不是应用代码里的判断。原因是:应用层的判断在并发下不可靠(多个请求同时读到"还有余额"然后同时放行),而且无法覆盖内部服务之间的调用。推荐的结构是:

在请求入口做配额检查与预留(乐观扣减),在成本事件落账时做实际结算(修正预估偏差),在周期边界做重置与结转。预留与结算的两段式是必要的——因为实际用量在请求发出时是未知的(尤其是流式生成),只能预估;但预估必须能事后修正,否则账本会长期偏离账单。

成本归因的九个常见坑

这一节是本文最实用的部分。下面九个坑几乎在每个规模化运行的 AI 产品中都会出现,而且大部分都会让成本分析得出方向性错误的结论,而不只是有小偏差。

坑 1:重试放大被静默吞掉

最典型的表现是监控上的成本曲线完全平滑,没有任何尖峰,但实际账单远高于预期。原因是重试请求走了不同的服务实例,缺少与原始请求关联的标识,于是重试成本被计入了另一个时间点、另一个功能、甚至另一个租户。

防御措施:trace_id 必须在整个重试链路上透传,包括网关内部的重试、SDK 层的重试、以及客户端的超时重发。attempt 必须从 1 开始严格递增。报表上必须有"重试成本占比"这个指标,并且这个指标应该出现在成本面板的第一屏——它是一个领先指标,通常在成本超支之前很久就会异常。

坑 2:上下文隐性膨胀

每个团队都清楚"上下文越长越贵",但很少有人去测量上下文的实际构成。真实系统里的膨胀通常来自这些不显眼的来源:

  • 工具定义(function schema)每轮都在输入里,且工具数量随功能增加而增长;
  • 多套检索结果同时注入,彼此高度重叠;
  • 系统提示里的 few-shot 示例在真实流量中远未被有效利用,却每轮都付费;
  • 结构化输出的 JSON Schema 随字段增加而膨胀;
  • 失败重试时把上一次失败的完整输出也带进了上下文;
  • 多 Agent 场景中,子 Agent 的完整输出被原样传给主 Agent,形成层层放大。

防御措施是把上下文当成一个需要预算管理的对象:在开发期就用工具对每类任务的输入 token 分布做统计,按业务分位数(不是平均值)设置上限;上线后持续监控输入 token 的分位数趋势而不是只看均值。输入 token 分布的长尾比均值更能预示成本事故。

坑 3:评测流量混入生产统计

离线评测、A/B 实验、回归测试集回放、人工标注——这些流量使用的模型配置、提示模板、参数可能与生产完全不同,但它们消耗的是同一个供应商账户。更隐蔽的情况是:内部员工在生产环境里做功能 dogfooding,这部分流量既不产生收入也不是评测,但它在账本里就是"prod"。

防御措施:env 标签必须在网关层统一注入,不能依赖调用方自觉传参。同时要定期做一次"账本 vs 账单"的差额归因,训练出一份可信的差额解释比例。判断标准很简单:如果你的生产成本和供应商账单之间存在一个你无法用两三个查询解释的稳定差额,那就是有流量没打标签。

坑 4:流式中断按全量计

用户点了一下发送,等了两秒觉得慢就关掉了页面。服务端可能还在继续生成,也可能已经生成了大量 token 才检测到断开。这部分 token 照常计费,但在很多实现里因为响应没有正常结束而没有被记录。结果是账本持续低估生产成本,而且低估的量与用户的不满意程度正相关——最贵的那部分体验最差的流量,恰恰是最容易被漏记的。

防御措施:成本事件必须在响应流的取消回调中落账,而不是只在成功完成时落账。已生成的部分按实际 token 数结算。同时这个指标本身值得监控——"中断前平均已生成 token 数"突然上升,往往是时延体验恶化或模型输出变啰嗦的早期信号。

坑 5:缓存命中率的分母错了

用 cached_prompt_tokens / prompt_tokens 计算命中率时,如果部分请求完全没有可缓存的前缀(短请求、动态前缀),它们会进入分母但永远不贡献命中,把整体命中率拉低。这会让你误判缓存失效,实际上问题只是存在一批天然不可缓存的请求。

正确做法是分母只包含"结构上应该可缓存"的请求,并且把不可缓存的流量比例本身作为一个指标单独看。这两个数要并排展示,否则你对缓存优化的判断会被一个结构性偏差持续误导。

坑 6:长上下文导致"看似省钱实际更贵"

一个反直觉但常见的现象:启用长上下文以避免"截断后需要重新提问"之后,总成本反而上升了。原因是用户不再需要多轮澄清,一次成功率高了很多,看起来效率提升了;但长上下文的单次成本是非线性的,而"多问几轮"的旧路径只有在失败时才付出额外成本。更糟的是,很多场景下被放进长上下文的文档大部分是没被用到的,纯属付了钱。

防御措施:对长上下文功能单独做单位经济分析,并且用"信息利用率"作为辅助指标——统计注入的上下文中有多少比例真正影响了最终答案。利用率长期偏低的上下文,应该改成按需检索,而不是无脑全量注入。

坑 7:A/B 实验的分摊错误

实验期的成本对比是团队最容易得出错误结论的地方。常见问题:实验组和对照组的流量配比不是设计假设、实验流量集中在某类用户上(通常是对该功能更感兴趣的用户)、实验期恰好遇上上游价格或流量结构变化、以及两组的缓存命中率天然不同(新方案可能与旧方案共享部分缓存,导致成本被错误摊薄)。

防御措施:实验期的成本分析必须使用单位成本而不是总量,必须校正流量配比,必须把缓存命中状态作为分层维度,并且要意识到实验流量通常不具代表性——它在长尾用户上的表现往往与生产相反。

坑 8:时区与账期错位

这是一个纯粹的技术性但后果很严重的坑。账单系统按 UTC 切分,你的业务系统按本地时区切分,租户的付费周期又是按签约日切分的。当你要回答"上个月哪个租户的推理成本最高"时,实际上是在把三个不同的时间窗口做交集。如果你没意识到这一点,会得出完全错误的排名,并据此做出错误的定价或套餐决策。

防御措施:所有成本报表显式标注时间窗口、时区和切分依据;租户级报表以会话完成时间为准归集,并且对未结账部分单列。

坑 9:把"成本下降"当成优化成功

成本下降有四种原因:用量结构变了、业务量掉了、路由被降级了、或者数据上报坏了。只有第一种配合单位质量指标稳定,才是优化成功。这是最容易让人松警惕的坑,因为它看起来是好消息。

防御措施:任何成本改善的结论都必须同时满足三个条件——单位成功任务成本下降、成功率没有下降、账本与账单的差额率保持稳定。三者缺一,就在结论旁标注"待验证",不要写进经营报告。

落地路线与组织分工

成本工程不需要一次做完。下面是一条按依赖关系排序的路径,每一步都以前一步的产出为输入。在展开三个阶段之前,先明确一个原则:每一步都必须留下一个可交付的产物,而不是只留下一个配置。可交付的产物包括一个能跑的对账脚本、一张有固定口径的面板、一份能支撑定价讨论的数字。产物是跨团队建立信任的唯一方式——没有产物,平台团队说"成本没问题"、业务团队说"成本太高了",双方都只能凭印象争论,而争论是成本工程推进不下去的最常见原因。

另一个原则是不要在账本建好之前承诺优化目标。很多团队会设一个"降本百分之多少"的季度目标,这在账本缺失时几乎必然失败:一方面团队不知道基数,另一方面即便达成了也无法证明是优化的功劳(可能是业务量结构变了)。先把测量做扎实,再谈目标不迟。

9.1 三个阶段

第一阶段:先让成本可见。目标是在两周内做到"账本能对上"。具体动作:在网关层统一注入 trace、租户、功能、环境标签;建立成本事件表并保证幂等写入;用价格快照字段保证历史不可变;实现"账本 vs 账单"的日对账,把差额显式暴露。这一阶段不要做任何优化,先把地基夯牢。验收标准是:任何一天的账本与账单差额都能被解释,且能按功能、租户、模型三个维度下钻。

第二阶段:让单位经济成立。目标是能回答"哪些功能、哪些租户在赚钱"。具体动作:把口径统一到"每成功任务成本";做租户分层和功能分层的毛利报表;建立与业务侧对齐的"成功"定义;识别出成本异常的长尾(高成本租户、长上下文任务、高轮次会话)。这一阶段的产出通常会让产品和业务对成本有完全不同的认知——通常比技术团队的估计更不乐观,这本身就是价值。验收标准是:能在会上对"哪个功能该限量"做出有数据的决策。

第三阶段:让成本可运营。目标是预算、限流、告警、降级链全部就位。预算先从软告警开始(只告警不拦截),跑两个周期确认阈值合理后再开硬约束。这一阶段的关键纪律是:硬约束上线必须配合降级链,且降级链要先在压测中验证过,否则限流会直接把可用性打下去,用成本换来了收入损失。验收标准是:能做一次完整的成本事故演练,从发现到降级到复盘。

9.2 组织分工:平台负责口径,产品负责结构

成本失控的原因很少是技术能力不足,更多是没有人对单位经济负责。责任划分上建议:

  • 平台/基础设施团队负责账本、计费口径、单价、限流与告警机制。职责是让成本可归因、可约束,不对业务成本结构负责。
  • 业务/产品团队负责单位经济和用量结构。职责是让成本值得——通过产品设计、套餐设计、提示与交互设计把成本降到有竞争力的水平。
  • 双方共同负责降级链的质量底线。降级会掉质量,这个取舍不能由任何一方单方面决定。

这个分工背后有一个现实考虑:平台团队没有动力去猜"哪个功能不该这么贵",产品团队没有能力去改单价和算力分配。成本工程要做的事情,本质上就是给这两方提供一把共同的尺子。

9.3 反模式清单

最后列出几个明确应该避免的做法,它们在实践中出现频率很高:

  • 先优化再测量。没有账本就做优化,事后无法证明收益,团队会逐渐失去对成本工程的信任。
  • 只优化输入不优化调用次数。调用次数的杠杆通常比单次成本大,忽略它是最大的结构性浪费。
  • 把成本当成模型的属性。成本是系统属性——同样用一个大模型,不同的提示结构、路由策略、会话设计,成本可以差很多。
  • 用总量指标做效率判断。业务量增长会淹没效率信号,所有效率结论都必须用归一化指标。
  • 上线硬预算但没有降级链。这会在第一次流量高峰时把收入一起砍掉。
  • 让业务方自己填标签。标签必须由系统在可信位置注入,否则它的数据质量无法支撑任何决策。

成本工程的成熟标志不是"账单变小了",而是成本变成了一个和延迟、可用性并列的日常运营指标:它有定义、有埋点、有面板、有告警、有责任人、有预算约束、有降级预案。到了这个阶段,成本不再是月底的一个坏消息,而是每天早上打开看板就能看到的一个数字——那时候,降本才会自然发生,而不需要靠一次次的专项优化运动。