背景问题
很多团队上线 LLM 应用后,最早遇到的成本问题不是”模型太贵”,而是**“账单解释不清”**。API 账单能告诉你总共花了多少钱,但很难直接回答这些问题:哪个团队消耗最多?哪个功能突然变贵?是用户量上涨、上下文变长、模型切换、重试放大,还是缓存命中率下降?
传统 Web 服务通常可以用 QPS、CPU、内存和带宽估算成本。但 LLM 应用的成本核心不只是请求数,而是 Token 结构、模型单价、上下文长度、输出长度、缓存策略、工具调用链路、重试次数和供应商计费口径的组合。
OpenAI 的 Responses API 返回 usage 字段,包括 input_tokens、output_tokens 和 total_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_id、team_id、project_id、cost_center |
| 业务入口 | application、feature、workflow、prompt_version、agent_name |
| 模型调用信息 | provider、model、region、endpoint、temperature、max_tokens |
| Token 明细 | input_tokens、output_tokens、cached_tokens、reasoning_tokens、audio_tokens、image_tokens |
| 调用结果 | status、error_code、latency_ms、retry_count、cache_hit |
| 成本字段 | unit_price_version、estimated_cost、provider_reported_cost、final_cost |
重点是:这张表必须接近请求级,而不是只按天聚合。日级聚合适合报表,但排查成本异常时,团队需要回到某个 prompt version、某个任务类型、某个用户段或某次发布。
2. Token 类型必须分开,不能只存 total_tokens
只存 total_tokens 会丢掉最关键的优化信号。一个 LLM 请求的总 Token 增加,可能来自几种完全不同的原因:
input_tokens增加:上下文、历史消息、检索内容或工具结果过长。output_tokens增加:回答模板变长,或者模型进入冗长解释模式。cached_tokens增加:不一定是坏事,可能意味着缓存复用有效。reasoning_tokens增加:推理模型在复杂任务上内部思考成本更高。image_tokens/audio_tokens增加:多模态输入分辨率、帧率、音频时长变化。
Langfuse 文档也把 input、output、cached_tokens、audio_tokens、image_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 结果包含 amount、currency、value 等字段,适合做聚合校准;Cloudflare AI Gateway 支持查看成本、Token、缓存、错误等分析指标,也可通过 GraphQL 查询用量数据。实践上,实时治理依赖 estimated_cost,财务对账依赖 final_cost。
工程落地
1. 在 SDK Wrapper 或 LLM Gateway 处统一采集
成本台账不应该散落在每个业务服务里。比较稳妥的做法是两层采集:
- SDK Wrapper:所有应用必须通过统一客户端调用模型,在调用前注入
tenant_id、feature、prompt_version等元数据。 - LLM Gateway:对跨语言、跨团队、跨供应商调用做兜底采集,并记录
api_key、provider、model、status、latency、retry_count、cache_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"
}
这个结构有三个设计要点:
- 字段尽量稳定,供应商特有字段放到
extra_usage,不要让主表随供应商变化频繁变更。 - 保留原始
provider_response_usage,方便后续校准。 - 业务维度要在调用前写入,不能事后靠日志猜。
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 可以是 estimated、provider_reported、reconciled、adjusted。这样可以保留成本从估算到校准的全过程,避免后续对账时不知道哪个环节改过数。
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% 需要审批或降级模型 |
| 第三阶段:Chargeback | 将 final_cost 按 cost_center 入账 |
这样做的好处是减少组织阻力。团队看到”成本来自哪里”和”怎么降下来”之后,再接受分摊会容易得多。
适用场景
这套方案适合以下场景:
- 多团队共用一套 LLM 平台。比如客服、搜索、知识库、研发助手和运营生成共用同一个模型网关,如果不做成本归因,所有费用都会堆到平台团队。
- Agent 工作流较多。2026 年一篇关于 Agentic Coding Tasks 的论文指出,Agent 任务的 Token 消耗非常高,且同一任务多次运行的 Token 用量可能存在显著波动。这类场景不能只按请求数收费,否则成本风险会被掩盖。
- 需要跨供应商管理成本。OpenAI、Anthropic、Google、开源模型、私有化模型的 usage 字段和计费方式不同,统一台账可以把不同供应商转换到同一成本语义。
- 需要做模型降级和预算治理。没有成本归因,就无法判断是应该换小模型、缩短上下文、提高缓存命中率,还是限制某个功能的批量任务。
常见误区
误区一:只按 API Key 分摊费用
API Key 适合做权限边界,但不一定等于成本中心。一个 Key 可能被多个应用复用,一个应用也可能调用多个 Key。更合理的做法是把 API Key 当作采集维度之一,同时强制应用传入 tenant_id、team_id 和 feature。
误区二:只看 total_tokens
total_tokens 适合快速粗算,但不适合优化。input、output、cached、reasoning、多模态 token 的优化手段完全不同。只看总数,会把问题掩盖掉。
误区三:把估算成本当财务账单
estimated_cost 是工程指标,适合实时预警;final_cost 才适合财务分摊。两者可能因为价格变更、缓存折扣、批处理折扣、供应商汇率或账单周期产生差异。
误区四:成本治理只靠限额
限额只能防止继续扩大损失,不能解释为什么超额。更完整的治理应包括台账、归因、异常检测、预算 burn rate、优化建议和模型策略回放。
上线检查清单
- ✅ 所有模型调用都有
request_id,并能关联业务日志。 - ✅ SDK Wrapper 或 Gateway 强制写入
tenant_id、team_id、feature、prompt_version。 - ✅ Usage schema 区分
input、output、cached、reasoning、audio、image等类型。 - ✅ 价格表版本化,支持历史价格回放。
- ✅ 成本流水采用追加式事件,保留
estimated、provider_reported、reconciled、adjusted状态。 - ✅ 每日与供应商账单或组织级成本 API 对账。
- ✅ 每个团队有 showback 报表,至少包含月成本、Top features、Top models、Top prompt versions。
- ✅ 预算预警使用 burn rate,而不是只看单日阈值。
- ✅ 对重试、失败、超时、缓存未命中单独统计成本。
- ✅ 正式 Chargeback 前先运行 1-2 个账期的 showback。
FAQ
1. LLM 成本归因应该做到多细?
至少做到团队、应用、功能、模型和 prompt version 级别。请求级明细不一定长期热存储,但必须能在异常排查窗口内回溯。否则成本上涨时只能看到总账,无法定位原因。
2. 本地估算成本和供应商账单不一致怎么办?
不要强行让实时估算等于账单。实时估算用于工程预警,供应商账单用于最终校准。建议保留 estimated_cost、provider_reported_cost 和 final_cost 三个字段,并通过 reconciled 事件记录差异。
3. 如何处理自托管模型的成本?
自托管模型没有供应商 token 账单,但仍然可以做归因。常见做法是把 GPU 小时、实例成本、批处理队列、模型吞吐和请求 token 数结合起来,计算内部单位成本。它的精度不如 API 账单,但足以支持团队分摊和容量规划。