04 · Prompt Cache 计费与成本计算¶
本文梳理 cache_creation_input_tokens / cache_read_input_tokens 这些字段在 LiteLLM 内部如何参与成本计算。重点不是上游计费规则(看 Anthropic 文档),而是 LiteLLM 是怎么读价格、怎么算钱、哪里容易漏配。
本文不讨论路由黏性。如果你只想让请求"黏到同一个 deployment",看 01-mechanism.md。 计费 vs 路由是两套互不相关的代码路径。
1. 计费字段全清单¶
1.1 价格 JSON 里的字段¶
model_prices_and_context_window.json 中每个模型条目可能含以下 cache 相关字段:
| 字段 | 含义 | 谁定义 |
|---|---|---|
cache_creation_input_token_cost |
缓存写入(首次)的单 token 价 | 上游计价 |
cache_creation_input_token_cost_above_1hr |
1 小时缓存的写入单价(Claude 4.5+) | 上游计价 |
cache_read_input_token_cost |
缓存命中(读取)的单 token 价,通常远低于 prompt | 上游计价 |
cache_creation_input_token_cost_above_{N}_tokens |
大 prompt 阈值后的缓存写入分级价 | 上游计价 |
cache_creation_input_token_cost_above_1hr_above_{N}_tokens |
1hr + 大 prompt 双重分级 | 上游计价 |
cache_read_input_token_cost_above_{N}_tokens |
大 prompt 阈值后的读取分级价 | 上游计价 |
举例(典型 Claude Sonnet 4.5 配置,价格仅示意):
"claude-sonnet-4-5-20250929": {
"input_cost_per_token": 0.000003,
"output_cost_per_token": 0.000015,
"cache_creation_input_token_cost": 0.00000375,
"cache_creation_input_token_cost_above_1hr": 0.000006,
"cache_read_input_token_cost": 0.0000003,
...
}
→ 写入比普通 prompt 贵 25%、读取比普通 prompt 便宜 10 倍、1hr 缓存写入贵一倍。
1.2 响应 Usage 里的字段¶
LiteLLM 归一化后的 Usage 含:
class Usage(...):
prompt_tokens: int # 总输入 token (含 cache 部分)
completion_tokens: int
total_tokens: int
cache_creation_input_tokens: int # ← 这次实际写入缓存的 token 数
cache_read_input_tokens: int # ← 这次实际从缓存读取的 token 数
prompt_tokens_details: PromptTokensDetailsWrapper
class PromptTokensDetailsWrapper(...):
cached_tokens: int # = cache_read_input_tokens
cache_creation_tokens: int # = cache_creation_input_tokens
cache_creation_token_details: Optional[CacheCreationTokenDetails]
class CacheCreationTokenDetails(BaseModel):
ephemeral_5m_input_tokens: Optional[int]
ephemeral_1h_input_tokens: Optional[int]
重要语义:
- cache_read_input_tokens 和 cache_creation_input_tokens 互斥但都计入 prompt_tokens(Anthropic 上游返回的 input_tokens 已经把它们排除掉了,LiteLLM 在 transformation.py:1553,1559 累加回去)
- cache_read_input_tokens 是"本次请求复用了缓存的 token 数"
- cache_creation_input_tokens 是"本次请求新写入缓存的 token 数"
- 一次请求里两者可以同时 > 0(前缀复用缓存 + 末尾新增内容触发新缓存)
2. 成本计算链路¶
2.1 价格查询:_get_token_base_cost¶
litellm/litellm_core_utils/llm_cost_calc/utils.py:158-290
入参:model_info(从价格 JSON / DB / litellm_params 合并而来)、usage、service_tier
返回:5 元素 tuple
(prompt_base_cost, completion_base_cost, cache_creation_cost,
cache_creation_cost_above_1hr, cache_read_cost)
关键查询逻辑:
cache_creation_cost = cast(
float, _get_cost_per_unit(model_info, cache_creation_cost_key)
)
cache_creation_cost_above_1hr = cast(
float,
_get_cost_per_unit(model_info, "cache_creation_input_token_cost_above_1hr"),
)
cache_read_cost = cast(float, _get_cost_per_unit(model_info, cache_read_cost_key))
Service tier 感知:通过 _get_service_tier_cost_key()(行 173-178)支持 flex / priority 等服务级别有独立价格的场景:
cache_creation_cost_key = _get_service_tier_cost_key(
"cache_creation_input_token_cost", service_tier
)
# 例如 service_tier="flex" → "cache_creation_input_token_cost_flex"
大 prompt 分级价(行 217-278):
- 触发条件:usage.prompt_tokens > threshold(threshold 从 input_cost_per_token_above_{N} 解析)
- 触发后:对应位置的 cache 价也走 _above_{N}_tokens 变体
2.2 缓存写入成本:5m vs 1h 拆分¶
litellm/litellm_core_utils/llm_cost_calc/utils.py:360-391:
def calculate_cache_writing_cost(
cache_creation_tokens: int,
cache_creation_token_details: Optional[CacheCreationTokenDetails],
cache_creation_cost_above_1hr: float,
cache_creation_cost: float,
) -> float:
total_cost: float = 0.0
if cache_creation_token_details is not None:
cache_creation_tokens_5m = cache_creation_token_details.ephemeral_5m_input_tokens
cache_creation_tokens_1h = cache_creation_token_details.ephemeral_1h_input_tokens
total_cost += (
cache_creation_tokens_5m * cache_creation_cost
if cache_creation_tokens_5m is not None else 0.0
)
total_cost += (
cache_creation_tokens_1h * cache_creation_cost_above_1hr
if cache_creation_tokens_1h is not None else 0.0
)
else:
total_cost += cache_creation_tokens * cache_creation_cost
return total_cost
→ 如果上游返回 usage.cache_creation.{ephemeral_5m_input_tokens, ephemeral_1h_input_tokens} 细分(Anthropic 原生 + Bedrock Invoke)→ 按 5m 走 cache_creation_cost,按 1h 走 cache_creation_cost_above_1hr,分别计算后求和。
→ Bedrock Converse 不返回细分(03-provider-matrix.md §2.5)→ 走 else 分支,全部按 cache_creation_cost 单价计算。
2.3 总输入成本组装:_calculate_input_cost¶
litellm/litellm_core_utils/llm_cost_calc/utils.py:513-560 大致逻辑:
# 1. 普通 prompt token 部分
prompt_cost = (prompt_tokens - cache_hit_tokens - cache_creation_tokens) * prompt_base_cost
# 2. 缓存命中部分
prompt_cost += cache_hit_tokens * cache_read_cost
# 3. 缓存写入部分(含 5m / 1h 拆分)
if cache_creation_tokens or cache_creation_token_details is not None:
prompt_cost += calculate_cache_writing_cost(
cache_creation_tokens,
cache_creation_token_details,
cache_creation_cost_above_1hr,
cache_creation_cost,
)
→ 这是 response_cost 字段的核心来源,后续会写到 SpendLogs、参与 budget 限流、显示在 UI。
3. _get_cost_per_unit:静默 0.0 的陷阱¶
litellm/litellm_core_utils/llm_cost_calc/utils.py(搜 def _get_cost_per_unit)
问题行为:当 model_info 里没有该字段(key 不存在 / 值为 None)时,函数默认返回 0.0,不抛错。
后果:
- cache_creation_input_token_cost 漏配 → 缓存写入成本 = 0
- cache_read_input_token_cost 漏配 → 缓存命中成本 = 0
- cache_creation_input_token_cost_above_1hr 漏配 → 1 小时缓存写入 = 0(静默免费)
直接后果:
- 用户花了缓存价的钱,但 LiteLLM 算成 0
- response_cost 被低估
- spend 累加慢 → budget 限流失效(业务用户超额仍能正常用)
详细案例见 docs/billing-and-pricing/05-cache-pricing-bugs.md。
3.1 已知 Bug 列表¶
3.1.1 cache_creation_input_token_cost_above_1hr 缺失 → 1hr 缓存静默免费¶
触发条件:客户端在请求里加 cache_control: {type: ephemeral, ttl: "1h"},但价格 JSON 没有 cache_creation_input_token_cost_above_1hr 字段。
结果:所有 1h 缓存写入按 0 价计算。
修复:在 DB litellm_params 或 model_prices_and_context_window.json 里显式加上该字段。
3.1.2 UI 编辑 litellm_params 漏字段 → cache 全免费¶
触发条件:通过 UI 给某个 deployment 设了 input_cost_per_token(强制用户自定义价格),但忘了设 cache 字段。
根因:_create_deployment 合并 litellm_params 时只合并非 None 字段。一旦用户开启了"自定义价格",model_prices JSON 里的值就被无视了,而 UI 表单又没列 cache 字段 → cache 永远 0。
修复:UI 表单加上 cache 字段全集,或者强制要求填齐。
3.1.3 中转站不返回 cache_creation_input_tokens → 计费按 0¶
触发条件:中转站把 response usage 里的 cache 字段吃掉。
结果:LiteLLM 解析到 cache_creation_input_tokens = 0,但 prompt_tokens 包含了所有 token(包括应该走缓存价的部分)→ 全部按 prompt 单价算。
这跟"价格漏配"语义不同:价格本身没问题,是上游计费透明度问题。
→ 看 03-provider-matrix.md §7 OpenAI 兼容中转站 的验证脚本。
4. 验证你的价格配置是否齐全¶
4.1 检查价格 JSON 字段¶
# Anthropic 模型
python -c "
import json
with open('model_prices_and_context_window.json') as f:
data = json.load(f)
m = data['claude-sonnet-4-5-20250929']
required = [
'cache_creation_input_token_cost',
'cache_creation_input_token_cost_above_1hr',
'cache_read_input_token_cost',
]
for k in required:
print(f'{k}: {m.get(k, \"❌ MISSING\")}')"
期望全部非 ❌ MISSING 且非 null。
4.2 检查 DB 中的 litellm_params 是否漏字段¶
SELECT
model_name,
litellm_params->>'input_cost_per_token' AS input_cost,
litellm_params->>'cache_creation_input_token_cost' AS cache_create_cost,
litellm_params->>'cache_read_input_token_cost' AS cache_read_cost,
litellm_params->>'cache_creation_input_token_cost_above_1hr' AS cache_create_1hr_cost
FROM "LiteLLM_ProxyModelTable"
WHERE litellm_params::text ILIKE '%cache%' OR litellm_params::text ILIKE '%input_cost%';
判定:
input_cost IS NULL→ 走 JSON 价格,看 §4.1input_cost IS NOT NULL但cache_*_cost IS NULL→ 危险:自定义了 input cost 但漏了 cache cost,所有 cache 计 0
4.3 端到端验证¶
发一个典型 cache 请求,检查 spend 计入:
import litellm
resp = litellm.completion(
model="claude-sonnet-4-5-20250929",
messages=[
{"role": "system", "content": [
{"type": "text", "text": "..." * 2000, "cache_control": {"type": "ephemeral"}}
]},
{"role": "user", "content": "查询"}
],
)
print("usage:", resp.usage)
print("response_cost:", resp._hidden_params.get("response_cost"))
手工对比:
expected_cost = cache_creation_tokens * cache_creation_cost
+ cache_read_tokens * cache_read_cost
+ non_cache_prompt_tokens * input_cost_per_token
+ completion_tokens * output_cost_per_token
差距大于 5% → 排查 §4.1 / §4.2。
5. 跟 SpendLogs / Budget 的接口¶
5.1 写入 SpendLogs¶
response_cost 计算完成后,会被写入 LiteLLM_SpendLogs 表的 spend 列、prompt_tokens / completion_tokens 列(含 cache 部分)。
注意:SpendLogs 表里没有单独的 cache_creation_tokens / cache_read_tokens 列 —— 这些信息只存在于 standard_logging_payload JSON 里(往 S3 / GCS 推送时可见),SQL 查询不到。
5.2 Budget 累加¶
spend 字段会累加到:
- LiteLLM_UserTable.spend
- LiteLLM_VerificationToken.spend(即 api_key 的 spend)
- LiteLLM_TeamTable.spend
Cache 漏配导致的 response_cost = 0 → 这些 spend 不增长 → 用户超额仍放行。详见 rate-limiting/05-skip-budget-checks-bug.md。
6. 速查表¶
6.1 字段命名(Provider vs LiteLLM 归一化)¶
| Provider 字段 | LiteLLM 归一化字段 |
|---|---|
Anthropic cache_creation_input_tokens |
Usage.cache_creation_input_tokens |
Anthropic cache_read_input_tokens |
Usage.cache_read_input_tokens |
Anthropic cache_creation.ephemeral_5m_input_tokens |
CacheCreationTokenDetails.ephemeral_5m_input_tokens |
Anthropic cache_creation.ephemeral_1h_input_tokens |
CacheCreationTokenDetails.ephemeral_1h_input_tokens |
Bedrock Converse cacheWriteInputTokens |
Usage.cache_creation_input_tokens |
Bedrock Converse cacheReadInputTokens |
Usage.cache_read_input_tokens |
OpenAI prompt_tokens_details.cached_tokens |
PromptTokensDetailsWrapper.cached_tokens |
Vertex Gemini cached_content_token_count |
PromptTokensDetailsWrapper.cached_tokens |
6.2 价格 JSON 字段全集¶
{
"<model_id>": {
// 标准
"input_cost_per_token": 0.000003,
"output_cost_per_token": 0.000015,
// Cache 基础
"cache_creation_input_token_cost": 0.00000375,
"cache_read_input_token_cost": 0.0000003,
// 1hr cache (Claude 4.5+)
"cache_creation_input_token_cost_above_1hr": 0.000006,
// 大 prompt 分级
"input_cost_per_token_above_200k_tokens": 0.000006,
"output_cost_per_token_above_200k_tokens": 0.0000225,
"cache_creation_input_token_cost_above_200k_tokens": 0.0000075,
"cache_creation_input_token_cost_above_1hr_above_200k_tokens": 0.000012,
"cache_read_input_token_cost_above_200k_tokens": 0.0000006,
// Service tier (可选)
"input_cost_per_token_flex": 0.0000015,
"cache_creation_input_token_cost_flex": 0.000001875,
"cache_read_input_token_cost_flex": 0.00000015
}
}
6.3 关键代码路径¶
| 文件 | 函数 | 作用 |
|---|---|---|
| litellm/types/utils.py | Usage.__init__ |
解析 provider 响应里的 cache 字段 |
| litellm/llms/anthropic/chat/transformation.py:1539 | _compute_usage |
Anthropic / Bedrock Invoke 的 cache 字段归一化 |
| litellm/llms/bedrock/chat/converse_transformation.py:1623 | _transform_usage |
Bedrock Converse 的 cache 字段归一化 |
| litellm/litellm_core_utils/llm_cost_calc/utils.py:158 | _get_token_base_cost |
5 元素价格 tuple |
| litellm/litellm_core_utils/llm_cost_calc/utils.py:360 | calculate_cache_writing_cost |
5m / 1h 拆分计费 |
| litellm/litellm_core_utils/llm_cost_calc/utils.py:513 | _calculate_input_cost |
总输入成本组装 |