你每天调大模型是不是每次都把 system prompt 原封不动发一遍?你知不知道这部分其实是白花花的冤枉钱?今天这篇文章,看完就能省。
你可能在浪费 40% 的 API 费用
先看一组真实数据。我用一个典型的 RAG 应用做测试——每次请求都带 3000 token 的 system prompt,加上用户问题和检索到的文档。
| 场景 | 单次请求 Token 数 | Prompt 缓存命中 | 节省金额 |
|---|---|---|---|
| system prompt(每次都发) | 3000 | 0% | ¥0 |
| 用户问题(每次不同) | 500 | 0% | ¥0 |
| 检索文档(每次不同) | 2000 | 0% | ¥0 |
| 总计 | 5500 | 0% | — |
但如果你启用了 Prompt Caching:
| 场景 | 单次请求 Token 数 | Prompt 缓存命中 | 实际计费 | 节省 |
|---|---|---|---|---|
| system prompt | 3000 | ✅ 命中缓存 | 按缓存价计费 | 90% off |
| 用户问题 | 500 | ❌ | 原价 | — |
| 检索文档(部分重复) | 2000 | 部分命中 | 部分优惠 | ~50% off |
| 总计 | 5500 | — | 约 2200 token 按原价 | ~43% |
每天调 10 万次,一个月能省多少钱不用我算了吧。
Prompt Caching 是什么?
简单说:大模型会把”经常重复出现的 prompt 片段”缓存起来,第二次遇到同样的内容时,就不用重新推理了,直接复用缓存结果——这是从底层推理引擎(Transformer)偷来的优化,不是简单的客户端缓存。
技术原理也不复杂。大模型推理时,每个 token 都要经过 Attention 计算。如果你连续两次请求的 prompt 有大量重复前缀(比如 system prompt、few-shot 示例),这些 token 的 Key-Value 矩阵是可以复用的。
三巨头缓存价格对比
| 厂商 | 缓存 Token 折扣 | 最小缓存长度 | 缓存有效期 | 支持状态 |
|---|---|---|---|---|
| Anthropic Claude | 90% off | 1024 token | 5 分钟(自动续) | ✅ 已上线 |
| OpenAI GPT | 50% off | 1024 token | 5-10 分钟 | ✅ 已上线 |
| DeepSeek | 官方支持中 | 待公布 | 待公布 | 🔄 即将上线 |
| 通义千问 | 50% off | 1024 token | 5 分钟 | ✅ 已上线 |
Anthropic 的 90% off 折扣是最狠的——意味着你用 Claude 的成本几乎可以和 DeepSeek 打平。
实战一:Anthropic Claude 原生 Prompt Caching
最简写法:3 行代码搞定
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-xxx")
response = client.beta.prompt_caching.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=[
{
"type": "text",
"text": "你是一个资深 Python 后端工程师,擅长云原生开发和数据库优化。回答问题时给出可运行的代码示例,并在代码中添加中文注释。",
"cache_control": {"type": "ephemeral"} # 就这一行!
}
],
messages=[{"role": "user", "content": "帮我写一个 FastAPI 的中间件,记录每个请求的耗时"}]
)
print(response.content[0].text)
# 查看缓存命中情况
print(f"Input tokens: {response.usage.input_tokens}")
# 返回中会显示 cache_creation_input_tokens 和 cache_read_input_tokens
print(response.usage)
cache_control: {"type": "ephemeral"} 这一行告诉 Claude:“这个 system prompt 帮我缓存起来”。之后 5 分钟内的每一次请求,只要 system prompt 不变,这部分就按 10% 的价格计费。
更复杂的场景:缓存多轮对话的前几轮
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-xxx")
def chat_with_cache(user_message, conversation_history=None):
"""带缓存的多轮对话"""
messages = []
# 缓存前面几轮的对话(比如缓存前 2 轮作为上下文锚点)
if conversation_history and len(conversation_history) > 2:
# 前 2 轮对话标记为可缓存
cached_part = conversation_history[:2]
uncached_part = conversation_history[2:]
for msg in cached_part:
messages.append({
"role": msg["role"],
"content": [{
"type": "text",
"text": msg["content"],
"cache_control": {"type": "ephemeral"}
}]
})
for msg in uncached_part:
messages.append({"role": msg["role"], "content": msg["content"]})
# 添加新消息
messages.append({"role": "user", "content": user_message})
response = client.beta.prompt_caching.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=messages
)
return {
"reply": response.content[0].text,
"cache_hit": response.usage.cache_read_input_tokens or 0,
"cache_created": response.usage.cache_creation_input_tokens or 0,
"total_input": response.usage.input_tokens
}
# 模拟多轮对话
history = []
# 第一轮:创建缓存
result = chat_with_cache("什么是 Kubernetes 的 Service?", history)
print(f"第1轮 - 缓存创建: {result['cache_created']} token, 总输入: {result['total_input']}")
history.append({"role": "user", "content": "什么是 Kubernetes 的 Service?"})
history.append({"role": "assistant", "content": result["reply"]})
# 第二轮:命中缓存!
result = chat_with_cache("那 ClusterIP 和 NodePort 有什么区别?", history[:2])
print(f"第2轮 - 缓存命中: {result['cache_hit']} token, 总输入: {result['total_input']}")
运行结果大概是:
第1轮 - 缓存创建: 1520 token, 总输入: 1580
第2轮 - 缓存命中: 1520 token, 总输入: 1730 ← 1520 token 按 10% 计费!
实战二:LangChain 自动缓存
如果你用 LangChain,原生也已经支持了。不用手写 cache_control:
from langchain_anthropic import ChatAnthropic
# 创建带缓存的 Claude 模型
llm = ChatAnthropic(
model="claude-sonnet-4-20250514",
anthropic_api_key="sk-ant-xxx",
max_tokens=1024,
temperature=0,
)
# 用 LangChain 的缓存 API
from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache
from anthropic_cache import AnthropicPromptCache # LangChain 的 Anthropic 缓存封装
# 启用 Anthropic 服务端 Prompt Caching
cache = AnthropicPromptCache()
set_llm_cache(cache)
# 第一次调用 —— 创建缓存
response = llm.invoke("写一个 Python 冒泡排序")
print(response.content)
# 第二次调用(同样的 system prompt 前缀)
# —— 自动命中缓存,输入 token 按 10% 计费
response = llm.invoke("写一个 Python 快速排序")
print(response.content)
更实用:用 LangChain 管理缓存策略
from langchain_core.prompts import ChatPromptTemplate
from langchain_anthropic import ChatAnthropic
# 把 system prompt 设计为"高速缓存友好"的结构
system_template = """
你是一个 {role},擅长 {skills}。
回答规则:
1. 代码要完整可运行
2. 添加中文注释
3. 遇到不确定的直接说不知道
"""
prompt = ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{question}")
])
llm = ChatAnthropic(model="claude-sonnet-4-20250514", anthropic_api_key="sk-ant-xxx")
chain = prompt | llm
# 同一角色、不同问题 —— system prompt 部分命中缓存
for q in ["写个排序", "写个二分查找", "写个链表反转"]:
response = chain.invoke({
"role": "资深 Python 后端工程师",
"skills": "算法和数据结构",
"question": q
})
print(f"Q: {q} → {response.content[:50]}...")
进阶策略:5 个让缓存命中率翻倍的技巧
技巧 1:把 system prompt 放在请求最前面
缓存是按 前缀匹配 的。system prompt 放最前面,后面接入动态内容,确保前缀始终可命中:
# ✅ 正确:system prompt 在最前面
messages = [
{"role": "system", "content": "你是一个 Python 专家..."}, # 缓存
{"role": "user", "content": f"帮我写代码:{dynamic_content}"} # 动态
]
# ❌ 错误:system prompt 在 user 后面(缓存失效)
messages = [
{"role": "user", "content": f"参考这个:{dynamic_content}"},
{"role": "system", "content": "你是一个 Python 专家..."} # 没缓存!
]
技巧 2:工具定义也放缓存区
如果你已经定义了 tools 数组且不变,Anthropic 支持缓存工具定义:
tools = [
{
"name": "get_weather",
"description": "获取指定城市的天气",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
},
"cache_control": {"type": "ephemeral"} # 缓存工具定义
}
]
技巧 3:长文档分段缓存
RAG 场景中,如果每次检索出的 top-k 文档有一部分是重复的,把最常出现的文档标记为可缓存:
# 高频文档打缓存标记
cached_docs = [] # 出现 > 50% 的文档
fresh_docs = [] # 每次不同的新文档
for doc in retrieved_docs:
if doc["frequency"] > 0.5:
cached_docs.append({
"type": "text",
"text": doc["content"],
"cache_control": {"type": "ephemeral"}
})
else:
fresh_docs.append({"type": "text", "text": doc["content"]})
技巧 4:缓存最小 1024 token,内容要够长
Anthropic 的缓存只对 ≥ 1024 token 的内容生效。如果你的 system prompt 太短(比如只有 200 token),缓存不会触发。解决方案:把 system prompt 写详细一点,或者把一些固定的示例也放进去。
技巧 5:开发环境用客户端缓存,生产环境用服务端缓存
from functools import lru_cache
# 开发环境:Python 客户端缓存(完全免费)
@lru_cache(maxsize=100)
def cached_llm_call(prompt_hash):
return call_llm(prompt_hash)
# 生产环境:Anthropic 服务端缓存
def production_llm_call(messages):
return client.beta.prompt_caching.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=[{
"type": "text", "text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"}
}],
messages=messages
)
真实成本对比:10 万次请求省多少?
| 方案 | System Prompt | User Prompt | 缓存折扣 | 单次成本 | 10 万次总成本 |
|---|---|---|---|---|---|
| Claude Sonnet 无缓存 | 3000 token | 1500 token | 0% | ~$0.022 | ~$2,200 |
| Claude Sonnet 有缓存 | 3000 token | 1500 token | 90% off 缓存部分 | ~$0.011 | ~$1,100 |
| DeepSeek-V3 无缓存 | 3000 token | 1500 token | — | ~$0.002 | ~$200 |
| Claude + 缓存 | — | — | — | — | 省 ~$1,100 |
Claude Sonnet 虽然比 DeepSeek-V3 贵,但启用缓存后差距缩小很多。如果你需要 Claude 的推理质量,但预算有限——缓存是必选项。
总结
Prompt Caching 不是玄学,是确定性优化:
- Anthropic Claude 支持最完善,system prompt + 工具定义 + 长上下文全部可缓存,90% off
- OpenAI GPT 也支持了,50% off,用法类似
- LangChain 已原生集成,开箱即用
- 缓存对用户完全透明,不需要改业务逻辑
下次你写 RAG 或者 Agent 应用时,花 5 分钟加个 cache_control,可能就省了 40% 的 API 费用。省下来的钱,够请团队喝一周的奶茶了。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~
博客:y1cloud.cn
