文档

文档

提示缓存

使用 cache_control 标记一次稳定的提示前缀;或者通过 OpenAI 格式调用 Claude 模型(该格式没有此字段),Kunavo 会为你设置缓存断点。后续每次复用此前缀的调用,都会按输入价格的一小部分对该前缀计费 — 大多数聊天模型按输入价格的 10% 计费,最低可至 2.5%。Kunavo 的路由层会将对话固定到同一个上游密钥,以保持缓存处于热状态。

使用 Claude Sonnet 5 处理 30K token 的系统提示词时,缓存读取费用为 $0.14 / 1M,而不是 $1.4 / 1M。 对于每个会话中会重复发送同一上下文 50 次的智能体,缓存可节省约 90% 的输入费用。

如何启用

在任意系统块、消息或工具定义上添加一个 cache_control: { type: "ephemeral" } 断点。模型会在首次调用时缓存该断点之前的内容;后续调用只要共享相同的前缀字节,就会命中缓存。

cache_example.py
from openai import OpenAI

client = OpenAI(
    api_key="sk-kn-...",
    base_url="https://api.kunavo.com/v1",
)

# Mark the large, stable prefix once with cache_control.
# Every subsequent call that sends the same prefix reads it back
# from cache at 0.10× the input rate on Claude Sonnet 5.
# Per-model rates are in the pricing table below.
SYSTEM = [
    {
        "type": "text",
        "text": open("long_system_prompt.txt").read(),
        "cache_control": {"type": "ephemeral"},
    },
]

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[
        {"role": "system", "content": SYSTEM},
        {"role": "user", "content": "Summarize the doc above in 3 bullets."},
    ],
)

u = resp.usage
# OpenAI-compat fields populated by Kunavo:
# - prompt_tokens_details.cached_tokens — read from cache
# - cache_creation_input_tokens          — written to cache this call
print(u.prompt_tokens_details.cached_tokens,
      getattr(u, "cache_creation_input_tokens", 0))

在原生 Messages API 上使用完全相同的线路格式 — cache_control 会原样传递给 Claude,不做翻译:

messages_api.sh
# Native Anthropic Messages API — cache_control passes through untranslated.
curl https://api.kunavo.com/v1/messages \
  -H "Authorization: Bearer $KUNAVO_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "system": [
      {
        "type": "text",
        "text": "<your large stable system prompt>",
        "cache_control": { "type": "ephemeral" }
      }
    ],
    "messages": [
      { "role": "user", "content": "Summarize the doc above in 3 bullets." }
    ],
    "max_tokens": 1024
  }'
字节必须保持稳定。 缓存键由内容决定。前缀中的任何位置只要有一个字符不同,该片段的缓存就会失效。将动态内容(时间戳、用户名、每次调用的 ID)放在缓存块之后,不要放在其中。

在 OpenAI 格式端点上自动插入断点

OpenAI Chat Completions 和 Responses 格式都没有 cache_control 字段 — OpenAI 会在自己的服务器上隐式缓存。Claude 则不会:它只缓存由断点标记的内容。因此,使用 OpenAI 协议的客户端无法请求缓存;如果只是直接翻译请求,发给 Claude 的内容就不会带有任何断点 — 长对话的每一轮都会按完整输入费率对全部历史记录重新计费。

因此,当您通过 /v1/chat/completions 或 /v1/responses 调用 Claude 模型时,Kunavo 会设置协议无法传递的断点。您无需开启任何设置。此功能仅适用于 Claude 模型 — 其他所有模型系列均通过其供应商会隐式缓存的协议提供服务,无需设置断点。

no_cache_control.py
# Plain OpenAI Chat Completions — no cache_control anywhere in the body.
# The format has no field for one; Kunavo inserts the breakpoints instead.
resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[
        {"role": "system", "content": LONG_SYSTEM_PROMPT},
        {"role": "user", "content": "first question"},
        {"role": "assistant", "content": "...previous answer..."},
        {"role": "user", "content": "follow-up"},
    ],
)

u = resp.usage
# Turn that writes the cache:
#   u.cache_creation_input_tokens            -> 18,000
#   u.prompt_tokens_details.cached_tokens    ->      0
# Next turn, same prefix, inside the 5-minute window:
#   u.cache_creation_input_tokens            ->    240   (only the new turn)
#   u.prompt_tokens_details.cached_tokens    -> 18,000
#
# Both stuck at 0 on a conversation well over the threshold? The prefix is
# changing between calls — a timestamp or request id inside the cached span.

