
最近被 LLM API 账单追杀,缓存成了救命稻草。但接下来问题来了——选精确缓存还是语义缓存?说实话,大多数情况下两个都要,只是用途不同。精确匹配几乎零成本运行,而且绝对不会返回错误答案;但在实际生产环境中命中率可能只有十分之一。语义缓存能捕获几倍的流量,但会引入一个必须正视的正确性风险。这篇把各擅胜场的地方说清楚,背后的数学逻辑也捋一遍,最后给个决策框架。
缓存是 AI API 缓存体系的一部分——精确缓存和语义缓算是两层;第三层是提供商原生的透传缓存,这个单独说。
精确匹配缓存的逻辑很直接:对请求做个确定性指纹(通常是对规范化后的消息数组、模型名、温度等参数跑一遍 SHA-256),然后在 Redis 这类键值存储里查这个指纹。命中就返回缓存响应。查询是 O(1),p95 延迟 10ms 以内。存储空间受缓存大小预算限制,条目按 LRU 或 TTL 淘汰。
语义缓存走的是另一条路:用嵌入模型(通常是小型快速的,比如 BGE-small、MiniLM 或者 text-embedding-3-small)把用户 prompt 向量化,然后去向量数据库查最接近的存储向量。如果余弦相似度超过阈值(通常 0.93–0.97),就返回那个存储向量关联的缓存响应。查询是 O(log n),p95 延迟 20–40ms,含嵌入推理。
两层都缓存完整响应。提供商原生透传不一样——它缓存的是前缀处理,在提供商那边完成。这篇只讨论响应缓存的两层。
精确匹配缓存在真实 LLM 流量中命中率惨淡,原因不在实现差。生产环境的 prompt 几乎必然携带每请求的上下文——用户名、session ID、时间戳、最近 RAG 召回的段落、可变的工具列表。即使用户意图完全相同,prompt 字节层面也是不同的,SHA-256 指纹自然分道扬镳。结果就是精确缓存只能命中那 5–15% 真正完全一致的流量——定时跑的内部查询、系统测试调用、用户重复提交。
VERIFY (founder):把上面的 5–15% 替换成过去 30 天 Prism 生产流量的实际精确缓存命中率,按 task_type 拆分。数据源:
usage_logs聚合,条件cache_status='hit-exact'。
语义缓存能抓住精确缓存漏掉的变体。两个用户问"退款政策是什么"和"怎么退款",prompt 字节不同,嵌入后向量几乎平行,余弦相似度能到 0.96–0.98。阈值 0.95 的语义缓存直接返回同一个答案。生产环境语义缓存命中率通常在 25–50%,是在精确缓存基础上叠加的——支持聊天机器人和 FAQ 类场景接近高限;工具调用代理带着可变检索上下文,在低限徘徊。
VERIFY (founder):把上面的 25–50% 替换成 Prism 默认 0.95 阈值下实测语义命中率,按 task_type(simple / code / reasoning / complex)拆分。数据源:
usage_logs,条件cache_status='hit-semantic'。
差距的结构性根源在于:用户意图的维度比用户输入低得多。问"退款政策是什么"有千万种表达方式,但退款政策只有一条。嵌入把输入维度压缩到意图维度,这才是语义缓存能跑起来的本质原因。
精确匹配是正确选择——往往也是唯一正确选择——当以下任一条件成立:
语义匹配在复杂度上值得投入,当:
假阳性问题才是大多数语义缓存实现翻车的地方。缓存返回了错误答案比没缓存更糟——用户带着错误信息离开,记在产品账上,你还可能浑然不知。安全兜底靠的是阈值工程,下一节展开。
余弦相似度阈值是语义缓存唯一的调参杠杆。设太低,自信满满给错误答案;设太高,命中太少覆盖不了嵌入开销。可辩护的默认值是0.95,理由如下。
把它理解成"这是真匹配吗"这个问题的精准率/召回率问题。阈值调节的是边界:
曲线形状跟工作负载强相关。范围窄的工作负载(比如单一产品文档聊天机器人)可以安心跑 0.92,因为相关问题天然聚堆。范围宽的工作负载(比如通用助手)必须跑 0.96 以上,因为问题空间更分散。
正确姿势是上监控。 跑 0.95,日志里记每个命中的相似度分数,定期抽样 100 个让人工评判缓存答案是否合适。假阳性 <2%,可以试试降低阈值多抓命中;假阳性 >5%,往上调。
假设你跑一个接在通义千问上的客服聊天机器人。流量画像:
不加缓存的提供商成本:20,000 × (800 × $0.004 + 300 × $0.012) / 1,000 = $136 / 天(约 ¥4,100 / 月)。
叠加上缓存:
语义缓存的嵌入成本:20,000 × $0.00002 = $0.40/天。可以忽略。
基础设施成本:阿里云 Redis(≈¥70/月托管版)+ Upstash Vector(≈¥210/月,50 万向量)。合计 ¥280/月,对比月省 ¥5,800。投产比一天出头。
VERIFY (founder):用真实 Prism 客户画像或代表性聚合数据替换算例,用当前定价。上面举例的数字合理,值得锚定在真实客户形态上。
重点不是具体数字——而是两层缓存的运行成本相比 savings 是毛毛雨。唯一真正的问题是假阳性率,阈值工程能解。
Prism 默认在每个付费请求上并发跑三层缓存——精确、语义、提供商原生透传。调度器先查精确缓存(Redis,p95 <8ms),miss 透传语义缓存(Upstash Vector + BGE-small 嵌入,阈值 0.95,p95 含嵌入调用约 30ms),再 miss 才代理给提供商并挂上 cache-control 标记触发提供商原生透传。每个响应带 X-Prism-Cache-Status 头标记命中哪层,还带 X-Prism-Cache-Saved-Cents 显示省了多少。
几个设计决策值得说说:
指纹规范化。 Prism 在指纹之前先规范化消息数组——剥离内部 cache-control 标记、给确定性 key 排序、tokenize 策略一致——这样字面上等价的请求哈希到同一个 key。踩过的坑记录在 Prompt cache fingerprinting pitfalls。
阈值按作用域可配置,Pro+ 限定。 默认 0.95,Pro+ 账户可通过 X-Prism-Cache-Threshold 头按项目调。/dashboard/cache 的缓存检查器展示命中率-阈值曲线,调之前能先看预期效果。
流式兼容。 缓存命中无论请求是否带 stream=true 都返回非流式 JSON。中途缓存是地雷(流中断会污染缓存);直接绕开。
可以在 savings calculator 里用自己的流量参数跑 ROI 估算,跟内部用的定价模型一样。
给工作量选缓存方案:
LLM API 响应缓存的经济账异常友好——假阳性风险是唯一真实成本,这是工程纪律问题,不是无解的。
余弦相似度阈值应该从多少起步?
0.95。在大多数生产工作量上保守到假阳性低个位数,同时能抓到大多数真实改写变体。从这里基于采样的假阳性率调参,别靠直觉。
语义缓存对代码 prompt 不适用?
通常是的,视嵌入模型而定。同一个意图但变量名不同,在大多数通用嵌入空间里会嵌入得很远,代码工作量的语义命中率通常偏低。两条路:换代码专用嵌入模型(比如 BGE-code),或者坦然接受语义缓存在代码 prompt 上不是主战场,靠精确 + 提供商原生。
能不能不依赖嵌入模型跑语义缓存?
不能。语义缓存在定义上就是基于嵌入的相似度匹配。能做的是只跑精确 + 提供商原生透传,不用嵌入依赖也能抓到真实流量的一部分。
底层答案变了缓存会不会被污染?
缓存失效问题是真实的。两招:TTL(条目在可配置时间窗口后过期)和显式失效(源内容变更时按模式清除匹配条目)。Prism 两个都支持——Pro+ 按项目配置 TTL,缓存检查器支持按模式逐个驱逐。
语义缓存必须用向量数据库?
实际上必须。要在成千上万甚至上百万条存储嵌入上做相似度搜索,需要 HNSW 或类似的索引。自托管选 pgvector、Qdrant;托管选 Pinecone、Upstash Vector。Prism 内部用 Upstash Vector。