原创

大模型 Token 成本核算与预算控制:从 usage 到每批任务成本

一套不依赖固定单价的大模型 Token 成本核算方法:统一供应商 usage 口径,按价格版本计算每次调用与每批任务成本,并处理缓存 Token、推理 Token、失败请求和真实账单对账。

AI 工程 Token 成本 大模型 API 预算控制 成本治理 Node.js
大模型 API 生产运维专题 · 第 4/4 篇查看专题目录 →

大模型接入生产系统后,成本问题很快会从“一个 Token 多少钱”变成更难回答的工程问题:一次任务可能调用多个模型,输入里既有固定 Prompt 又有动态正文,供应商对缓存和推理 Token 的定义不同,失败请求甚至拿不到 usage。要让成本可控,不能只在月底查看账单,而要建立一条从原始响应到任务、批次、预算和账单的核算链路。

本文不记录任何具体单价。模型价格会调整,同名模型也可能出现不同版本、区域或计费层级;把某个时间点的价格写死在业务代码或文章中,反而容易制造错误。正确做法是保存带生效时间的价格版本,并以供应商当前官方价格页和最终账单为准。

一、先定义要回答的三个成本问题

一个可用的核算系统至少要回答:

  1. 单次调用预计花费多少?
  2. 一篇文章、一次日报或一批抓取任务总共花费多少?
  3. 内部估算与供应商真实账单为什么存在差异?

这三个问题对应三层数据:调用层记录 usage,任务层聚合业务成本,账单层负责最终校准。不要只保存每天的 Token 总量,否则无法定位是哪类内容、哪个模型或哪次重试推高了成本。

如果系统还没有稳定的任务边界,可以先参考AI 内容管线可观测性,为一次业务运行分配 runId,再为批次分配 batchId。

二、不要假设所有供应商的 usage 字段相同

OpenAI 兼容接口通常返回 prompt_tokens、completion_tokens 和 total_tokens,但“兼容”不等于计费语义完全一致。不同供应商或接口版本还可能提供缓存命中、缓存写入、推理 Token、音频 Token 等明细。

因此,建议同时保存两份数据:

  • rawUsage:原样保存供应商响应中的 usage,便于追溯;
  • normalizedUsage:映射到站内统一字段,用于聚合和告警。

统一结构可以从下面这组字段开始:

function normalizeUsage({ provider, model, usage }) {
  return {
    provider,
    model,
    inputTokens: usage?.prompt_tokens ?? usage?.input_tokens ?? null,
    outputTokens: usage?.completion_tokens ?? usage?.output_tokens ?? null,
    cachedInputTokens:
      usage?.prompt_tokens_details?.cached_tokens ??
      usage?.cache_read_input_tokens ?? null,
    cacheWriteTokens: usage?.cache_creation_input_tokens ?? null,
    reasoningTokens:
      usage?.completion_tokens_details?.reasoning_tokens ?? null,
    totalTokens: usage?.total_tokens ?? null,
    rawUsage: usage ?? null
  };
}

字段缺失时保留 null,不要擅自填成 0。null 表示未知,0 表示供应商明确报告没有产生该类 Token,两者在成本审计时含义完全不同。

三、价格必须版本化,而不是散落在代码里

价格表至少需要模型、币种、单位、生效区间、输入价、输出价和特殊 Token 价格。每次计算都保存所使用的 priceVersion,这样即使下个月价格改变,也能复算历史任务。

{
  "provider": "example",
  "model": "model-version-id",
  "currency": "USD",
  "unitTokens": 1000000,
  "effectiveFrom": "YYYY-MM-DDT00:00:00Z",
  "effectiveTo": null,
  "inputRate": null,
  "outputRate": null,
  "cachedInputRate": null,
  "cacheWriteRate": null,
  "reasoningBillingMode": "provider-defined",
  "sourceUrl": "供应商官方价格页"
}

示例故意不填单价。部署时应由管理员根据官方页面录入并复核,而不是从第三方文章复制。模型别名也要解析到实际计费版本;如果响应能返回具体模型版本,优先记录响应值。

一次调用的基础计算可以写成纯函数:

function tokenCost(tokens, rate, unitTokens) {
  if (tokens == null || rate == null) return null;
  return tokens / unitTokens * rate;
}

function estimateCost(usage, price) {
  const uncachedInput = usage.inputTokens == null
    ? null
    : Math.max(0, usage.inputTokens - (usage.cachedInputTokens ?? 0));

  const parts = {
    input: tokenCost(uncachedInput, price.inputRate, price.unitTokens),
    cachedInput: tokenCost(usage.cachedInputTokens, price.cachedInputRate, price.unitTokens),
    cacheWrite: tokenCost(usage.cacheWriteTokens, price.cacheWriteRate, price.unitTokens),
    output: tokenCost(usage.outputTokens, price.outputRate, price.unitTokens)
  };

  const known = Object.values(parts).filter(Number.isFinite);
  return {
    parts,
    estimatedCost: known.length === Object.keys(parts).length
      ? known.reduce((sum, value) => sum + value, 0)
      : null,
    complete: known.length === Object.keys(parts).length
  };
}

实际实现必须按供应商规则调整。尤其要确认缓存 Token 是包含在输入 Token 中还是独立计数,以及推理 Token 是否已经包含在输出 Token 中。没有看懂官方口径之前,不要简单相加,否则可能重复计费。

四、缓存 Token 与推理 Token 要单独建模

缓存命中通常不能直接等同于“免费”。有的供应商分别计价缓存读取和缓存写入,有的按折扣输入计费,还有的对缓存有效期或最小前缀有要求。核算时至少记录:

  • 缓存写入 Token;
  • 缓存读取或命中 Token;
  • 未缓存输入 Token;
  • 对应价格规则和价格版本。

