文章

LLM 成本归因生产实战:用 Token 台账与 Chargeback 稳住多团队预算

从 Token 台账、成本归因、账单校准、预算预警和 Chargeback 四个层面,系统讲解多团队 LLM 应用如何把不可解释的模型账单改造成可追踪、可分摊、可优化的工程指标体系。

背景问题

很多团队上线 LLM 应用后,最早遇到的成本问题不是”模型太贵”,而是**“账单解释不清”**。API 账单能告诉你总共花了多少钱,但很难直接回答这些问题:哪个团队消耗最多?哪个功能突然变贵?是用户量上涨、上下文变长、模型切换、重试放大,还是缓存命中率下降?

传统 Web 服务通常可以用 QPS、CPU、内存和带宽估算成本。但 LLM 应用的成本核心不只是请求数,而是 Token 结构、模型单价、上下文长度、输出长度、缓存策略、工具调用链路、重试次数和供应商计费口径的组合。

OpenAI 的 Responses API 返回 usage 字段,包括 input_tokensoutput_tokenstotal_tokens;其组织级 Usage / Costs API 也支持按 model、project、API key、user 等维度聚合用量或费用。Cloudflare AI Gateway 的 Analytics 也把 requests、token usage、costs、errors、cached responses 作为网关分析指标。Langfuse 的成本跟踪文档则进一步强调,成本既可以从 LLM 响应中直接采集,也可以基于模型价格表推断,但直接采集供应商返回的 usage 更稳健。

因此,生产环境不能只看供应商账单截图,而应该建设一套 Token 成本台账。它不替代财务账单,而是把账单拆成工程可解释的明细。

核心原理

1. 成本归因不是监控图表,而是一套账本

“成本看板”通常只能回答现在花了多少,而成本归因要回答:为什么花、谁触发、归到哪里、是否合理。

一个最小可用的 LLM 成本台账需要记录以下维度:

分类关键字段
请求标识request_id(串联网关日志、应用日志、供应商响应和业务事件)
租户与团队tenant_idteam_idproject_idcost_center
业务入口applicationfeatureworkflowprompt_versionagent_name
模型调用信息providermodelregionendpointtemperaturemax_tokens
Token 明细input_tokensoutput_tokenscached_tokensreasoning_tokensaudio_tokensimage_tokens
调用结果statuserror_codelatency_msretry_countcache_hit
成本字段unit_price_versionestimated_costprovider_reported_costfinal_cost

重点是:这张表必须接近请求级,而不是只按天聚合。日级聚合适合报表,但排查成本异常时,团队需要回到某个 prompt version、某个任务类型、某个用户段或某次发布。

2. Token 类型必须分开,不能只存 total_tokens

只存 total_tokens 会丢掉最关键的优化信号。一个 LLM 请求的总 Token 增加,可能来自几种完全不同的原因:

  • input_tokens 增加:上下文、历史消息、检索内容或工具结果过长。
  • output_tokens 增加:回答模板变长,或者模型进入冗长解释模式。
  • cached_tokens 增加:不一定是坏事,可能意味着缓存复用有效。
  • reasoning_tokens 增加:推理模型在复杂任务上内部思考成本更高。
  • image_tokens / audio_tokens 增加:多模态输入分辨率、帧率、音频时长变化。

Langfuse 文档也把 inputoutputcached_tokensaudio_tokensimage_tokens 等视为不同 usage types,并指出复杂模型需要更细的 usage 类型才能准确计算成本。对工程团队来说,拆分 usage type 才能知道是应该压缩上下文、限制输出、优化缓存,还是改模型路由。

3. 价格版本要独立建模

不要把模型价格写死在代码里。价格表至少需要包含以下字段:

