跳转至

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_tokenscache_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 合并而来)、usageservice_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.1
  • input_cost IS NOT NULLcache_*_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 总输入成本组装