通过向量嵌入而非字符串精确匹配实现语义缓存,将 LLM API 调用延迟降低 90%、成本降低显著,命中率可达 40%+。
用语义缓存把 LLM 延迟砍掉 90%,同时把 API 账单也压下去——靠的是缓存查询意图,而不是原始字符串。
直接调用 LLM 是现代后端栈里最慢、成本最高的环节。一次到 LLM 提供商的往返通常需要 2000ms 到 4000ms,每次请求要花几分钱。上了规模之后,用户体验和基础设施预算都会被拖垮。
传统的缓存在对话 AI 和搜索界面面前完全失效。如果你把原始查询字符串 hash 后丢进标准键值存储,缓存命中率通常超不过 15%–20%。
# 朴素做法:小变动就失效
cache_key = hashlib.sha256(user_query.strip().lower().encode()).hexdigest()
cached_response = redis_client.get(cache_key) # 近义词或拼写错误直接 miss
一个用户问"How do I reset my password?",另一个问"Forgot my credentials, need a reset link",其实表达的是完全相同的用户意图。但字符串精确匹配的 hash 把它们当作两个完全不同的查询,两次都 miss 了,白白触发两次冗余的、昂贵的模型推理。
解决方案是语义缓存:基于向量嵌入来缓存查询,而不是原始文本字符串。
不做精确键查找,而是把传入的查询转换为密集向量表示,然后在 Redis 里用余弦距离做近似最近邻(ANN)搜索。如果与某个已缓存查询的距离落在置信阈值内(例如相似度 $\ge$ 0.90),就立即返回缓存的答案。
[ 用户查询 ]
│
▼
[ 嵌入模型 ](如 bge-small-en-v1.5,约 10ms)
│
▼
[ Redis 向量搜索 ] ──(余弦相似度 ≥ 0.90?)
│ │
[ 是 ] [ 否 ]
│ │
▼ ▼
[ 返回缓存 ] [ 调用 LLM API ](约 3000ms)
(约 50ms) │
▼
[ 保存向量 + 文本 ]
这样一来,缓存命中的情况下下游延迟从约 3000ms 降到 50ms 以下,同时保护了上游的 rate limit。
下面是一套生产可用的模式,使用快速的本地嵌入模型(BAAI/bge-small-en-v1.5)和 Redis 向量搜索。
import numpy as np
import redis
from redis.commands.search.query import Query
from sentence_transformers import SentenceTransformer
encoder = SentenceTransformer("BAAI/bge-small-en-v1.5")
r = redis.Redis(host="localhost", port=6379, decode_responses=False)
def get_semantic_answer(query: str, threshold: float = 0.90) -> str:
query_vector = encoder.encode(query).astype(np.float32).tobytes()
# 在索引中搜索最近向量
q = Query("(*)=>[KNN 1 @vector $vec AS score]").return_fields("response", "score").dialect(2)
results = r.ft("idx:cache").search(q, query_params={"vec": query_vector})
if results.docs and (1 - float(results.docs[0].score)) >= threshold:
return results.docs[0].response.decode("utf-8")
# 缓存未命中:从 LLM 获取并存储向量
llm_response = call_llm(query) # 替换为实际的 LLM provider 调用
doc_id = f"cache:{hash(query)}"
r.hset(doc_id, mapping={"vector": query_vector, "response": llm_response})
return llm_response
低开销:本地运行轻量嵌入模型在 CPU 上只需要约 10ms,缓存命中时整个往返控制在 50ms 以内。
故障开放设计:如果向量搜索失败或遇到网络降级,管道会优雅地回退到直接调用 LLM,不会导致请求周期崩溃。
相似度阈值要保守调优:从严格的余弦相似度阈值开始,范围在 0.88 到 0.92 之间。低于 0.85 会引入误报——稍微不同的问题会拿到错误的答案。
嵌入与 Worker 就近部署:把嵌入模型直接跑在应用运行时容器里,或者与 Redis 放在同一 VPC 子网内。避免用外部嵌入 API 做缓存查找,因为网络延迟会让缓存失去意义。
对动态知识强制 TTL:对缓存的向量键设置明确的 TTL 策略。依赖变化后端数据的 LLM 答案必须自动过期,避免给出过时的幻觉答案。