3.1 token 三本账:输入、输出与缓存


3.1 token 三本账:输入、输出与缓存

本节摘要:成本观测的第一课是口径:一次调用的 token 必须分三本账记——输入账(prompt 进去多少)、输出账(completion 出来多少)、缓存账(多少输入命中了 prompt 缓存、多少是新写入)。为什么要拆?因为单价差出一个数量级:输出单价通常为输入的 3~5 倍(示意),缓存命中价约为输入价的 1/10(官方,Anthropic 口径),缓存写入还要略加价。只记总数,你既不知道钱被谁吃掉,也不知道缓存帮你省了多少。本节给出记账与日结算代码(纯标准库),盘点三个典型口径错误,并给出成本突增的三个典型来源——它们是第 3.2 节分摊与第 5 章诊断的案发起点。

学习目标

  • 说清三本账的口径与单价结构,解释"只记总数等于没记"
  • 运行记账代码:usage → 三本账 → 美元,输出日结算与缓存节省估算
  • 避开三个口径错误:自数 token、忽略缓存写加价、只存总额
  • 掌握成本突增的三个典型来源,形成排查直觉

一、三本账的口径

账本 记什么 单价量级(以某旗舰模型示意) 说明
账一 · 输入 未命中缓存的输入 token 3 美元/百万 token(示意) prompt 中真正按原价计费的部分
账二 · 输出 模型生成的 token 15 美元/百万 token(示意) 通常是输入的 3~5 倍(示意,各家不同)
账三 · 缓存 缓存命中(读)token 约 0.3 美元/百万 token 约为输入价 1/10(官方,Anthropic 口径)
缓存写入 token 约 3.75 美元/百万 token 写入加价约 1.25 倍(官方,Anthropic 口径)

三个推论值得背下来:

  1. 输出是最贵的常规项:控制输出长度(max_tokens、结构化摘要)往往比抠输入更见效;
  2. 缓存是最大的杠杆:同一大 prompt 命中与不命中,输入侧成本差约 10 倍(官方口径推算)——多轮会话、固定系统提示词、重复知识库前缀,都该吃缓存红利;
  3. 缓存写入不是免费午餐:一次性请求开缓存只多花钱;命中次数大于等于 2~3 次才回本(示意估算,取决于具体加价与命中率)。

⚠️ 各供应商的缓存规则差异很大(有的自动前缀缓存、有的要显式标记缓存段落、有的不提供缓存计价),字段名与口径一律以其官方定价页为准,本节单价为示意值。

二、记账代码:从 usage 到美元

数据来源是第 2.2 节挂在 span 上的 usage(供应商响应字段,官方口径)。账本行 = 三本账 token 数 + 分摊键 + trace_id。

# token_ledger.py —— token 三本账:记账、算钱、出日结算(纯标准库) from collections import defaultdict from dataclasses import dataclass # 单价表:美元 / 百万 token(示意值,务必替换为供应商当前官方定价) PRICE = { "demo-large": { "input": 3.00, # 账一:输入 "output": 15.00, # 账二:输出 "cache_read": 0.30, # 账三:缓存命中(约为输入价 1/10,官方口径量级) "cache_write": 3.75, # 缓存写入(约 1.25 倍输入价,官方口径量级) }, } @dataclass class Usage: model: str input_tokens: int = 0 # 未命中缓存的输入 output_tokens: int = 0 cache_read_tokens: int = 0 # 命中缓存的部分(不再按账一计价) cache_write_tokens: int = 0 def cost(self) -> float: """返回本次调用费用,单位:美元。""" p = PRICE[self.model] m = 1_000_000 return (self.input_tokens * p["input"] + self.output_tokens * p["output"] + self.cache_read_tokens * p["cache_read"] + self.cache_write_tokens * p["cache_write"]) / m class TokenLedger: def __init__(self): self.rows: list[Usage] = [] def record(self, model: str, **tok) -> None: self.rows.append(Usage(model=model, **tok)) def daily_summary(self) -> None: acc = defaultdict(lambda: [0.0, 0, 0, 0, 0]) # 费用 + 三本账 + 缓存写 for u in self.rows: a = acc[u.model] a[0] += u.cost() a[1] += u.input_tokens; a[2] += u.output_tokens a[3] += u.cache_read_tokens; a[4] += u.cache_write_tokens total = sum(v[0] for v in acc.values()) for model, a in acc.items(): saved = a[3] / 1_000_000 * (PRICE[model]["input"] - PRICE[model]["cache_read"]) print(f"{model}: 费用 {a[0]:.4f} 美元 | 输入 {a[1]:,} | 输出 {a[2]:,}" f" | 缓存读 {a[3]:,} | 缓存写 {a[4]:,} | 缓存节省约 {saved:.4f} 美元") print(f"合计:{total:.4f} 美元") if __name__ == "__main__": ledger = TokenLedger() # 场景(示意):2 万 token 系统提示词首轮写入缓存,随后两轮命中 ledger.record("demo-large", input_tokens=800, output_tokens=600, cache_read_tokens=20_000, cache_write_tokens=0) ledger.record("demo-large", input_tokens=500, output_tokens=700, cache_read_tokens=21_000, cache_write_tokens=0) ledger.record("demo-large", input_tokens=600, output_tokens=400, cache_read_tokens=0, cache_write_tokens=20_000) ledger.daily_summary() ​

