Workshop

Prompt Caching 实战:一行配置让 Claude/GPT API 成本降低 90%

3 min read ·

为什么 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 + 知识库部分命中):

月成本降至约 $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 },
  ],
});

缓存命中的条件

  1. 前缀必须逐 token 完全一致——哪怕多一个空格都会导致缓存失效
  2. 最小缓存长度 1024 tokens——太短的 Prompt 不会被缓存
  3. TTL 约 5 分钟——5 分钟内没有新的命中请求,缓存过期
  4. 同一 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 缓存对比

维度ClaudeGPT-4o
控制方式显式标记 cache_control自动前缀匹配
最小前缀1024 tokens128 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 的结构顺序。

核心要点:

  1. 不变的内容放前面,变化的内容放后面
  2. Claude 用 cache_control 显式标记,GPT 自动缓存
  3. 避免在缓存前缀中放入时间戳、随机值等动态内容
  4. 监控 cache_read_input_tokens 确认命中率
  5. 缓存命中率 >80% 时,成本节省可达 70-90%

Frequently asked questions

Prompt Caching 的基本原理是什么?
LLM 推理时需要对输入 Prompt 的每个 token 计算 KV Cache(Key-Value 缓存)。Prompt Caching 将这些计算结果在服务端缓存一段时间(Claude 5 分钟,GPT 约 5-10 分钟)。当下一个请求的 Prompt 前缀与缓存一致时,直接复用已有的 KV Cache,跳过重复计算,因此计费更低、延迟更小。
Claude 和 GPT 的 Prompt Caching 有什么区别?
三个关键区别:1) 定价——Claude 缓存读取价格是正常输入的 10%($0.30/M→$0.03/M),GPT 是 50%($2.50/M→$1.25/M);2) 控制方式——Claude 需要显式标记 cache_control 断点,GPT 自动缓存前缀;3) 最小前缀——Claude 要求至少 1024 tokens,GPT 要求至少 128 tokens。Claude 的折扣力度更大但需要手动控制,GPT 更透明但折扣较小。
怎样设计 Prompt 结构才能最大化缓存命中率?
核心原则:把不变的内容放前面,变化的内容放后面。推荐结构:System Prompt(不变)→ 知识库上下文(较少变化)→ 对话历史(逐渐增长)→ 用户最新消息(每次变化)。System Prompt 和知识库部分会被缓存,后续请求只需重新计算用户消息部分。如果知识库上下文也不变,缓存命中率可以接近 100%。
Prompt Caching 的 TTL 有多长?
Claude 的缓存 TTL 约 5 分钟,每次命中会刷新计时器。这意味着只要你在 5 分钟内持续发送请求(如对话场景),缓存可以一直保持。GPT 的 TTL 不公开但估计约 5-10 分钟,且会根据系统负载动态调整。离线批量任务中缓存可能提前失效,需要通过 Batch API 等方式优化。
Prompt Caching 适用于所有场景吗?
不适用于以下场景:1) 每次请求的 Prompt 都完全不同(无共同前缀);2) 请求间隔超过 TTL(>5 分钟);3) Prompt 总长度不到 1024 tokens(达不到最低缓存要求)。最适合的场景是:多轮对话(System Prompt 不变)、RAG 问答(知识库上下文重复)、批量处理同类任务(任务描述相同)。