halfoff.dev文档
获取密钥
模型 API

提示缓存

在五折的基础上进一步降低输入成本:使用各厂商自带的提示缓存,Half Off Dev 会把每段对话送回持有其缓存的渠道。

五折只是起点。对于反复发送同一段前缀的场景,比如长对话、编程助手、基于固定文档集的检索问答,提示缓存还能把输入成本再压低一大截。模型最近处理过的前缀可以直接复用,复用的 token 按缓存读取单价计费。以 claude-sonnet-5.5 为例,缓存读取是每百万 token $0.05,未缓存输入则是 $1.00。

在 Half Off Dev 上用好缓存,靠的是两件事:

  1. 厂商自己的缓存机制。 完全按各厂商文档使用,cache_control、prompt_cache_key 等字段原样转发。
  2. 对话亲和性。 缓存只存在于写入它的那个渠道上。网关能识别同一段对话的后续轮次,并把它们送回该渠道,下一轮到来时缓存还在。

Claude:标记要缓存的部分#

Claude 只缓存你标记过的内容。在不变的前缀(工具定义、系统提示词、之前的轮次)的最后一个块上加 cache_control。最多可以设四个断点;也可以在请求顶层放一个 cache_control,缓存到最后一个块为止的全部内容。

response = client.messages.create(
    model="claude-sonnet-5.5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": PRODUCT_MANUAL,
        "cache_control": {"type": "ephemeral"},
    }],
    messages=chat_history,
)
print(response.usage)

usage 中的 cache_creation_input_tokens 是写入量(按缓存写入单价计费),cache_read_input_tokens 是读取量(按缓存读取单价计费)。短于模型最小长度的前缀不会被缓存。断点放在哪里、哪些改动会让缓存失效,见 Anthropic 缓存指南。

GPT:自动缓存,可选缓存键#

GPT 模型会自动缓存较长的前缀,不需要做任何标记。把不变的内容放前面、变化的内容放后面即可。为每段对话或每个用户设置一个固定的 prompt_cache_key 能提高命中率,网关也会用这个值把该对话固定在同一个渠道上。

response = client.chat.completions.create(
    model="gpt-6.1-sol",
    messages=chat_history,
    prompt_cache_key="thread_51c7d0",
)
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-halfoff-session 请求头全部
x-claude-code-session-id 请求头(Claude Code 会发送)全部
请求体中的 metadata.user_idAnthropic
请求体中的 prompt_cache_keyOpenAI
请求体中的 userOpenAI
系统提示词加第一条用户消息全部

最后一条无需你做任何事。对话是靠追加消息往下进行的,系统提示词和第一条用户消息不会变,所以每一轮得到的指纹都相同。比较时只取两者各自的前 4,000 个字符。

指纹的作用范围是同一个 API 密钥、同一个模型、同一个渠道分组。用另一个密钥或换一个模型发送同一段对话,会重新建立固定关系。

固定关系能维持多久#

对话的第一次成功调用会把它固定到服务该调用的渠道上,之后的轮次优先发往这个渠道。

  • 时长。 固定关系最长保持一小时,对话活跃期间会不断续期。闲置 30 到 60 分钟后失效,下一轮按新对话处理。
  • 缓存已过期。 在 Anthropic Messages 格式下,如果对话的闲置时间超过了缓存的存活时间(5 分钟;若 cache_control 断点设了 ttl: "1h" 则为 1 小时),固定关系也会结束。此时缓存已经没了,下一轮会重新分配渠道,而不是去等原来那个。
  • 渠道繁忙。 被固定的渠道满载时,这一轮会稍等片刻(短提示词约 1 秒,长提示词最多 10 秒),而不是立刻换渠道、重新付一次缓存写入的钱。等过之后仍然满载,才会换渠道。
  • 渠道故障。 被固定的渠道不健康时,这一轮会换到健康的渠道,固定关系随之转移。这一轮会写入新的缓存,之后的轮次再从中读取。

显式指定对话 ID#

如果第一条用户消息每轮都会变,比如你的应用会压缩或改写旧的历史记录,自动指纹就会失效。这时请为每段对话发送一个固定的 ID。请求头在所有格式下都有效:

curl https://halfoff.dev/v1/messages \
  -H "x-api-key: $HALFOFF_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "x-session-id: thread_51c7d0" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-5.5", "max_tokens": 1024,
       "messages": [{"role": "user", "content": "接着上次的内容继续。"}]}'

用请求体字段也可以:Anthropic 格式用 metadata.user_id,OpenAI 格式用 prompt_cache_key。每段对话用一个 ID;让许多无关的对话共用一个 ID,会把它们全挤到同一个渠道上。

在日志和账单中查看缓存#

日志中每次调用都会显示:

字段含义
输入未缓存输入加上缓存写入。
已缓存从缓存读取的输入 token。
缓存写入(详情)写入缓存的输入 token。
缓存命中(详情)缓存 token 占全部输入的比例。
会话亲和(详情)explicit:你提供了会话 ID。sticky:对话已固定,并回到了原渠道。new:首次调用,或固定关系已过期、原渠道不可用。none:无法生成指纹。

控制台概览页显示你所有调用的缓存命中率。每类 token 都有各自的五折单价,见计费公式。

命中率偏低?

如果一段对话每一轮都显示 new,说明它的系统提示词或第一条用户消息在轮次之间有变化。最常见的原因是系统提示词里带了时间戳,这同样会让厂商的缓存失效:把它挪到最新一条用户消息里,或者发送显式的会话 ID。如果显示 sticky 但 已缓存 一直为空,请确认前缀达到了模型的最小长度;对 Claude 还要确认带了 cache_control。