缓存与幂等

省钱的两层缓存怎么用,以及重试时怎样不被重复扣费。

SciRouter 上有两层缓存,省的东西不一样:

上游提示词缓存SciRouter 响应缓存
缓存在哪模型厂商那边我们的网关
命中条件请求开头一段和之前相同整个请求和之前完全相同
省什么命中部分按更低的「缓存命中价」计,仍然会推理不调用模型,不计费
要你做什么多数情况什么都不用做(单轮批量调用见下)对话请求带一个请求头

上游提示词缓存

同一段长前缀(系统提示、参考文档、工具定义)反复发送时,上游会把它缓存起来, 命中的部分按模型详情页里的缓存命中价计,通常远低于输入价。 有的上游第一次写入缓存时按缓存写入价计,略高于输入价,之后几轮命中就省回来了。

网关替你做了能做的:

  • 需要显式标记缓存位置的上游:输入够长(约 1000 token 起)且是多轮对话(system 之外至少两条消息)时, 网关自动在工具定义、系统提示、上一轮末尾和最后一块上打标记;
  • 支持缓存分组键的上游:网关替你算好分组键再发过去(默认按 API Key 隔离,你在请求里给了 prompt_cache_key 就按你的键,仍按账号隔离)。

用量里 usage.prompt_tokens_details.cached_tokens 是命中的数量。

单轮批量调用要自己标。 一批请求共用同一段很长的系统提示、但每条只有一个问题时,网关不会自动打标记 (它判断不出这段前缀还会不会再用)。在想缓存的那一段内容上加 cache_control,网关会原样转给上游:

json
{
  "model": "bio/protein-72b",
  "messages": [
    {
      "role": "system",
      "content": [
        { "type": "text", "text": "(很长的评测规则与参考资料……)", "cache_control": { "type": "ephemeral" } }
      ]
    },
    { "role": "user", "content": "第 17 题:……" }
  ]
}

同一个请求里最多保留 4 个标记(多了留最后 4 个);不需要标记的上游会忽略这个字段。

想让它多命中,你只需要做一件事:把不变的内容放在前面,变化的内容放在后面。 把当前时间、随机 id 之类塞进系统提示的开头,会让每次请求的前缀都不一样,一次也命中不了。

模型没有单独标缓存价时,这部分按输入价计——不会因为没标价就不收,也不会多收。

SciRouter 响应缓存

对话请求带上 X-SciRouter-Cache: enabled,完全相同的请求第二次起直接返回上一次的结果:

bash
curl https://api.scirouter.cn/v1/chat/completions \
  -H "Authorization: Bearer $SCIROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-SciRouter-Cache: enabled" \
  -d '{
    "model": "bio/protein-72b",
    "temperature": 0,
    "messages": [{"role": "user", "content": "列出这段序列里所有的跨膜区段"}]
  }'

Python(OpenAI SDK):

python
response = client.chat.completions.create(
    model="bio/protein-72b",
    temperature=0,
    messages=[{"role": "user", "content": "列出这段序列里所有的跨膜区段"}],
    extra_headers={"X-SciRouter-Cache": "enabled"},
)

向量接口(/embeddings)默认就走缓存——同样的输入必然是同样的向量。 不想要时带 X-SciRouter-Cache: disabled

适合用它的场景:批量评测反复跑同一套题、向量化一批会重复出现的文本、开发时反复调试同一个请求。 不适合要求每次重新生成的场景——命中时你拿到的是同一份回答,一字不差。 带了这个头就会缓存,不看 temperature:温度不为 0 时,之后每次拿到的都是第一次那一份随机结果。

请求头

请求头取值作用
X-SciRouter-Cacheenabled / disabled开启或关闭这次请求的缓存
X-SciRouter-Cache-TTL正整数(秒)这次结果保存多久。对话默认 1 小时、向量默认 24 小时;超过 24 小时按 24 小时算
Cache-Controlno-cache不读旧结果,调用模型后用新结果覆盖缓存
Cache-Controlno-store这次既不读也不写

X-SciRouter-Cache 不是这两个值、X-SciRouter-Cache-TTL 不是正整数时直接返回 400。 Cache-Control 里其它的值(max-age 之类)会被忽略。

怎么知道命中了