CREATE TABLE llm_price_version (
    id              BIGSERIAL PRIMARY KEY,
    provider        TEXT NOT NULL,
    model           TEXT NOT NULL,
    usage_type      TEXT NOT NULL,
    unit            TEXT NOT NULL DEFAULT '1m_tokens',
    price_usd       NUMERIC(18, 8) NOT NULL,
    effective_from  TIMESTAMPTZ NOT NULL,
    effective_to    TIMESTAMPTZ,
    pricing_note    TEXT,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

价格版本独立之后,历史成本就能按历史价格重算,新的价格也不会污染旧账。对于多供应商、多模型、多地区部署,这一点尤其重要。

4. 供应商账单是最终校准,不是唯一来源

请求级台账通常有三个成本层级:

成本层级说明
estimated_cost调用完成后根据 usage 和本地价格表实时估算
provider_reported_cost如果供应商或网关返回成本字段,直接写入
final_cost按日或按小时与组织级账单 API 校准后的最终费用

OpenAI 的组织 costs 结果包含 amountcurrencyvalue 等字段,适合做聚合校准;Cloudflare AI Gateway 支持查看成本、Token、缓存、错误等分析指标,也可通过 GraphQL 查询用量数据。实践上,实时治理依赖 estimated_cost,财务对账依赖 final_cost

工程落地

1. 在 SDK Wrapper 或 LLM Gateway 处统一采集

成本台账不应该散落在每个业务服务里。比较稳妥的做法是两层采集

  • SDK Wrapper:所有应用必须通过统一客户端调用模型,在调用前注入 tenant_idfeatureprompt_version 等元数据。
  • LLM Gateway:对跨语言、跨团队、跨供应商调用做兜底采集,并记录 api_keyprovidermodelstatuslatencyretry_countcache_hit

两层都做不是重复,而是防止遗漏。SDK Wrapper 更了解业务语义,Gateway 更了解真实出站调用。

2. 标准化 Usage Schema

不同供应商返回的 usage 字段不完全一致,需要转换成统一结构。例如:

{
  "request_id": "req_20260702_001",
  "tenant_id": "tenant_a",
  "team_id": "search_team",
  "feature": "support_agent",
  "prompt_version": "support-v12",
  "provider": "openai",
  "model": "gpt-5.5-mini",
  "usage": {
    "input_tokens": 18420,
    "output_tokens": 960,
    "cached_tokens": 12000,
    "reasoning_tokens": 0,
    "audio_tokens": 0,
    "image_tokens": 0
  },
  "estimated_cost_usd": 0.0142,
  "latency_ms": 1830,
  "status": "success"
}

这个结构有三个设计要点:

  1. 字段尽量稳定,供应商特有字段放到 extra_usage,不要让主表随供应商变化频繁变更。
  2. 保留原始 provider_response_usage,方便后续校准。
  3. 业务维度要在调用前写入,不能事后靠日志猜。

3. 建立追加式成本流水

成本数据不要只做 upsert 汇总表。推荐采用追加式流水

CREATE TABLE llm_cost_event (
    id                  BIGSERIAL PRIMARY KEY,
    request_id          TEXT NOT NULL,
    event_type          TEXT NOT NULL,
    tenant_id           TEXT NOT NULL,
    team_id             TEXT,
    feature             TEXT,
    prompt_version      TEXT,
    provider            TEXT NOT NULL,
    model               TEXT NOT NULL,
    input_tokens        BIGINT DEFAULT 0,
    output_tokens       BIGINT DEFAULT 0,
    cached_tokens       BIGINT DEFAULT 0,
    reasoning_tokens    BIGINT DEFAULT 0,
    estimated_cost_usd  NUMERIC(18, 8),
    final_cost_usd      NUMERIC(18, 8),
    price_version_id    BIGINT,
    occurred_at         TIMESTAMPTZ NOT NULL,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX uq_llm_cost_event_request_type
    ON llm_cost_event(request_id, event_type);

event_type 可以是 estimatedprovider_reportedreconciledadjusted。这样可以保留成本从估算到校准的全过程,避免后续对账时不知道哪个环节改过数。

4. 预算预警要看 Burn Rate,而不只是单日阈值

很多团队会设置”某团队每日成本超过 100 美元报警”。这个规则简单,但有两个问题:低峰期突然翻倍可能不报警,高峰期正常增长又可能误报。

更实用的方式是把月度预算转换成 budget burn rate

budget_burn_rate = 当前窗口实际花费 / 当前窗口理论可花费

例如某团队月预算 3000 美元,按 30 天算,理论每天 100 美元。如果过去 6 小时花了 80 美元,而 6 小时理论额度是 25 美元,那么 burn rate 是 3.2。这个信号比”今天是否超过 100 美元”更早发现异常。

Google SRE 的 SLO 告警方法中,burn rate 用来衡量服务消耗错误预算的速度。成本治理可以借鉴同样思路:不要等到账单超标才处理,而是在预算被快速消耗时提前告警

一个简单 Prometheus 风格规则可以这样写:

- alert: LLMTeamBudgetFastBurn
  expr: |
    sum(rate(llm_cost_usd_total{team="support"}[1h])) /
    (monthly_budget_usd{team="support"} / 30 / 24 / 3600) > 3
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "LLM budget is burning faster than expected"

5. Chargeback 先从 Showback 开始

Chargeback 是把成本分摊给实际使用团队;showback 是先展示成本归属但不立即扣费。生产落地时建议先 showback,再 chargeback

阶段内容
第一阶段:Showback展示团队月成本、Top features、Top prompt versions、Top models、缓存节省、重试放大、失败成本
第二阶段:预算与软限制超过 80% 预算提醒,超过 100% 需要审批或降级模型
第三阶段:Chargebackfinal_costcost_center 入账

这样做的好处是减少组织阻力。团队看到”成本来自哪里”和”怎么降下来”之后,再接受分摊会容易得多。

适用场景

这套方案适合以下场景:

  1. 多团队共用一套 LLM 平台。比如客服、搜索、知识库、研发助手和运营生成共用同一个模型网关,如果不做成本归因,所有费用都会堆到平台团队。
  2. Agent 工作流较多。2026 年一篇关于 Agentic Coding Tasks 的论文指出,Agent 任务的 Token 消耗非常高,且同一任务多次运行的 Token 用量可能存在显著波动。这类场景不能只按请求数收费,否则成本风险会被掩盖。
  3. 需要跨供应商管理成本。OpenAI、Anthropic、Google、开源模型、私有化模型的 usage 字段和计费方式不同,统一台账可以把不同供应商转换到同一成本语义。
  4. 需要做模型降级和预算治理。没有成本归因,就无法判断是应该换小模型、缩短上下文、提高缓存命中率,还是限制某个功能的批量任务。

常见误区

误区一:只按 API Key 分摊费用

API Key 适合做权限边界,但不一定等于成本中心。一个 Key 可能被多个应用复用,一个应用也可能调用多个 Key。更合理的做法是把 API Key 当作采集维度之一,同时强制应用传入 tenant_idteam_idfeature

误区二:只看 total_tokens

total_tokens 适合快速粗算,但不适合优化。inputoutputcachedreasoning、多模态 token 的优化手段完全不同。只看总数,会把问题掩盖掉。

误区三:把估算成本当财务账单

estimated_cost 是工程指标,适合实时预警;final_cost 才适合财务分摊。两者可能因为价格变更、缓存折扣、批处理折扣、供应商汇率或账单周期产生差异。

误区四:成本治理只靠限额

限额只能防止继续扩大损失,不能解释为什么超额。更完整的治理应包括台账、归因、异常检测、预算 burn rate、优化建议和模型策略回放。

上线检查清单

  • ✅ 所有模型调用都有 request_id,并能关联业务日志。
  • ✅ SDK Wrapper 或 Gateway 强制写入 tenant_idteam_idfeatureprompt_version
  • ✅ Usage schema 区分 inputoutputcachedreasoningaudioimage 等类型。
  • ✅ 价格表版本化,支持历史价格回放。
  • ✅ 成本流水采用追加式事件,保留 estimatedprovider_reportedreconciledadjusted 状态。
  • ✅ 每日与供应商账单或组织级成本 API 对账。
  • ✅ 每个团队有 showback 报表,至少包含月成本、Top features、Top models、Top prompt versions。
  • ✅ 预算预警使用 burn rate,而不是只看单日阈值。
  • ✅ 对重试、失败、超时、缓存未命中单独统计成本。
  • ✅ 正式 Chargeback 前先运行 1-2 个账期的 showback。

FAQ

1. LLM 成本归因应该做到多细?

至少做到团队、应用、功能、模型和 prompt version 级别。请求级明细不一定长期热存储,但必须能在异常排查窗口内回溯。否则成本上涨时只能看到总账,无法定位原因。

2. 本地估算成本和供应商账单不一致怎么办?

不要强行让实时估算等于账单。实时估算用于工程预警,供应商账单用于最终校准。建议保留 estimated_costprovider_reported_costfinal_cost 三个字段,并通过 reconciled 事件记录差异。

3. 如何处理自托管模型的成本?

自托管模型没有供应商 token 账单,但仍然可以做归因。常见做法是把 GPU 小时、实例成本、批处理队列、模型吞吐和请求 token 数结合起来,计算内部单位成本。它的精度不如 API 账单,但足以支持团队分摊和容量规划。

参考资料

  1. OpenAI API Reference - Organization Usage
  2. OpenAI API Reference - Responses Object
  3. Cloudflare AI Gateway Analytics
  4. Langfuse Model Usage & Cost Tracking
  5. Google SRE Workbook - Alerting on SLOs
  6. How Do AI Agents Spend Your Money? Analyzing and Predicting Token Consumption in Agentic Coding Tasks

常见问题

LLM 成本归因应该以请求数还是 Token 数为核心?
应以 Token 和模型价格为核心,请求数只能反映流量规模,不能解释长上下文、工具调用、重试、缓存命中和不同模型价格带来的真实成本差异。
是否只依赖供应商账单就足够?
不够。供应商账单适合财务结算,但工程治理需要请求级、租户级、功能级和版本级台账,否则无法定位是哪类业务、提示词或模型策略导致成本上升。
Chargeback 会不会影响团队使用大模型的积极性?
如果只做扣费会造成阻力;更好的方式是先做透明 showback,再逐步引入预算、预警、异常解释和成本优化建议。