断点的位置及预算的使用顺序如下:

  • 在最后一条消息上设置滚动断点 — 放在最后一条消息的最后一个内容块上,以便缓存之前的所有内容;如果请求以一条留在对话中的 system 消息结尾(见下文),则改放在该消息前面紧挨着的那个内容块上。只有在 messages 已有一轮 assistant 对话后才会添加:写入缓存会按 1.25× 输入计费,因此标记一个不会被再次发送的提示会多花四分之一的费用,却没有任何收益。之前已有 assistant 对话,表明这个完全相同的前缀即将再次出现。
  • 在 system 和 tools 上各设置一个断点 — 用于标记提示中稳定不变的开头。即使是单次调用,也值得标记,因为调用方只要发送一次系统提示,几乎总会在下一次调用中发送相同的提示。
  • 在留在对话中的 system 消息前面 — 滚动断点和 system、tools 上的断点用剩的预算,会放在最后一条后面还有内容的此类消息前面紧挨着的内容块上,然后是再前面的一条。它排在最后填充,因为只有在不同轮次之间移动此类消息的客户端才需要它;没有此类消息的对话(几乎所有对话都是这样)就没有这个位置。

该功能受两项限制。提示内容(包括消息、system 和工具架构)少于 8,192 个字符时,不会插入任何断点:Anthropic 会静默忽略其可缓存长度下限以下的前缀上的断点(1,024 个令牌,Haiku 为 2,048 个),也不会收取缓存写入或读取费用,因此在此处添加断点没有任何收益。此外,Anthropic 每个请求最多接受 4 个断点 — 第 5 个断点会导致硬性 400 错误,而非静默忽略。

模型首次回答之后发送的 system 或 developer 消息会留在对话中、留在它被发送时所在的那一轮,而不会被提升到 system 中:那样做会在加入它的那一轮改变提示的开头,并把整段历史重新写入缓存。在上游接受的情况下,Claude 会以 system 消息的形式收到它;否则,它会作为该用户轮次中的文本,放在 <system-reminder> 标签之间。在模型首次回答之前发送的 system 消息,或其自身断点要求一小时缓存的消息,仍会放入 system。

您自己的断点绝不会被移动或删除。 您设置的断点也计入相同的 4 个断点上限,Kunavo 只会填补您留空的位置。如果您设置了 4 个断点,系统就不会添加任何断点;如果您设置了 1 个,系统最多可在其他位置再添加 3 个。预算紧张时会优先填补最靠后的空位 — 提示中较靠后的断点会缓存其之前的全部内容,因此滚动断点最有价值。在原生 /v1/messages 端点上不会插入任何内容:您的请求体会按原样传递,其中的每个断点都由您设置。
设置断点不代表命中缓存。 断点只是将前缀标记为可缓存;要命中缓存,还需要在 Anthropic 的临时缓存窗口内(5 分钟,每次读取都会刷新)再次发送字节完全相同的前缀,并且请求必须到达同一个上游密钥 — 下文的亲和性路由正是为此设计的。新前缀的首次调用始终是写入,而非读取;如果提示每轮都会变化,无论断点放在哪里,都不会命中缓存。

报告方式不会改变:无论断点是由您还是网关添加,响应字段部分所述的同一组 cached_tokens 和 cache_creation_input_tokens 字段都会得到填充,因此您可以通过 usage 对象确认插入的断点是否确实带来了缓存命中。

定价

缓存费率根据每个模型的输入价格,按照供应商规定的比例计算 — 以下表格为准,表中每项费率均根据该模型的输入价格计算得出,而非手动填写。在 Anthropic 和 OpenAI 上,缓存读取均按输入价格的 0.10× 计费,有2项例外:0.025× on Claude Fable 5.1, 0.05× on Claude Opus 5.5。每个 Claude 模型的缓存写入均按输入价格的 1.25× 计费 ,GPT-6 Astra、GPT-6 Sol、GPT-6 Luna、GPT-5.6 Sol和GPT-5.6 Terra 也适用同一费率。Kunavo 对 Claude 的 1 小时 TTL 写入也按相同的 1.25× 计费,低于 Anthropic 的 2×。其他所有模型均没有单独的写入价格:其写入按普通输入费率计费。无论哪种情况,前缀只要复用一次,缓存后的费用就会低于重新发送。每种缓存类型都采用模型自身的折扣,因此缓存价格相对于供应商标价的折扣幅度,与其输入和输出价格的折扣幅度相同。

Anthropic

ModelInputCache readCache write
Claude Fable 5.1$7$0.175 0.025×$8.75 1.25×
Claude Fable 5$7$0.7 0.10×$8.75 1.25×
Claude Opus 5.5$2.8$0.14 0.050×$3.5 1.25×
Claude Opus 5$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.8$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.7$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.6$3.5$0.35 0.10×$4.375 1.25×
Claude Sonnet 5$1.4$0.14 0.10×$1.75 1.25×
Claude Sonnet 4.6$2.1$0.21 0.10×$2.625 1.25×
Claude Haiku 4.5$0.7$0.07 0.10×$0.875 1.25×