响应头 X-SciRouter-Cache

  • HIT —— 来自缓存,没有调用模型,不计费;
  • MISS —— 没命中,正常调用并计费,成功的结果会存下来;
  • BYPASS —— 本该走缓存,但这次请求要求不读(disabledno-cacheno-store),或者请求形状不能缓存(见下);
  • 没有这个头 —— 这次请求不涉及缓存。例外:流式请求排队很久、心跳已经先把响应头发出去时, 这个头来不及带上;以请求日志里的「缓存」标记为准。

非流式响应的 scirouter.cache 字段是同样的结论(小写)。 控制台的请求日志里,命中的记录带「缓存」标记,token 与费用都是 0,也不计入用量统计。

什么算「完全相同」

规范化之后的请求体比较:

  • 键的顺序、空白、11.0 这类写法差异不影响;
  • streamstream_optionsusermetadatastore 不参与比较—— 流式和非流式共用一份缓存,非流式存下的结果也能以流式回放;
  • max_completion_tokensmax_tokens 视为同一个参数,n: 1 与不写相同;
  • 其余任何字段不同(换了模型、改了一个标点、temperature 不同)都是另一个请求。

平台调整了这个模型背后的上游(换了接口或模型版本)之后,旧结果自动不再命中。

什么不会被缓存

  • 要多个候选(n 大于 1)或要逐 token 概率(logprobs / top_logprobs)的请求:回放给不出这些,响应头是 BYPASS
  • 失败的调用,以及被截断(finish_reasonlength)或内容为空的回答;
  • 太大的结果(对话超过 256 KB、向量超过 2 MB)。

隔离与计费细节

  • 缓存按 API Key 隔离:同一账号下的两把 Key 互相看不到对方的缓存。
  • 命中也占每分钟请求数(RPM),不占每分钟 token 数与并发。
  • 只对 /v1 的调用生效;Playground 与 Chat 不走响应缓存(在那里点「重新生成」,就该真的重新生成)。

幂等:重试不重复扣费

网络抖动时你多半会重试。为了不让一次重试变成两次计费,给请求带一个你自己生成的 Idempotency-Key

python
import uuid

key = str(uuid.uuid4())  # 同一个逻辑请求的所有重试用同一个值
response = client.chat.completions.create(
    model="bio/protein-72b",
    messages=[{"role": "user", "content": "帮我判断这段序列的二级结构倾向"}],
    extra_headers={"Idempotency-Key": key},
)

/chat/completions/embeddings 都支持,规则是:

  • 键最长 128 个字符,按 API Key 隔离,保留 24 小时;
  • 同一个键、同一个请求体:直接返回第一次的响应,带响应头 Idempotent-Replayed: true,不再调用模型、不再计费;
  • 第一次还没处理完时,同一个键再来:返回 409 idempotency_conflict
  • 同一个键、不同的请求体:也是 409 idempotency_conflict——这多半是你的代码把键复用到了别的请求上。 两种 409 的 code 相同,只有 message 不同;确定是重试同一个请求时,按退避等一会儿再试、并设上限;
  • 只记住成功(2xx)的响应。失败的请求可以用同一个键修正后重试;
  • 你这边先断开的请求不算数:客户端超时断开时,服务端会取消这次调用、不保存结果, 之后同一个键的重试会重新调用模型、照常计费。所以客户端超时要设得比模型的正常耗时长。

流式请求的幂等只管「进行中」:流还没结束时,同一个键的重试会得到 409; 流结束之后,同一个键会重新执行一次(照常计费)。流式响应没法完整重放,这是有意的取舍。

幂等和响应缓存是两回事:幂等只认你给的键,保证「同一个请求只算一次」; 响应缓存认的是请求内容,让「不同时间发出的相同请求」共用结果。两个可以一起用。

请验证你的身份

为了保护你的账户,这个操作需要再验证一次。

验证码已发送到你绑定的手机号。5 分钟内的其他敏感操作不会再问。

Cookie 偏好

您可以随时回到这里修改。页脚有常驻入口。

必要

维持登录状态、记住主题与语言偏好。没有它们本站无法正常工作,因此无法关闭。

统计

了解哪些功能有人用、用户在哪一步流失。只收集维度(页面、操作类型、计数),不收集您输入的任何内容。

本站不加载任何第三方统计脚本,也不做会话录屏。完整说明