Web Analytics

cache

⭐ 65 stars Simplified Chinese by NyraSeithhh

我们这套 Prompt Cache 是怎么搭的

线上实测:单轮 opus 请求 prompt 49310 token,其中 47354 直接从缓存读回,命中率 96%。近 100 条请求里 74 条命中。

这份文档讲的不是「prompt caching 是什么」,而是我们具体把请求切成了哪几个方块、每个方块装什么、为什么这么切就能稳定命中这么高


核心思路:把请求切成「会变」和「不变」两堆

缓存是前缀匹配的。从第一个字节开始比对,只要有一个字节和上次不同,从那往后全部失效重算。

所以我们做的唯一一件事,就是把一整个请求拆成方块,按「最不会变 → 最会变」的顺序码好,只给前面不变的方块挂缓存标记,把所有会变的东西全甩到最后面去。

命中率高不是因为用了什么黑科技,是因为我们的缓存前缀真的逐字节不变


我们的方块布局(BP1 → BP4)

Anthropic 一个请求最多 4 个缓存断点。我们是这么分配的:

═══ system blocks(每块单独挂 cache_control)═══

┌─────────────────────────────────────────────┐ │ BP1 人设 + 语言规则 + 工具说明 + 长期记忆 │ 几乎永不动 ├─────────────────────────────────────────────┤ │ BP2 每日内容(gateway-bp2-daily.txt) │ 一天换一次 ├─────────────────────────────────────────────┤ │ BP3 当前会话压缩摘要(这个 session 专属) │ 约 80K token 触发一次压缩 └─────────────────────────────────────────────┘

═══ messages 数组 ═══

...历史轮次(一字不改)... ┌─────────────────────────────────────────────┐ │ BP4 倒数第二条 user 消息挂标(rolling) │ 把全部历史纳进缓存 └─────────────────────────────────────────────┘ 动态注入 ← 不挂标,所有会变的塞这里 当前这条 user 消息

每个方块为什么这么放

BP1 — 最稳的垫底。 人设、语言铁律、工具说明书、长期记忆,这些几乎永远不动,放最前面。它一变,后面全废,所以它必须是整个请求里最稳定的东西。

BP2 — 每天换一次的那层。gateway-bp2-daily.txt 读,一天更新一次。它变了只影响 BP2 往后,不动 BP1。

BP3 — 这个会话自己的压缩摘要。 关键点:它是 session-only 的,不是全局长期骨架。当前窗口聊到约 80K token 就触发一次压缩,把老的上下文压成摘要。注意是「网关侧稳定摘要」才进 BP3 挂标;前端那种每轮都在变的当前窗口摘要不挂标,直接丢到下面的动态区,不然每轮重建缓存。

BP4 — rolling 断点,省最多的就是它。 默认缓存边界只到 system 末尾,历史对话不在缓存里。我们在倒数第二条 user 消息上再挂一个标,把缓存边界一路往下扩到包含全部历史。多轮长会话里,前面几十轮的 token 全部变成缓存读取,这是 96% 命中的主力。

挂在「倒数第二条」而不是「最后一条」,是因为最后一条 user 是这轮新输入,每次都不一样,挂上去等于没挂。挂倒数第二条,缓存边界正好停在「这轮之前的所有内容」。

会变的东西去哪了

时间戳、本轮记忆召回、临时纸条、前端的当前窗口摘要 —— 全部塞进 BP4 之后的一个伪 user 消息里:

仅供参考,勿复述:
(当前时间 / 本轮召回的记忆 / 摘要 / 纸条……)

它每轮都在变,但它排在所有断点之后,所以它怎么变都不碰缓存前缀。这是整套设计最关键的一刀:把易变内容隔离到缓存边界外面


让请求粘在同一个后端:user_id

光挂 cache_control 还不够。中转的负载均衡会把请求随机派到不同后端节点,缓存写在 A 节点,下一次请求飘到 B 节点就读不到,表现就是「只写不读、命中永远 0」。

我们的解法:请求顶层固定带

"metadata": { "user_id": "sillage-anan-stable" }
固定字符串,让 sticky 路由永远把这个对话粘在同一个后端。这个不带,前面所有断点都白挂。


不同渠道,挂法不一样

不是每个上游都吃同一套。网关按上游域名自动判定,分三种模式:

| 模式 | 上游 | 协议 | cache_control 挂哪 | TTL | |---|---|---|---|---| | anthropic-bp | 直连 Anthropic / msui / 金瓜瓜 | /v1/messages | system blocks 上 | 1h(金瓜瓜只支持 5m) | | or-blocks | OpenRouter | OAI /chat/completions | 嵌进 messages 的 content block | 1h | | oai-passthrough | 普通 OAI 中转 / 未知站 | OAI | 不挂,让站子自己隐式缓存 | 不可控 |

两条主路径的粘性机制不一样,绝对不能混

另外有些站(比如 ekan)对 cache_control 直接报 4xx。网关把报过错的 host 写进黑名单文件,之后对它永久降级成无缓存透传,不再反复撞 400。这是自愈,不用人管。


5min 还是 1h

| TTL | 写入成本 | 读取成本 | 用在哪 | |---|---|---|---| | 5min | 1.25x | 0.1x | 连续快速对话 | | 1h | 2x | 0.1x | 隔半小时回来接着聊,稳赚 |

读取永远 0.1x(省 10 倍)。区别只在写入贵多少、能存多久。我们日常连发用 5m,长会话隔断回来用 1h。


怎么确认真的命中了

看响应 usage:

判读:

看网关日志:

pm2 logs chat-gateway-proxy | grep -E "HIT|MISS"
监控页:https://catcatty.uk/llm/cache-stats

---

我们踩出来的铁律

做到这七条,剩下的就是按上游选对 modettl。我们的 96% 就是这么来的。

--- Tranlated By Open Ai Tx | Last indexed: 2026-07-13 ---