一、背景简介
原AI聊天助手项目中,每次提问(Prompt)都需要调用公有LLM API来获取大模型的回答(Answer),然而,有些时候用户会向聊天助手询问同样的问题,我们将KV存储集成到聊天助手当中,初步实现了一个Prompt对应一个Answer的缓存功能。
但是有时候,用户向聊天助手询问的问题并不是一字不差,同样的语义,由于prompt字面的不同,原先的缓存功能就失效,现在的问题是如何实现语义级别的缓存功能。
二、组件依赖
2.1 高性能中间件 - 自研KV存储
支持命令:
| 命令 | 语法 | 说明 |
|---|---|---|
| SET | SET | 写入键值对,key 已存在则覆盖 |
| GET | GET | 读取键值,不存在返回 $-1 |
| DEL | DEL | 删除键,返回删除数量 |
| EXISTS | EXISTS | 检查键是否存在,返回 0 / 1 |
| MOD | MOD | 仅当键存在时更新值 |
| SAVE | SAVE | 手动触发 RDB 快照保存 |
| PING | PING message | 连通性探测,返回 PONG 或回显 message |
2.2 高效向量搜索 - FAISS
语义级别的识别,首先想到的方案是结合FAISS来做,(Facebook AI Similarity Search),这是由 Meta(原 Facebook)开源的一个高效向量搜索库。
FAISS向量搜索具体的内部实现是一个比较复杂的问题,这里只粗浅介绍一下:
原始的prompt,比如"我饿了"、"我想吃饭",被模型编码成为高维向量:
文本输入 高维向量空间
"我饿了" → 模型编码 → [0.12, -0.34, 0.56, ..., 0.78] (768维)
"我想吃饭" → 模型编码 → [0.11, -0.32, 0.55, ..., 0.79] (768维)
↑ 两个向量方向几乎相同 ↑
内存结构:
| 隐式ID | 向量数据 (768个float32) | 对应的文本 |
|---|---|---|
| 0 | [0.0123, -0.0456, 0.0789, ...] |
"我饿了" |
| 1 | [0.0115, -0.0412, 0.0765, ...] |
"我想吃饭" |
| 2 | [-0.0891, 0.1234, -0.0056, ...] |
"今天天气不错" |
- FAISS 只存数字,不知道prompt的文本是什么
- ID 自动生成(从0开始,每次调用add自增)
- prompt文本存在KV存储里,用 ID 关联
检索时暴力遍历,通过余弦相似度衡量向量方向的相似性,给定阈值,判断是否命中。当然了,检索算法有内置的优化实现。
我们faiss模块中使用到的核心接口如下:
| 分类 | 接口 | 一句话作用 |
|---|---|---|
| 创建索引 | IndexFlatIP |
建一个用内积算相似度的索引 |
| 写入 | add |
把向量存进索引 |
| 检索 | search |
找最相似的 Top-K 个向量 |
| 保存 | write_index |
把内存索引存成文件 |
| 加载 | read_index |
从文件恢复索引到内存 |
| 取数量 | ntotal |
看索引里存了多少条 |
| 取维度 | d |
看向量是几维的 |
2.3 通信底座 - gRPC
gRPC(Google Remote Procedure Call)是由谷歌公司开发的远程调用框架。什么叫远程调用?
如下图所示,这是一个多语言多平台的系统,不同语言和平台之间的对象本不能直接相互调用,gRPC就是为了解决这个问题出现的:

通过Protobuf 协议(一种语言无关、平台无关且便于传输的序列化协议),server端对需要共享的对象进行序列化,接收端进行反序列化,并且client端通过stub封装从而实现直接调用 (无需关系底层实现),这篇博客介绍了你所需要的Protobuf相关内容
三、模块架构
Go后端作为gRPC的client,通过stub收发proto消息,由Python实现的faiss语义层为gRPCserver,他们之间的通信消息和服务由Proto文件给定,proto文件中定义三个接口函数:
伪代码
GetCache(prompt)
PutCache(prompt)
DeleteCache(prompt)
prompt进faiss层被编码为相似度+ID,ID与KV中的answer绑定,faiss层通过GET ID、SET ID Answer等命令与KV存储交互。
整体模块架构总结如下:
┌────────────────────────────────────────────────────────┐
│ 业务客户端层 (Go Web 后端) │
└───────────────────────────┬────────────────────────────┘
│ gRPC 通信 (50051)
┌───────────────────────────▼────────────────────────────┐
│ 1. 网关层 (main.py) - 流量拦截与服务生命周期管理 │
└───────────────────────────┬────────────────────────────┘
│ 本地方法调度
┌───────────────────────────▼────────────────────────────┐
│ 2. 核心算法层 (engine.py) │
│ ├── [模块 A] 语义编码 (SentenceTransformer) │
│ ├── [模块 B] 几何正规化 ($L_2$ Normalization) │
│ └── [模块 C] 容错自愈 (维度探测器) │
└───────────────────────────┬────────────────────────────┘
│ 双向读写
┌───────────────────────────▼────────────────────────────┐
│ 3. 存储与索引层 (持久化) │
│ ├── [模块 D] 内存阵列检索 (FAISS) ──> 负责"控门" │
│ └── [模块 E] KV缓存 (自研KV) ──> 负责"管账" │
└────────────────────────────────────────────────────────┘