「这个模型贵」是错的归因;成本是个乘积,每一项都能压一个数量级。
2026 年的真实账单结构:同一个 Sonnet,重度用户月账单从 $80 到 $8000 的差距,90% 不是用得多少,是工程做得粗细。Anthropic 官方文档把账单展开成五项乘积——uncached input、cache write、cache read、batch、output——任何一项忽略掉就少掉一次 5×~10× 的杠杆。Simon Willison 在 2024 年底总结过去一年最反直觉的趋势:「LLM 价格在崩塌」。但真正吃到红利的不是用便宜模型的人,而是会把 caching + routing + batch 三件事叠起来的人——三者乘起来理论可以做到原价的 5% 以下。这一期讲四件事:怎么把账单拆到 token 级、prompt caching 的工程化、什么时候该 route 到便宜模型、batch + 监控告警的最小工程。
Anthropic API 的实际计费公式(官方 prompt caching 文档可验证):
这意味着三件反直觉的事:(1)模型名只决定 base rate,乘上 cache 命中率和 batch 比例后,"贵" 模型可能比 "便宜" 模型更省。(2)output 是真正的成本中心——多数模型 output 单价是 input 的 5 倍;让模型「少说话」(structured output、明确 max_tokens、禁止 chain-of-thought 当 final answer)的 ROI 大于换模型。(3)cache_write 比默认 input 贵 25%,所以「试一试 cache 看看」是负 ROI 的——必须先估命中率。
Simon Willison 维护的 llm-prices.com 是值得收藏的对照表。他 2024 年底有一条结论值得记:用 Gemini Flash 8B 给 68000 张照片生成 caption 总成本 $1.68——说明真正贵的从来不是「调 LLM」,是「用错档位的 LLM 调没必要调的内容」。
把账单结构嵌进每次调用,三行代码做成本归因:
import anthropic
client = anthropic.Anthropic()
def call_with_cost(messages, system, model="claude-sonnet-4-6"):
r = client.messages.create(model=model, max_tokens=1024,
system=[{"type":"text", "text":system,
"cache_control":{"type":"ephemeral"}}],
messages=messages)
u = r.usage
# 单价来自 llm-prices.com 或自己维护的表;这里以 Sonnet 4.6 为例
rate = {"in":3.0, "out":15.0, "cw5":3.75, "cr":0.30} # $/M tok
cost = (u.input_tokens * rate["in"]
+ u.cache_creation_input_tokens * rate["cw5"]
+ u.cache_read_input_tokens * rate["cr"]
+ u.output_tokens * rate["out"]) / 1e6
print(f"${cost:.4f} | hit={u.cache_read_input_tokens/(u.cache_read_input_tokens+u.cache_creation_input_tokens+1):.0%}")
return r, cost
跑一周后看 p50 / p95 cost 分布,95% 的团队会发现:少数 outlier 请求(output 超长 / 没命中 cache)占总成本一半以上——这才是优化目标。
Anthropic prompt caching 的契约:用 cache_control: {type:"ephemeral"} 标记 breakpoint,从请求开头到该 breakpoint 的所有内容(tools → system → messages 顺序)作为一个 cache key。下次请求只要前缀字节完全相同,就以 0.1× input 价命中;任何一个字节变了,整段重写、付 1.25× 写入费。
这条契约带来三条铁律:(1)不变的放前面,变的放后面——tool schema、system prompt、long doc、few-shot 全部前置;user 当前请求放最后。(2)动态拼接是 cache killer——任何随时间变化的 token(当前时间、随机 ID、动态计数器)出现在前缀里,命中率直接归零。(3)breakpoint 上限 4 个——通常放在 tools 末、system 末、history 末,给当前 user 留空。Minimum cacheable 长度按模型,Sonnet 通常 1024 tokens,Opus 4096 tokens(来自官方文档);不到阈值不缓存,所以小请求 cache 是无效的。
「滚动追加 history」是最值得注意的 pattern——多轮对话里把 cache_control 打在最新一轮的前一轮 assistant 消息上,让旧 history 全部命中:
messages = [
{"role":"user", "content":[{"type":"text","text":turn1_user}]},
{"role":"assistant", "content":[{"type":"text","text":turn1_asst}]},
{"role":"user", "content":[{"type":"text","text":turn2_user}]},
{"role":"assistant", "content":[{"type":"text","text":turn2_asst,
"cache_control":{"type":"ephemeral"}}]},
{"role":"user", "content":[{"type":"text","text":turn3_user}]}, # 新
]
turn3 请求过来时,前面 4 条全部走 cache_read(0.1×),只有 turn3_user 是新 input。第 4 轮再请求时把 breakpoint 移到 turn3 末——这是滚动 cache 的标准做法,多轮对话账单瞬间砍掉 80%。5 分钟 TTL 内每命中一次会自动续期,所以高频会话不需要 1h cache(贵 2×)。
cache_read_input_tokens / total_input 监控真实命中率,低于 40% 说明前缀不稳,要重构 prompt 结构,而不是关 cache。
真实流量的难度分布是长尾的:大部分请求(分类、抽取、改写、简单代码)小模型就能做对,少数请求(推理、长上下文、复杂代码)才需要旗舰。两种工程化做法:
什么时候 routing 是负 ROI?两个红线:(1)routing 决策的成本 > 节省——给 Haiku 调用前再跑一次 LLM 分类,分类成本可能已经吃掉差价;(2)routing 错误代价 > 节省——把医疗诊断 route 到 mini 模型省下 $0.01,错一次赔 $10000。规则:routing 适用于高量、低单价错误代价、难度方差大的场景(客服、抽取、初筛、批量生成)。
不要从训路由器开始——先用 LiteLLM 这种 open-source proxy 把多 provider 收拢到一个 OpenAI 兼容端点,再加 fallback 规则。10 分钟见效:
# litellm config.yaml — 把 80% 流量灌给便宜模型,错误时升级
model_list:
- model_name: tier-cheap
litellm_params: {model: claude-haiku-4-5-20251001, api_key: os.environ/ANTHROPIC_API_KEY}
- model_name: tier-mid
litellm_params: {model: claude-sonnet-4-6, api_key: os.environ/ANTHROPIC_API_KEY}
router_settings:
fallbacks:
- tier-cheap: [tier-mid] # 429 / 5xx / context-window 错误自动升级
context_window_fallbacks:
- tier-cheap: [tier-mid] # Haiku 装不下时落到 Sonnet
# client 侧:只调 tier-cheap,复杂请求走显式 tier-mid
client.chat.completions.create(model="tier-cheap", messages=[...])
这套配置做了三件事:fallback(错误时升级)、context window fallback(装不下时升级)、统一 OpenAI 接口(client 代码不需要 if-else 分支)。配合 LiteLLM 的 cost tracking,能直接看每个 tier 的占比和单价——比从头训 router 务实得多。
Anthropic Message Batches API 和 OpenAI Batch API 的契约几乎一致:异步提交、24 小时 SLA、所有 token 价 -50%,且能和 prompt caching 叠加(理论极限 ≈ 原价的 5%——0.5 × 0.1)。它的工程意义不是「便宜一半」,是把可异步的工作流和必须同步的工作流分开,前者天然便宜一半。
判断该不该走 batch 的三个问题:(1)用户在不在等结果?不在 → 可 batch。(2)下一步操作是否阻塞当前 trace?不阻塞 → 可 batch。(3)失败重试容忍 24h 吗?能容忍 → 可 batch。典型可 batch 场景:nightly eval、回填历史数据、文档批量摘要、嵌入计算、报告生成。不可 batch:用户实时对话、agent loop 内部 tool call、需要立刻反馈的 UI。
监控这一层经常被跳过——直到月底账单 3 倍才发现。最小可用监控只要三个信号:per-request cost、cache hit rate、model tier 分布。任何一个突变都对应一类 bug(prompt 改了破坏 cache / 上游误路由到旗舰 / output 失控)。
# A) Batch 提交 — 异步任务一行换 -50%
from anthropic.types.messages.batch_create_params import Request
batch = client.messages.batches.create(requests=[
Request(custom_id=f"doc-{i}", params={"model":"claude-sonnet-4-6",
"max_tokens":1024, "messages":[{"role":"user","content":doc}]})
for i, doc in enumerate(docs)
])
# poll batch.id 直到 processing_status == "ended",再拉 results
# B) 监控三信号 — 接到任何 metrics backend 都行
def emit(usage, model, feature, cost):
hit = usage.cache_read_input_tokens / max(1, usage.cache_read_input_tokens
+ usage.cache_creation_input_tokens
+ usage.input_tokens)
metrics.histogram("llm.cost_usd", cost, tags=[feature, model])
metrics.histogram("llm.cache_hit", hit, tags=[feature, model])
metrics.histogram("llm.output_tokens", usage.output_tokens, tags=[feature])
# C) 三条告警规则(PromQL / Datadog 都一样)
# 1) p95(llm.cost_usd) by feature > 历史 3× ← 突发烧钱
# 2) avg(llm.cache_hit) by feature < 0.4 持续 1h ← 前缀被破坏
# 3) sum(cost) by model{tier=premium} / sum(cost) > 0.6 ← routing 失效
这套监控不到一天搭完,下次「不知道钱花哪了」的故障可以预防。把 batch 比例和 cache 命中率做成 dashboard 头牌指标,你会发现「优化 prompt」和「优化成本」常常是同一件事。
把这四点串成一个真实可执行的复盘工作流——周一拉账单、周五看结果:
max_tokens,多数是默认开到 4096 但实际只用几百。设到合理上限,立刻砍 20-40%。叠加完保守估计 60-80% 成本砍掉,且质量不降。最重要的副产品:你会培养出一种「看 prompt 就能估账单」的工程肌肉——这是 AI 超级个体的基本功之一。
cache_control 标记的边界,从请求开头到该点的内容作为一个 cache key。每请求最多 4 个。max_tokens 限定输出长度的工程手段。Output 是成本中心(通常 5× input 价)。0.25/0.9 ≈ 0.28 次未来命中——即未来 5 分钟内还会访问 ≥1 次同前缀。结论:单次性请求(搜索、一次性 QA)开 cache 是负 ROI;多轮对话、agent loop、batch 处理同一文档都是正 ROI。判定标准:写入时能不能预测「这段 prefix 5 分钟内还会被请求」。不能预测就别开。