语义缓存
当多个用户或应用提出相同的问题时,再次调用 LLM 会浪费时间和 token。语义缓存按 prompt 内容和模型作为键来存储响应,因此相同或几乎相同的请求可以立即获得缓存的答案。
语义缓存如何工作
Section titled “语义缓存如何工作”缓存在请求到达 provider 之前拦截 AI 请求。它通过对模型名称和请求中最后一条 user 消息的内容取哈希来构建缓存键。如果存在匹配且未过期的缓存条目,则立即返回缓存的响应,完全不会调用 provider。
缓存键使用完整内容的哈希,而不是截断的前缀。两个措辞不同但含义相同的请求会生成不同的键,从而产生不同的缓存条目。缓存是精确匹配,而非基于嵌入的相似匹配。这意味着只有逐字符完全相同的 prompt 才能共享缓存的响应。
缓存支持通过 CacheBackend trait 接入可插拔后端:
| 后端 | 适用场景 |
|---|---|
内存(DashMap) | 单实例部署,低延迟,无外部依赖。默认容量为 10,000 条。 |
| Redis | 多实例部署,所有数据面 Pod 共享一份缓存。条目在 Pod 重启后仍然存在。 |
| pgvector | 已经运行 PostgreSQL 并希望将缓存与其他数据放在一起的环境。支持比内存更大的工作集。 |
后端在配置阶段选定。网关对存储层做了抽象,所有后端共享相同的缓存键格式、TTL 语义和淘汰行为。
内存后端详情
Section titled “内存后端详情”默认的内存后端使用 DashMap 实现无锁并发访问。默认最多容纳 10,000 条。当缓存达到容量上限时:
- 首先淘汰所有过期条目。
- 如果淘汰后缓存仍满,则移除最旧的条目以腾出空间。
这种两阶段淘汰机制在保持缓存容量有界的同时,优先保留较新的条目。
通过 AIService 资源启用和调优语义缓存。默认 TTL 为一小时(3600 秒),默认 maxTokens 上限为 4096:
apiVersion: gateway.nantian.dev/v1alpha1kind: AIServicemetadata: name: cached-openai namespace: nantian-demospec: provider: openai format: openai model: gpt-4o semanticCache: enabled: true ttl: 3600s maxTokens: 4096ttl 字段控制缓存条目从创建起的存在时间。较短的 TTL 让响应更及时,但代价是更多的 provider 调用。较长的 TTL 节省更多 token,但如果底层模型行为发生变化,可能会提供过时的响应。
Max Tokens
Section titled “Max Tokens”maxTokens 字段限定了可缓存的响应大小上限。超过 maxTokens 的响应不会被存储,防止缓存被长篇生成内容填满。如果你的典型请求产出的补全较短,设置较低的 maxTokens(如 1024)可以让缓存聚焦于最可复用的响应。
将 enabled 设为 false 可以完全绕过缓存,无需删除配置。这在调试缓存相关问题或在模型迁移期间临时关闭缓存时很有用。
缓存命中时,网关返回存储的响应,其中包含与原始 provider 调用相同的 ID、模型、choices 和 usage 数据。客户端无法区分缓存响应与新鲜响应。
缓存未命中时,请求正常发送到 provider,响应被存储以供后续使用。只有在响应满足 maxTokens 阈值且后端有空间时才会存储。
缓存不会在命中时延长 TTL。以一小时 TTL 存储的条目会在创建后恰好一小时过期,无论被访问了多少次。这保证了缓存生命周期的可预测性,避免过期条目超出预期窗口继续存在。
AI Gateway 指标通过命中和未命中计数器反映缓存行为。利用这些指标评估缓存效果:
- 命中率高意味着缓存通过避免 provider 调用获得了回报。生产流量上超过 30% 的命中率是保留缓存的强烈信号。
- 命中率低说明 prompt 变化太大,精确匹配缓存效果不佳,或者 TTL 对你的访问模式来说太短。
- 将未命中计数器和 provider 延迟一起观察。缓存命中率突然下降伴随延迟上升,可能表明缓存后端故障。
- 随时间追踪缓存条目数,确认淘汰策略是否将后端控制在合理范围内。
生产环境建议
Section titled “生产环境建议”- 从适中的 TTL(600 到 3600 秒)开始,根据你的 prompt 多样性和响应新鲜度需求调整。知识库问答类工作负载通常比创意写作类工作负载可以容忍更长的 TTL。
- 单 Pod 部署使用内存后端。扩展到多个数据面副本时切换到 Redis,让所有实例共享一份缓存。
maxTokens应与你想缓存的响应类型对齐。设低一些(如 1024)可以避免存储长篇补全,调高则可以覆盖更多响应形态。- 缓存键与模型名绑定。如果你更改
spec.model,已有的缓存条目不会被复用。在模型迁移期间清空缓存或让条目自然过期。 - 在调整 TTL 或
maxTokens后监控命中率。大幅调整可能会显著改变缓存行为。
缓存键由两部分构成:模型名和请求中最后一条 user 消息的内容。两者通过标准哈希函数一起哈希,生成十六进制编码的标识符,格式为 cache:0123456789abcdef。
只有最后一条 user 消息参与生成键。system 消息和对话历史中的早期消息被排除在外。这意味着两个 system prompt 不同但最终用户问题相同的请求将共享一个缓存条目。同时也意味着,在一次多轮对话中,用户在第三轮和第五轮问出相同问题时,会生成相同的缓存键。
全内容哈希的方式避免了截断前缀可能带来的碰撞。两个开头相同但后续内容不同的 prompt(例如 “What is the capital of France?” 与 “What is the capital of Germany?”)会生成不同的缓存键。
一致性注意事项
Section titled “一致性注意事项”由于缓存是对最后一条 user 消息做精确匹配,它在相同 prompt 能稳定产出一致质量响应的环境中效果最好。以下场景中缓存可能效果较差:
- 包含时间戳、随机 ID 或会话特定数据的 prompt。每个变体都会生成不同的缓存键,导致缓存未命中。
- 频繁变化的 system prompt。由于 system 消息不参与键生成,两个 system prompt 不同但 user 消息相同的请求会收到相同的缓存响应,即使 system prompt 本应改变输出。
- 返回非确定性响应的模型。如果相同的 prompt 在不同调用中产生不同的答案,首个响应会被缓存,后续调用都返回首个版本,即使一次新的调用本可能产生不同的答案。
在这些情况下,考虑缩短 TTL 或完全禁用缓存。