OpenAI

ModelInputCache readCache write
GPT-6 Astra$4$0.4 0.10×$5 1.25×
GPT-6 Sol$0.8$0.08 0.10×$1 1.25×
GPT-6 Luna$0.04$0.004 0.10×$0.05 1.25×
GPT-5.6 Sol$2$0.2 0.10×$2.5 1.25×
GPT-5.6 Terra$0.7$0.07 0.10×$0.875 1.25×
GPT-5.5$2$0.2 0.10×$2 1×
长提示按供应商的长上下文档位计费。 超过阈值后,整个请求都会按模型输入和输出价格的倍数计费,包括缓存读取和写入:超过 272K 个输入 token 时,GPT-6 Astra、GPT-6 Sol、GPT-6 Luna、GPT-5.6 Sol、GPT-5.6 Terra和GPT-5.5 的费率为(输入 2×,输出 1.5×)。 Claude 没有长上下文附加费:其 1M 上下文按标准费率计费。
目录最低价已考虑缓存费用。 Kunavo 按每次调用 max(catalog_cost, upstream × markup) 计费 — 该最低价根据缓存输入费率而非未缓存输入费率计算,因此节省的费用会由您享有,而不会被抵消。
以上费率为目录价格。 每次调用的实际账单根据模型实际消耗的上游额度(加上加价)计算,然后以目录价格作为最低价。缓存命中时,上游收取的额度更少 — 因此上游费用加价后的金额通常会低于目录价格,此时按目录价格计费。简而言之:目录价格是您的最高价格;缓存调用的价格可能低于该价格,但绝不会高于它。

亲和性路由让缓存保持热状态

上游提示缓存按 API 密钥隔离。如果每个请求都随机分配到不同的上游密钥,缓存命中率约为 1/N。Kunavo 会根据您的系统提示和第一条用户消息生成稳定哈希,并将具有相同前缀的所有调用路由到同一个上游密钥 — 使用加权 rendezvous 哈希,因此池发生变化时,只有约 1/N 的密钥会被重新映射。

您无需进行任何配置。亲和性路由会自动应用于所有聊天调用。(图像 / 视频 / TTS 使用随机路由 — 这些模态没有按密钥隔离的缓存。)

仪表盘指标

开始命中缓存后,/app/usage 会针对所选时间范围显示两项汇总指标:

  • 命中率 — 缓存输入令牌数 ÷ 输入令牌总数。根据每次调用的令牌数精确计算。
  • 节省金额 — 此时间范围内节省的美元金额,按模型使用其实际缓存读取费率计算(不采用简单平均值近似)。此金额按目录费率估算 — 实际账单以目录价格为最低价,而缓存调用的费用可能低于该最低价,因此此数值是实际节省金额的严格上限。对于典型的聊天工作负载,两者相差不到几个百分点。

每次调用的详情页面(/app/usage/<id>)会显示非零的缓存及缓存写入令牌数,以及实际计费金额。

响应字段

Kunavo 会在两种响应格式中显示缓存令牌。在 OpenAI 兼容的 /v1/chat/completions 端点上:

字段含义
usage.prompt_tokens输入令牌总数(包括缓存令牌和写入缓存的令牌)。
usage.prompt_tokens_details.cached_tokens输入令牌中由缓存提供的部分。
usage.cache_creation_input_tokens此次调用写入缓存的令牌(按上表所列模型的缓存写入费率计费)。

在 /v1/messages 的原生 Messages API 上,会原样传递 Anthropic 的原始字段:

字段含义
input_tokens新输入(未缓存)。
cache_read_input_tokens从缓存提供,按 0.10× 输入价格计费(在Claude Fable 5.1上的比例为0.025×,在Claude Opus 5.5上的比例为0.05×)。
cache_creation_input_tokens此次调用写入缓存的内容(按输入价格的 1.25 倍计费 — 1 小时 TTL 写入也是如此,低于 Anthropic 的 2 倍)。

接下来

  • Messages API 参考 — 原生 Anthropic 格式,包含缓存示例。
  • Claude 提示缓存 — 如何根据 usage 对象中的数值诊断缓存,以及通过网关路由导致命中率下降的三种方式。
  • 计费与账本 — 缓存感知的 max() 最低价如何与上游成本交互。
  • 完整定价表 — 所有聊天模型的未缓存输入 / 输出价格,底部附有缓存子表。