提示缓存
重复的提示词前缀只需支付输入价格的一小部分。使用各厂商自己的缓存机制,并由网关让每个对话始终留在持有其缓存的渠道上。
提示缓存让模型可以复用它最近处理过的提示词前缀。缓存输入的价格只有普通输入价格的一小部分:在 claude-sonnet-5.5 上,缓存读取每百万 token $0.10,而未缓存输入为 $2.00。对于智能体和长对话,每一轮都会重新发送全部历史,因此大部分输入 token 都可以从缓存中读取。
通过 Lovely Router 使用提示缓存,依靠两点:
- 厂商缓存。 你完全按照各厂商的文档使用其缓存机制。
cache_control、prompt_cache_key等请求字段原样透传。 - 对话亲和。 缓存存在于写入它的渠道上。网关会识别同一对话的各轮请求,并把它们送回同一个渠道,这样下一轮到来时缓存仍然在那里。
Anthropic#
Claude 只缓存你标记的内容。把 cache_control 放在稳定前缀(工具、系统提示词、之前的轮次)的最后一个块上。你最多可以设置四个断点,或者在请求的顶层放一个 cache_control,缓存截至最后一个块的所有内容。
response = client.messages.create(
model="claude-sonnet-5.5",
max_tokens=1024,
system=[{
"type": "text",
"text": SYSTEM_PROMPT_AND_REFERENCE_DOCS,
"cache_control": {"type": "ephemeral"},
}],
messages=history,
)
print(response.usage)响应会报告 cache_creation_input_tokens(写入,按缓存写入价格计费)和 cache_read_input_tokens(读取,按缓存读取价格计费)。短于模型最低长度的前缀不会被缓存。断点的放置位置以及哪些情况会使缓存失效,请参阅 Anthropic 的缓存指南。
OpenAI#
GPT 模型会自动缓存较长的提示词前缀,无需任何标记。把提示词中稳定的部分放在前面,变化的部分放在最后。为每个对话或每个用户把 prompt_cache_key 设为一个固定值可以提高命中率,网关也会用它把该对话保持在同一个渠道上。
response = client.chat.completions.create(
model="gpt-6.1-sol",
messages=history,
prompt_cache_key="conv_8f2a91",
)
print(response.usage.prompt_tokens_details.cached_tokens)usage.prompt_tokens_details.cached_tokens 按缓存读取价格计费,prompt_tokens 的其余部分按输入价格计费。GPT 模型没有单独的缓存写入费用。请参阅 OpenAI 的缓存指南。
Gemini#
Gemini 模型会自行缓存重复的前缀(隐式缓存)。缓存的 token 显示在 usageMetadata.cachedContentTokenCount 中,按缓存读取价格计费,没有缓存写入费用。不支持通过 cachedContents API 进行显式上下文缓存。
如何识别对话#
网关会为每个对话生成一个指纹,取以下列表中的第一个匹配项:
| 来源 | API 格式 |
|---|---|
x-session-id 请求头 | 全部 |
x-lovely-session 请求头 | 全部 |
x-claude-code-session-id 请求头(由 Claude Code 发送) | 全部 |
请求体中的 metadata.user_id | Anthropic |
请求体中的 prompt_cache_key | OpenAI |
请求体中的 user | OpenAI |
| 系统提示词加上第一条用户消息 | 全部 |
最后一条规则不需要你做任何事。对话的后续轮次只会追加消息,系统提示词和第一条用户消息保持不变,因此每一轮都会得到相同的指纹。两者都只比较前 4,000 个字符。
指纹的作用范围限定在你的 API 密钥、模型和渠道分组之内。同一个对话如果用不同的密钥发送,或发往不同的模型,会开始一次新的锁定。
对话会锁定多久#
对话的第一次成功调用会把它锁定到服务该调用的渠道上。之后的轮次会优先发往该渠道。
- 时长。 锁定最长持续一小时,对话活跃期间会持续续期。30 到 60 分钟内没有请求时,锁定过期,下一轮会像新对话一样重新分配渠道。
- 缓存已过期。 使用 Anthropic Messages 格式时,如果对话空闲的时间超过了提示词缓存的有效期(5 分钟;
cache_control断点设置ttl: "1h"时为 1 小时),锁定也会结束。此时它保护的缓存已经失效,下一轮会像新对话一样分配渠道,而不是继续使用上次服务的渠道。 - 渠道繁忙。 如果锁定的渠道容量已满,这一轮会短暂等待它(短提示词约一秒,长提示词最多十秒),而不是换到别的渠道并为新的缓存写入付费。如果等待后仍然已满,这一轮会转到另一个渠道。
- 渠道故障。 如果锁定的渠道正在出错,这一轮会转到一个健康的渠道,锁定也随之转移。这一轮会写入新的缓存;之后的轮次会再次从中读取。
发送显式会话 ID#
当第一条用户消息在不同轮次之间发生变化时,自动指纹就会失效,例如你的应用会改写或摘要旧的历史记录。这种情况下,请为每个对话发送一个固定的 ID。请求头在所有 API 格式中都可用:
curl https://lovelyrouter.com/v1/messages \
-H "x-api-key: $LOVELY_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "x-session-id: conv_8f2a91" \
-H "content-type: application/json" \
-d '{"model": "claude-sonnet-5.5", "max_tokens": 1024,
"messages": [{"role": "user", "content": "Continue the report."}]}'请求体字段同样可用:Anthropic 格式用 metadata.user_id,OpenAI 格式用 prompt_cache_key。每个对话使用一个 ID;如果在许多互不相关的对话中复用同一个 ID,它们会全部落到同一个渠道上。
在日志和账单中查看缓存使用情况#
日志为每次调用显示:
| 字段 | 含义 |
|---|---|
| 输入 | 未缓存输入加上缓存写入。 |
| 已缓存 | 从缓存读取的输入 token。 |
| 缓存写入(详情) | 写入缓存的输入 token。 |
| 缓存命中(详情) | 缓存 token 占全部输入 token 的比例。 |
| 会话亲和(详情) | explicit:你发送了会话 ID。sticky:对话已被锁定,并回到了它的渠道。new:对话的第一次调用,或者它的锁定已过期、渠道不可用。none:无法生成指纹。 |
控制台概览页显示你所有调用的缓存命中率。每类 token 按各自的价格计费;请参阅每次调用如何计费。
排查命中率偏低
如果一个对话在每一轮都显示 new,说明它的系统提示词或第一条用户消息在不同轮次之间发生了变化。最常见的原因是系统提示词中带有时间戳,这同样会破坏厂商的缓存:把时间戳移到最新的用户消息中,或发送显式会话 ID。如果各轮显示 sticky,但 已缓存 一直为空,请检查前缀是否达到模型的最低长度,以及(对 Claude 而言)是否带有 cache_control。