跳转到主要内容

Documentation Index

Fetch the complete documentation index at: https://docs.apiyi.com/llms.txt

Use this file to discover all available pages before exploring further.

如果你用 Claude Code、Cline、Cursor,或者自己写代码调 Claude API,Prompt Cache 是把账单打下来最直接的一件事——命中缓存的部分只按 0.1× 计费,相当于打 1 折。 本页基于 Anthropic 官方文档整理(docs.claude.com/en/docs/build-with-claude/prompt-caching),并按 API易 的接入方式给出可直接复制的示例。

一句话理解

把一段反复使用的长 prompt(系统说明 / 长文档 / few-shot 示例)打上 cache_control 标记,服务器会把它存起来。下次相同前缀的请求来,服务器跳过重复处理,便宜约 10 倍、也更快。一段时间内没人再用就会过期。

为什么要用 —— 看账单倍率

以模型原始输入 token 价为 计:
类型价格说明
普通输入没缓存的部分,原价
缓存写入(5 分钟 TTL)1.25×第一次写入贵 25%
缓存写入(1 小时 TTL)想存更久要付更多
缓存读取(命中)0.1×后续每次便宜 90%
回本点:
  • 5 分钟 TTL:只需 2 次复用同一前缀即可回本(1.25 + 0.1 = 1.35,比两次不缓存的 2.0 便宜)。
  • 1 小时 TTL:需要 3 次才回本(2 + 0.2 = 2.2,比 3.0 便宜)。
TTL 是滑动窗口:每次命中都会把过期时间重置,因此活跃的会话不会平白过期。只有真正闲置超过 TTL 才会失效。

适合场景

  • 同一份长系统提示词被多次调用(Agent、客服机器人)
  • 多轮对话(每加一轮,前面的历史都能复用)
  • 批量处理同一份文档(一份合同问 50 个问题)
  • RAG 把检索到的稳定文档块作为前缀

不适合场景

  • 每次 prompt 从第一个字开始都不一样
  • 整体很短,根本到不了最小 token 阈值(见下)

触发缓存的三个硬条件

缺一不可。

1. 必须显式打标记 cache_control

content 不能是纯字符串,必须是 content block 数组,在要缓存的那一块上加 cache_control
# ❌ 错:纯字符串永不缓存
"content": "一大段长文..."

# ✅ 对:content block + cache_control
"content": [
    {
        "type": "text",
        "text": "一大段长文...",
        "cache_control": {"type": "ephemeral"},
    },
    {"type": "text", "text": "问题"},
]

2. 长度必须达到最小阈值

短于阈值的内容,就算打了标记也不会缓存(不报错,静默忽略)。按模型不同:
模型最小 tokens
Sonnet 4.5 / 4 / 3.71024
Sonnet 4.6 / 早期 Haiku2048
Opus 4.5 / 4.6 / 4.7、Haiku 4.54096
中文 1 个字大约 0.5–1 token,所以 Sonnet 4.6 至少要 2000 字以上的稳定内容才有意义。阈值数字可能随官方版本变化,以 Anthropic 官方文档为准

3. 前缀必须逐字节相同

缓存按前缀匹配:从请求开头一直到 cache_control 标记位置,这段字节流必须和上一次完全一样。改任何一个字符——哪怕是空格、JSON 字段顺序、时间戳——都算”新前缀”,会重新写入而不是命中。 实践含义:稳定的东西放前面,易变的东西放后面。
# ❌ 错:每换一个问题前缀就变了,永远命中不了
content = [
    {"type": "text", "text": "请回答下面问题: " + 问题},  # 这块在变
    {"type": "text", "text": 长文, "cache_control": {"type": "ephemeral"}},
]

# ✅ 对:长文在前打标记,问题在后不打标记
content = [
    {"type": "text", "text": 长文, "cache_control": {"type": "ephemeral"}},  # 稳定
    {"type": "text", "text": 问题},                                            # 随便变
]

最小可运行示例

跑两次同一段长文 + 不同问题,第一次写入缓存,第二次命中:
import json, os, requests

URL = "https://api.apiyi.com/v1/messages"
KEY = os.environ["APIYI_API_KEY"]
HEADERS = {
    "content-type": "application/json",
    "x-api-key": KEY,
    "anthropic-version": "2023-06-01",
}

# 必须足够长。Sonnet 4.6 至少 2048 tokens,大约 2000+ 中文字。
LONG_TEXT = open("long_document_zh.txt").read()


