调用大模型的成本里,有一块被严重低估:重复传输的 system prompt 和上下文。一个典型的 RAG 应用,每次请求都要把几千 token 的文档上下文塞进 prompt;一个代码审查 bot,每次都要附带几百行的规范说明。这些内容在单次对话里几乎一字不差,但每次都按全量 token 计费。
Anthropic 和 OpenAI 都提供了 prompt 缓存 API 机制来解决这个问题。两家的实现思路不同,命中条件也不同,用错了照样按原价跑。本文把两家的缓存逻辑拆开来看,并给出一套可以直接落地的结构设计方法。
---
Claude Opus 4.8 / Sonnet 4.6 / Haiku 4.5 使用的是显式缓存机制,开发者需要在请求体里用 cache_control 字段手动标记需要缓存的 block:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": "你是一个代码审查助手,以下是完整的编码规范文档...",
"cache_control": {"type": "ephemeral"}
}
],
messages=[{"role": "user", "content": user_question}]
)
关键参数和规则:
type: "ephemeral" 是目前唯一支持的缓存类型,TTL 为 5 分钟(官方数据),超时后自动失效cache_control 断点(breakpoint)返回的 usage 对象里会包含 cache_creation_input_tokens 和 cache_read_input_tokens,可以直接监控命中情况。
GPT-5.5 / GPT-5.4 / GPT-5.4-mini 使用的是隐式缓存机制,不需要任何额外字段,系统自动对 ≥ 1024 tokens 的 prompt 前缀进行缓存:
response = client.chat.completions.create(
model="gpt-5.4",
messages=[
{"role": "system", "content": long_system_prompt},
{"role": "user", "content": user_question}
]
)
# 命中情况在 usage 里查看
print(response.usage.prompt_tokens_details.cached_tokens)
OpenAI 缓存规则:
| 维度 | Anthropic | OpenAI | |---|---|---| | 缓存方式 | 显式 cache_control | 自动前缀匹配 | | 最低门槛 | 1024 / 2048 tokens | 1024 tokens | | 命中折扣 | 标准价 × 0.1(节省 90%) | 标准价 × 0.5(节省 50%) | | TTL | 5 分钟 | 5~10 分钟 | | 最大断点数 | 4 个 | 不限(自动) | | 监控字段 | cache_read_input_tokens | cached_tokens |
---
两家机制不同,但有一条共同的核心原则:把不变的内容放前面,把变化的内容放后面。
把 prompt 的内容按变动频率分成三层,从上到下排列:
1. 静态层(完全不变):角色定义、任务描述、输出格式要求、few-shot 示例、大段知识文档 2. 半静态层(按会话变化):当前对话的历史记录 3. 动态层(每次请求都变):用户当前输入
对于 Anthropic,在静态层末尾打上 cache_control 标记;对于 OpenAI,确保 system message 和 few-shot 示例放在 messages 数组最前面,且内容逐字节不变。
很多人把 few-shot 示例放在 user message 里,这在 OpenAI 的自动缓存下会导致每次前缀长度不固定,缓存无法命中。正确做法是把示例统一挪到 system message,或在 messages 数组里作为固定的前置对话轮次出现(role: user + role: assistant 交替),并保证它们在动态内容之前。
messages = [
# 这两轮是固定 few-shot,始终在最前面
{"role": "user", "content": "示例输入 A"},
{"role": "assistant", "content": "示例输出 A"},
{"role": "user", "content": "示例输入 B"},
{"role": "assistant", "content": "示例输出 B"},
# 动态内容从这里开始
*conversation_history,
{"role": "user", "content": current_user_input}
]
Anthropic 的 5 分钟 TTL 意味着,如果你的应用请求间隔超过 5 分钟,缓存就会失效,下一次又是 cache write 价格。对于低频场景,有两种应对方式:
4 个 cache_control 断点可以应对"部分内容变、部分不变"的场景。比如一个法律助手,有一段固定的通用法规(2000 tokens),还有一段每个用户不同的合同文本(1500 tokens):
system=[
{
"type": "text",
"text": FIXED_LEGAL_RULES, # 所有用户共享,永远缓存
"cache_control": {"type": "ephemeral"}
},
{
"type": "text",
"text": user_specific_contract, # 同一用户的多轮对话里缓存
"cache_control": {"type": "ephemeral"}
}
]
这样通用法规对所有用户共享缓存,用户合同对单个用户的多轮对话内缓存,两层各自独立命中。
---
以一个典型的文档问答应用为例:system prompt 4000 tokens,每次用户问题 50 tokens,模型回答 300 tokens,每天 10000 次调用。
使用 Claude Sonnet 4.6:
命中率能做到 80% 以上,前提是你的请求间隔稳定在 5 分钟以内,且 prompt 结构严格遵循"静态在前"原则。
---
如果你在做这类精细化的 API 成本控制,顺带推荐一下我用的接入方案:XycAi 词元平台(https://www.xyc.ai)。它是 OpenAI 兼容协议,一个 API key 能接 200+ 模型,Claude Code、Codex、Gemini CLI 都支持一键配置。GPT-5 和 Claude Opus 4.8 这类旗舰模型有时候低至官方价 1.4 折,加上全球 CN2 直连节点,延迟比直连官方 API 还低——对于需要频繁刷新缓存心跳的场景,这个延迟优势还挺实在的。企业用的话也有大模型算法备案号和全球发票支持。