核心摘要
LLM 语义缓存会为语义相近的请求复用完整答案。只判断相似度并不安全:有效命中还必须满足经过校准的阈值,并确保租户、授权范围、语言、模型、提示词、来源版本、安全策略和新鲜度窗口一致。误命中率应按请求意图和风险类别分别统计;缓存结果不能作为权威数据源,还必须支持主动失效、跳过缓存和运行监控。
区分语义缓存、精确缓存、提示词缓存与 KV Cache
“缓存”这个词对应多种不同机制。
| 缓存类型 | 复用对象 | 匹配方式 | 是否执行生成 |
|---|---|---|---|
| 精确响应缓存 | 完整回答 | 规范化请求完全一致 | 否 |
| 语义响应缓存 | 完整回答 | 向量相似度 + 过滤 | 否 |
| 厂商提示词缓存 | 模型前缀计算 | 重复 Token 前缀 | 是 |
| KV Cache | Attention K/V 状态 | 当前请求或受支持的跨请求 Token | 是 |
| RAG 检索 | 来源片段 | 查询相关性 | 是 |
语义缓存更接近“近似响应缓存”。即使都使用向量搜索和嵌入,它也不是 RAG 使用的向量数据库:RAG 检索证据后生成新答案,语义缓存直接返回历史答案。
生产环境命中路径
一次有效查询至少应包含:
{
"tenant": "tenant_42",
"auth_scope": "support:public",
"locale": "zh-CN",
"model_version": "model-family/revision",
"prompt_version": "support-answer/v12",
"source_revision": "kb-2026-07-28",
"safety_policy": "policy-v8",
"response_type": "public_faq"
}
尽量在向量查询内部执行这些过滤。先查全局最近邻,再在应用层检查租户,会制造不必要的数据泄露和时序风险。
阈值本质上是风险决策
阈值宽松会提高命中率和误匹配,阈值严格会减少错误,却可能让缓存没有经济价值。正确阈值取决于嵌入模型、距离度量、规范化方式、请求分布,以及错误回答的危害。
建立标注请求对:
| 请求对 | 预期 |
|---|---|
| “如何退货?”/“退货流程是什么?” | 匹配 |
| “最终销售商品能退吗?”/“如何退货?” | 通常不匹配 |
| “重置我的密码”/“重置另一个用户的密码” | 不匹配 |
| 不同租户的相同问题 | 不能跨分区匹配 |
扫描阈值并报告:
- 可缓存请求占比;
- 命中率;
- 误命中率;
- 有害误命中率;
- 陈旧命中率;
- p50/p95 查询与总延迟;
- 每个成功答案的成本。
必须按意图和风险切片分析。整体误命中率 1% 看似可控,但如果集中在账户、医疗或支付问题,就不可接受。
键设计与失效
缓存键不只是向量。任何会改变答案含义的输入都要版本化。
type CachePartition = {
tenant: string;
authScope: string;
locale: string;
modelVersion: string;
promptVersion: string;
sourceRevision: string;
policyVersion: string;
};
function canReuse(
candidate: CachePartition,
request: CachePartition,
): boolean {
return Object.keys(request).every(
(key) =>
candidate[key as keyof CachePartition] ===
request[key as keyof CachePartition],
);
}
TTL 用于时间新鲜度,但不能单独承担失效。以下变化应触发失效或版本轮换:
- 来源文档更新或删除;
- 权限或租户归属改变;
- 提示词、模型或嵌入模型变更;
- 安全策略改变;
- 回答被纠正或报告有害;
- 事故使某类答案全部失效。
优先使用版本化命名空间,不要扫描并修改所有条目。旧条目自然过期,新请求只进入当前版本。
缓存条目应包含什么
保存足以判断可复用性的证据,同时最小化敏感内容。
{
"request_embedding": "<vector>",
"normalized_request_hash": "sha256:...",
"response": "<长度受限且已验证的回答>",
"source_ids": ["policy-v7#returns"],
"partition": { "tenant": "public", "locale": "zh-CN" },
"versions": { "prompt": "v12", "model": "rev-8", "policy": "v8" },
"created_at": "2026-07-28T10:00:00Z",
"ttl_seconds": 86400,
"validation": { "status": "passed", "suite": "faq-v4" }
}
不要缓存秘密、原始凭据、一次性 Token、隐藏授权决策或私有模型上下文。静态加密不能让跨租户复用变得安全。
缓存准入策略
不是每个成功响应都应该写入缓存。只有满足显式准入策略的结果才能进入:
- 请求被分类为可缓存;
- 不含个性化或秘密字段;
- 确定性策略与安全检查通过;
- 回答包含所需引用或证据;
- 来源修订与过期时间已知;
- 输出完整,不是拒答或截断;
- 响应类型适合重复播放。
准入策略可以避免一次低质量回答或攻击结果被持续放大。
主要失败模式
| 失败 | 影响 | 控制 |
|---|---|---|
| 语义误命中 | 为错误意图返回合理外观答案 | 标注阈值评估与意图路由 |
| 跨租户命中 | 数据泄露 | 查询前分区与可信身份 |
| 陈旧命中 | 返回过时政策或事实 | 来源版本、TTL、事件失效 |
| 缓存投毒 | 恶意答案反复被服务 | 准入校验与来源 |
| 模型或提示词漂移 | 行为与当前版本不一致 | 版本化命名空间 |
| 热门答案锁定 | 旧回答抑制新版本改进 | 抽样绕过与定期刷新 |
| 缓存故障 | 延迟突增或请求失败 | 将缓存设为非必要依赖,并设置较短超时 |
缓存必须可安全绕过和重建,不能成为唯一事实系统。
评估与渐进发布
按阶段上线:
- 影子模式: 查询缓存但不返回,收集候选请求对。
- 只读灰度: 只开放已审查公开意图与严格阈值。
- 有限生产: 为选定租户或路由启用,并提供即时熔断。
- 阈值调优: 审查误命中切片后再扩大范围。
- 持续审计: 抽样绕过命中,与新生成结果比较以发现漂移。
每次命中记录条目 ID、分区摘要、距离、阈值、条目年龄、来源修订、校验版本、绕过原因和结果。当摘要与意图分类足够时,不要记录原始提示词。
何时适合使用语义缓存
它适用于高频、稳定、重复、低个性化的问题,且完整回答可以安全复用。FAQ 助手、公开文档和边界明确的分类是常见候选。
以下场景应避免或严格限制:
- 账户专属答案;
- 授权与安全决策;
- 快速变化的库存、价格和状态;
- 法律、金融或医疗结论;
- 写操作与副作用;
- 对话上下文会改变问题含义的请求。
LLM Gateway 架构指南说明缓存策略在路由和预算中的位置;AI 推理成本指南介绍如何衡量收益而不掩盖质量回归。
常见问题
应该使用完整对话计算语义相似度吗?
通常不能直接使用。对话历史可能包含身份、偏好、已有决策和指令,都会改变含义;只取最后一句也可能错误。应定义任务专用的规范表示。
一个缓存能同时服务多个嵌入模型吗?
不能把不兼容向量空间的结果混在同一索引。应版本化嵌入模型,并在迁移期间重建或双读。
命中后应该刷新 TTL 吗?
取决于新鲜度语义。滑动 TTL 可能让热门旧答案永不过期。政策和知识内容更适合绑定来源修订的绝对过期时间。
流式响应如何使用缓存?
只能缓存完整且通过验证的响应。取消或超时后的部分流不能准入;流式与非流式响应格式可能需要不同条目。
如何防止缓存投毒?
准入前执行与直接响应相同的证据、策略、安全和完整性检查,记录来源,并支持按条目、来源、版本和策略即时失效。
参考资料
- Redis Semantic Cache 文档,访问于 2026-07-28。
- RedisVL SemanticCache 指南,访问于 2026-07-28。
- Traefik Hub Semantic Cache 文档,访问于 2026-07-28。