def ask(question: str, label: str):
    payload = {
        "model": "claude-sonnet-4-6",
        "max_tokens": 256,
        "messages": [{
            "role": "user",
            "content": [
                {"type": "text", "text": LONG_TEXT, "cache_control": {"type": "ephemeral"}},
                {"type": "text", "text": question},
            ],
        }],
    }
    r = requests.post(URL, headers=HEADERS, data=json.dumps(payload), timeout=120)
    u = r.json().get("usage", {})
    print(f"[{label}] input={u.get('input_tokens')} "
          f"write={u.get('cache_creation_input_tokens')} "
          f"read={u.get('cache_read_input_tokens')}")


ask("请概括主旨", "第1次")    # 期望 write>0, read=0
ask("请给3个关键词", "第2次")  # 期望 write=0, read>0
期望看到的输出:
[第1次] input=35 write=6512 read=0
[第2次] input=22 write=0    read=6512
第 2 次的 read ≈ 第 1 次的 write,说明同一段前缀被命中复用了。

怎么判断命中没命中 —— 看三个字段

每次响应的 usage 里:
字段含义计费倍率
input_tokens没被缓存的剩余输入 token
cache_creation_input_tokens这次写入缓存的 token 数1.25× 或 2×
cache_read_input_tokens这次从缓存读取的 token 数0.1×
输入总量 = 三者之和。 只要 cache_read_input_tokens > 0,你就在省钱。

最常见的踩坑

现象原因
write 永远是 0 或字段不存在没打 cache_control 标记 / 长度没到阈值 / 用了 OpenAI 兼容格式
第 2 次 write 又 > 0,read 还是 0前缀变了。常见:prompt 里有 datetime.now()、UUID、变化的用户 ID;JSON 序列化顺序不稳定;带时间戳的 system 提示
隔了一会儿再调又变成写入闲置超过 TTL 过期。要常驻可加 {"type": "ephemeral", "ttl": "1h"}
同一份 prompt 切换模型后没命中缓存按模型隔离,换模型相当于换 key
长对话最近几轮不命中单次请求最多 4 个 cache_control 断点;且每个断点的前缀查找窗口为 20 个 block,更久远的内容不会被纳入命中检查
Prompt Cache 只在 Anthropic 原生格式(/v1/messages)下生效。 用 OpenAI 兼容格式(/v1/chat/completions)调 Claude 时,无论你怎么传,都拿不到缓存计费。Claude Code、Cline、Cursor 等深度场景请务必走原生格式。

进阶:多轮对话怎么打

cache_control 打在最近一条 user 消息的最后一个 content block 上。每加一轮,缓存读取范围会自动延伸到上一轮结束的位置:
# 每一轮请求构造时
messages[-1]["content"][-1]["cache_control"] = {"type": "ephemeral"}
两个硬限制要注意:
  • 单次请求最多 4 个 cache_control 断点。
  • 每个断点的前缀查找只回溯最近 20 个 content block——超出 20 个 block 的更久远内容不会再被检索去拼命中。换言之:很长的多轮对话靠”最末一次打标记”是兜不住前面所有历史的。
实践建议:在工具定义/系统提示/长文档/最近一轮对话各打一个断点,正好用满 4 个槽位,让不同变化频率的内容互不影响彼此的命中。

API易 关于缓存的说明

API易完整透传缓存字段。 你在请求里写的 cache_control 会原样转发给上游 Claude(AWS Claude 或 Claude Official),响应里的 cache_creation_input_tokens / cache_read_input_tokens 也会原样回吐给你——所以你的代码无需为中转层做任何额外适配。
如何自检:
  1. 第一次发送时观察响应 usage.cache_creation_input_tokens > 0(写入成功)。
  2. 几秒后用相同前缀再发一次,应看到 usage.cache_read_input_tokens > 0(命中)。
  3. 后台账单里会单独显示缓存写入 / 缓存读取两类计费项,倍率与官方一致(1.25× / 2× / 0.1×)。

要点回顾

1. 打标记

cache_control: {"type": "ephemeral"} 加在 content block 上,纯字符串 content 永不缓存

2. 够长度

Sonnet 4.6 ≥ 2048 tokens,Opus 4.7 / Haiku 4.5 ≥ 4096 tokens,否则静默忽略。

3. 稳前缀

稳定内容在前、易变内容在后;任何一个字符变化都会让缓存失效。

4. 看 usage

cache_read_input_tokens > 0 才说明真的省钱了。

相关链接

  • 父页面:Claude API 调用基础说明
  • 获取 / 管理令牌:https://api.apiyi.com/token
  • Anthropic 官方文档:docs.claude.com/en/docs/build-with-claude/prompt-caching