推理 Token 也不能一概而论。部分推理模型会在 usage 明细中报告 reasoning tokens,但最终计费字段、可见输出字段与总输出字段之间的关系由供应商定义。正确的策略是保存原始明细,使用供应商文档规定的计费口径,并通过账单对账验证,而不是自行推断。

五、失败请求不能默认成本为零

请求失败有多种阶段:连接建立前失败、供应商接受后超时、流式输出中断、返回错误状态、客户端拿到内容但没有 usage。只有供应商明确说明未计费时,才能把成本认定为零。

建议给每次调用增加成本状态:

  • calculated:usage 完整且价格版本匹配;
  • estimated:使用本地 Token 估算或不完整口径;
  • unknown:请求可能已被处理,但没有可靠 usage;
  • reconciled:已与供应商账单核对。

对于 unknown,预算系统可采用保守占用:按请求前估算的输入 Token 加最大输出上限预留预算,等账单到达后再释放或修正。这样不会因为大量超时和重试,让实时仪表盘错误地显示“零成本”。重试策略还应记录 attempt 和幂等业务标识,具体做法可参考大模型 API 429、超时和 5xx 排查。

六、从单次调用聚合到每批任务成本

每条成本记录应带上业务维度,而不只是模型维度:

{
  "requestId": "供应商或本地请求标识",
  "runId": "一次业务运行",
  "batchId": "一批任务",
  "articleId": "可选的内容标识",
  "operation": "classify|summarize|draft|review",
  "provider": "example",
  "model": "model-version-id",
  "attempt": 1,
  "usageStatus": "calculated",
  "priceVersion": "example-model-YYYYMMDD",
  "estimatedCost": null,
  "currency": "USD"
}

批次成本不是简单计算成功请求。它应该包含成功调用、重试调用和未知成本预留,并明确区分:

batchEstimated = calculated + estimated
batchReserved  = unknown requests 的保守预留
batchExposure  = batchEstimated + batchReserved

按 operation 聚合后,就能知道成本主要来自分类、总结还是写作;按 model 聚合后,则能判断模型路由是否符合预期。多模型的统一配置和回退关系可参考OpenAI 兼容 API 多模型统一配置。

七、预算控制要在调用前、调用中和调用后同时发生

只设置月度告警不够。更实用的是三层控制:

调用前:预算准入

根据本地分词器或供应商 Token 计数接口估算输入,再结合 max_tokens 和当前价格版本计算最坏暴露。如果项目、批次或租户的剩余预算不足,就拒绝、排队、缩短上下文或切换到已批准的低成本模型。

调用中:增量观察

流式接口如果只有结束时才返回 usage,就不要把未结束请求视为零成本。设置并发上限、最大输出上限和超时,并将正在运行请求的预留额计入预算。

调用后:阈值告警

告警至少覆盖日预算、月预算、单批任务、单篇内容和未知成本数量。可以设置多级阈值,例如提醒、限制新批次、人工确认;具体阈值应根据站点自己的账单和业务价值制定,不应照搬他人的百分比。

Prompt 变更也可能改变 Token 分布。把 Prompt 版本写入成本记录,结合生产环境 Prompt 版本管理,才能比较版本发布前后的输入长度、输出长度和成本变化,而不是只看总体账单。

八、最终以真实账单对账,不把内部估算当财务事实

内部 usage 核算适合实时控制,但供应商账单才是最终依据。建议每天或每个账期执行一次对账:

  1. 固化内部调用明细和当时使用的价格版本;
  2. 从供应商官方账单或用量接口取得同一时区、同一项目和同一币种的数据;
  3. 按日期、项目、模型或供应商允许的最细维度聚合;
  4. 计算内部估算与账单差额,但不要在粒度不同的情况下强行逐请求匹配;
  5. 将汇率、税费、赠送额度、批处理折扣、价格生效时间和账单延迟列为差异原因;
  6. 超出站内阈值时保留原始证据并人工复核;
  7. 对账完成后把记录标记为 reconciled,修正报表但不覆盖原始 usage。

多币种环境不要使用当天随手查询的汇率重算历史成本。应保存账单币种、内部展示币种、汇率来源和汇率日期;财务报表则遵循组织既定的会计规则。

九、上线前检查清单

  • 是否原样保存供应商 usage 和响应中的模型版本?
  • 字段缺失是否使用 null,而不是错误地填 0?
  • 价格是否带来源、生效时间、币种和单位?
  • 缓存读取、缓存写入、推理 Token 是否按供应商口径处理?
  • 超时、流式中断和无 usage 请求是否进入未知成本队列?
  • 重试是否计入同一业务任务的总成本?
  • 批次是否同时展示已算成本与未知成本预留?
  • Prompt 版本、operation、runId 和 batchId 是否可追踪?
  • 是否定期与真实账单对账并解释差异?
  • 价格或 usage schema 变化时,是否有告警与复审机制?

结语

大模型成本治理的核心不是寻找一个永远正确的单价公式,而是保留可复算的数据链:供应商原始 usage、站内统一口径、带生效时间的价格版本、业务批次聚合、未知成本预留和真实账单对账。做到这些之后,系统才能在调用前阻止预算失控,在运行中识别异常,并在账期结束时解释每一类差异。

实测与内容说明

实测记录

  • 正文中的代码只演示数据建模与缺失值处理,未绑定任何供应商当前单价;部署前必须依据所用供应商官方文档校正字段映射和计费公式。
  • 已检查示例不会把缺失 usage 当作零,也不会把缓存 Token 或推理 Token未经确认地重复相加。
  • 文章未声称具体节省比例、准确率、吞吐量或账单金额;预算阈值留给站点基于真实业务数据设置。

参考资料

内容版本 1.0 · 审核:站点编辑 · 计划复审:2026-11-13