为什么 Prompt Caching 是成本优化的第一步
你在用 Claude API 构建 RAG 应用。每次用户提问,你发送:
System Prompt (500 tokens)
+ 知识库检索结果 (3000 tokens)
+ 对话历史 (2000 tokens)
+ 用户问题 (50 tokens)
= 5550 tokens 输入
如果每天有 1 万次查询,月输入 token 量约 1.67B tokens。
没有缓存:1.67B × $3/M = $5,010/月
有缓存(假设 System Prompt + 知识库部分命中):
- 首次:5550 tokens × $3/M
- 后续:3500 tokens(缓存命中)× $0.30/M + 2050 tokens(新内容)× $3/M
月成本降至约 $800——节省 84%。
这不是理论值。这是我在生产环境中的真实数据。
Claude 的 Prompt Caching
工作原理
Claude 的 Prompt Caching 需要你显式标记缓存断点(cache breakpoint)。断点之前的内容会被缓存,断点之后的内容每次重新计算。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: "你是一个专业的客服助手。以下是产品知识库:\n\n" + knowledgeBase,
cache_control: { type: "ephemeral" }, // 标记缓存断点
},
],
messages: [
{ role: "user", content: userQuestion },
],
});
cache_control: { type: "ephemeral" } 告诉 Claude:“这个内容块可以被缓存”。下一次请求如果 system 内容完全相同,就会命中缓存。
多断点场景
你可以设置多个缓存断点:
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: systemPrompt,
cache_control: { type: "ephemeral" }, // 断点 1:System Prompt
},
{
type: "text",
text: knowledgeContext,
cache_control: { type: "ephemeral" }, // 断点 2:知识库上下文
},
],
messages: [
...conversationHistory, // 对话历史(不缓存,每轮变化)
{ role: "user", content: userQuestion },
],
});
缓存命中的条件
- 前缀必须逐 token 完全一致——哪怕多一个空格都会导致缓存失效
- 最小缓存长度 1024 tokens——太短的 Prompt 不会被缓存
- TTL 约 5 分钟——5 分钟内没有新的命中请求,缓存过期
- 同一 API Key 范围内共享——不同用户的请求可以共享缓存(只要前缀一致)
如何验证缓存命中
const response = await client.messages.create({ ... });
console.log(response.usage);
// {
// input_tokens: 5550,
// output_tokens: 200,
// cache_creation_input_tokens: 3500, // 首次:创建缓存
// cache_read_input_tokens: 0, // 首次:没有命中
// }
// 第二次请求(相同前缀)
// {
// input_tokens: 2050, // 只有非缓存部分
// output_tokens: 180,
// cache_creation_input_tokens: 0, // 不需要创建
// cache_read_input_tokens: 3500, // 命中缓存!
// }
cache_read_input_tokens > 0 表示缓存命中。
OpenAI 的 Prompt Caching
GPT 的缓存是自动的——不需要显式标记断点。
import OpenAI from "openai";
const client = new OpenAI();
// GPT 自动缓存 Prompt 的最长前缀
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: longSystemPrompt }, // 自动缓存
...conversationHistory,
{ role: "user", content: userQuestion },
],
});
// 检查缓存命中
console.log(response.usage);
// {
// prompt_tokens: 5550,
// completion_tokens: 200,
// prompt_tokens_details: {
// cached_tokens: 3500, // 命中缓存的 token 数
// }
// }
GPT vs Claude 缓存对比
| 维度 | Claude | GPT-4o |
|---|---|---|
| 控制方式 | 显式标记 cache_control | 自动前缀匹配 |
| 最小前缀 | 1024 tokens | 128 tokens |
| 缓存价格 | 正常价 × 0.1 | 正常价 × 0.5 |
| 写入价格 | 正常价 × 1.25 | 无额外费用 |
| TTL | ~5 分钟 | ~5-10 分钟 |
| 透明度 | 高(精确控制) | 中(自动优化) |
Claude 的折扣更大但首次有 25% 溢价;GPT 无溢价但折扣只有 50%。
对于高频场景(命中率 >80%),Claude 的总成本更低。
6 个高 ROI 场景
场景 1: RAG 问答(最常见)
// 知识库内容作为缓存前缀
const system = [
{
type: "text",
text: `你是客服助手。以下是产品文档:\n\n${productDocs}`,
cache_control: { type: "ephemeral" },
},
];
// 每次用户提问,只有 userQuestion 部分是新的
const response = await client.messages.create({
model: "claude-sonnet-4-6",
system,
messages: [{ role: "user", content: userQuestion }],
});
预期节省:70-90%(知识库内容通常不变)
场景 2: 多轮对话
const system = [
{
type: "text",
text: systemPrompt,
cache_control: { type: "ephemeral" },
},
];
// 对话历史逐轮增长,但前面的轮次不变
const messages = [
{ role: "user", content: "你好" },
{ role: "assistant", content: "你好!有什么可以帮你的?" },
{ role: "user", content: "帮我写一个排序函数" },
{ role: "assistant", content: "好的,你需要什么语言的?" },
// cache_control 可以放在最后一个 assistant 消息上
{
role: "user",
content: [
{ type: "text", text: "TypeScript,要支持泛型" },
],
},
];
预期节省:50-70%(前面的对话轮次被缓存)
场景 3: 批量数据处理
// 所有数据项共享同一个处理指令
const system = [
{
type: "text",
text: `你是一个数据提取专家。按以下 JSON Schema 提取信息:
${JSON.stringify(schema, null, 2)}
规则:
1. 金额字段必须是数字类型
2. 日期必须是 ISO 8601 格式
3. 缺失字段用 null 填充`,
cache_control: { type: "ephemeral" },
},
];
// 批量处理 1000 条数据,每条只改变用户消息
for (const item of dataItems) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
system,
messages: [{ role: "user", content: item.text }],
});
}
预期节省:80-95%(System Prompt 完全一致,首条之后全部命中)
场景 4: 代码生成(带大量上下文)
const system = [
{
type: "text",
text: `你是 TypeScript 开发助手。以下是项目的核心代码:
// === types.ts ===
${typesFile}
// === utils.ts ===
${utilsFile}
// === config.ts ===
${configFile}
请基于以上代码上下文回答问题。`,
cache_control: { type: "ephemeral" },
},
];
预期节省:60-80%(代码上下文在开发期间相对稳定)
场景 5: Few-shot 模板
const system = [
{
type: "text",
text: `你是一个情感分析器。以下是标注示例:
输入: "这个产品真的太好用了!" → 输出: {"sentiment": "positive", "score": 0.95}
输入: "售后太差了,等了三天没人回复" → 输出: {"sentiment": "negative", "score": 0.88}
输入: "还行吧,一般般" → 输出: {"sentiment": "neutral", "score": 0.52}
输入: "价格偏高但质量确实好" → 输出: {"sentiment": "mixed", "score": 0.60}
按以上格式分析用户输入的情感。`,
cache_control: { type: "ephemeral" },
},
];
预期节省:90%+(Few-shot 模板完全固定)
场景 6: Agent 工具描述
const tools = [
{ name: "search", description: "...", input_schema: {...} },
{ name: "database_query", description: "...", input_schema: {...} },
{ name: "send_email", description: "...", input_schema: {...} },
// ... 10+ 工具定义,通常占 2000-5000 tokens
];
// 工具定义会被自动包含在 Prompt 前缀中
// Claude 会自动缓存 tools 部分
const response = await client.messages.create({
model: "claude-sonnet-4-6",
tools,
messages: [{ role: "user", content: taskDescription }],
});
预期节省:70-85%(工具定义在 Agent 生命周期内不变)
Prompt 结构设计最佳实践
黄金法则:不变在前,变化在后
┌──────────────────────────────┐
│ System Prompt (不变) │ ← 缓存层 1
│ Few-shot 示例 (不变) │
├──────────────────────────────┤
│ 知识库上下文 (偶尔变化) │ ← 缓存层 2
├──────────────────────────────┤
│ 对话历史 (每轮增长) │ ← 部分缓存
├──────────────────────────────┤
│ 用户最新消息 (每次变化) │ ← 不缓存
└──────────────────────────────┘
避免缓存失效的陷阱
// ❌ 错误:把时间戳放在 System Prompt 中
const system = `当前时间是 ${new Date().toISOString()}。你是客服助手...`;
// 每秒都不同 → 缓存永远不命中
// ✅ 正确:时间戳放在用户消息中
const system = `你是客服助手。...`;
const userMsg = `[${new Date().toISOString()}] 用户问:${question}`;
// ❌ 错误:知识库检索结果排序不稳定
const context = retrievedDocs.map(d => d.content).join("\n");
// 相同查询可能返回不同排序 → 缓存失效
// ✅ 正确:按 ID 稳定排序
const context = retrievedDocs
.sort((a, b) => a.id.localeCompare(b.id))
.map(d => d.content)
.join("\n");
成本计算公式
月成本 = 请求数 × (
首次请求占比 × (缓存写入 tokens × 写入价格 + 非缓存 tokens × 正常价格)
+ 命中请求占比 × (缓存读取 tokens × 缓存价格 + 非缓存 tokens × 正常价格)
+ 输出 tokens × 输出价格
)
快速估算表(Claude Sonnet,1M 请求/月)
| 缓存命中率 | 缓存部分 (3000 tok) | 非缓存部分 (2000 tok) | 月输入成本 |
|---|---|---|---|
| 0%(无缓存) | $3.00/M × 5000 tok | - | $15,000 |
| 50% | 混合计算 | - | $9,750 |
| 80% | 混合计算 | - | $5,700 |
| 95% | 混合计算 | - | $3,450 |
95% 命中率相比无缓存节省 77%。
监控和调优
// 封装一个带缓存监控的 API 客户端
class CachedClaudeClient {
private stats = { hits: 0, misses: 0, totalSaved: 0 };
async chat(system: string, messages: any[]) {
const response = await this.client.messages.create({
model: "claude-sonnet-4-6",
system: [{
type: "text",
text: system,
cache_control: { type: "ephemeral" },
}],
messages,
});
const cached = response.usage.cache_read_input_tokens ?? 0;
const created = response.usage.cache_creation_input_tokens ?? 0;
if (cached > 0) {
this.stats.hits++;
this.stats.totalSaved += cached * (3.00 - 0.30) / 1_000_000;
} else {
this.stats.misses++;
}
return response;
}
getStats() {
const total = this.stats.hits + this.stats.misses;
return {
hitRate: total > 0 ? (this.stats.hits / total * 100).toFixed(1) + "%" : "N/A",
totalSaved: `$${this.stats.totalSaved.toFixed(2)}`,
};
}
}
总结
Prompt Caching 是 2026 年 LLM 成本优化中投入产出比最高的一项——不需要改模型、不需要改架构,只需要调整 Prompt 的结构顺序。
核心要点:
- 不变的内容放前面,变化的内容放后面
- Claude 用
cache_control显式标记,GPT 自动缓存 - 避免在缓存前缀中放入时间戳、随机值等动态内容
- 监控
cache_read_input_tokens确认命中率 - 缓存命中率 >80% 时,成本节省可达 70-90%