输出(示意):

demo-large: 费用 0.1185 美元 | 输入 1,900 | 输出 1,700 | 缓存读 41,000 | 缓存写 20,000 | 缓存节省约 0.1107 美元 合计:0.1185 美元 ​

注意两个设计:命中缓存的输入不进账一(供应商计费时它按缓存价走,重复计价必错);缓存节省额单独算——这个数字是向管理层证明"缓存工程值得做"的最短路径。真实系统里 record 的调用点就在第 2.2 节的 llm.call span 闭合处,并附带 team/feature/user 键(第 3.2 节直接消费)。

三、三个典型口径错误

  1. 自数 token:用分词器数自己的 prompt。工具调用的隐藏 token、推理模型的思考 token、图片折算规则,各家口径不同——只认供应商 usage 字段(官方)。网关若剥掉 usage,先修网关再谈账本。
  2. 忽略缓存写加价:开了缓存就以为全是节省。写入了但从未命中的缓存段是纯支出;命中率要看"写入后实际读到的比例",该指标的定义在第 4 章漂移信号里还会复用。
  3. 只存总额:账本行只存 total_tokens。这不是省事,是销毁证据——三本账一旦合并,成本归因(哪本账涨的)与缓存运营(省了多少)全部失效。历史数据救不回来,从今天开始拆。

四、成本突增的三个典型来源

把三本账摆开,成本突增基本逃不出三类(第 5.1 节故障分类会引用此处):

账一涨(输入膨胀)──▶ 多轮会话历史重发变长 / 记忆注入加码 / few-shot 示例堆砌 账二涨(输出失控)──▶ max_tokens 形同虚设 / agent 生成冗长过程 / 结构化输出失败重试 账三变(缓存失效)──▶ prompt 前缀被改动导致缓存全 miss / 会话中断重写 / 命中率跌破基线 ​

排查口诀:先看三本账哪本涨,再看分摊哪个维度涨,最后下钻到 prompt 版本——三步分别是本节、第 3.2 节与第 2.2 节版本标签的连用。

💡 建议给账本配一张"成本结构周报":三本账占比 + 缓存命中率 + 单次调用成本 P50/P95。三本账占比变了,往往比总额变了更早暴露问题(例如输出占比悄悄从 40% 爬到 60%)。

本节要点回顾

  • 三本账口径:输入、输出、缓存读/写分记;输出约为输入 3~5 倍、缓存读约为输入 1/10(官方口径量级,具体以定价页为准)。
  • 记账代码三要点:命中缓存的输入不进账一、缓存节省单独算、账本行带分摊键与 trace_id。
  • 三个口径错误:自数 token、忽略缓存写加价、只存总额(销毁证据)。
  • 成本突增三来源:输入膨胀、输出失控、缓存失效——排查口诀三步走。

账记准了,但"全公司一个月 4 万美元"仍然无法行动。第 3.2 节把总账摊到团队、功能与用户头上,再和供应商的账单对一次——摊清了,责任与优化方向才会自己浮出来。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U