向量库¶
一句话:向量库是 RAG 的"记忆中枢"——把分块文本经 Embedding 压成稠密向量,再用近似最近邻(ANN)索引在毫秒级完成相似度检索,是召回速度与精度的工程核心。
概念¶
Embedding 之后,每个文本块都变成一个固定维度的稠密向量(如 768 维浮点数组)。但几百万个向量摆在那里,用户问一句"如何配置熔断",你怎么在毫秒级找到语义最接近的 Top-5?
如果用暴力法(计算 query 向量和所有文档向量的相似度,再排序),百万级文档要算百万次点积,延迟秒级起步,完全不可用。向量库解决的就是这个问题:它用近似最近邻(ANN, Approximate Nearest Neighbor)索引把暴力搜索的 O(N) 降到 O(logN) 量级,牺牲极小的精度(召回率 95%+)换取几十到几百倍的速度提升。
所以向量库 = Embedding 向量的存储 + ANN 索引 + 相似度检索。常用相似度度量有三类:
| 度量 | 含义 | 适用 |
|---|---|---|
| 余弦相似度(Cosine) | 衡量方向夹角,归一化后只看语义方向 | 文本语义检索最常用,对向量长度不敏感 |
| 点积(Dot / Inner Product) | 向量未归一化时同时受方向和模长影响 | 归一化向量下等价于余弦;部分模型(如 OpenAI)用点积 |
| 欧氏距离(L2) | 几何距离,越小越相似 | 图像/聚类场景更多,文本少用 |
经验:文本 RAG 默认用余弦相似度;许峰的百万级文档案例正是"余弦 + BM25"的组合。
原理¶
Embedding 选型与维度¶
Embedding 模型决定了向量的"语义表达能力"上限,是召回质量的地基。中文场景主流选型:
| 模型 | 维度 | 特点 |
|---|---|---|
| bge-large-zh / bge-m3(智源) | 1024 / 多语言 | 中文 SOTA 开源,bge-m3 支持稠密+稀疏+多向量混合,长文本友好 |
| m3e-base / m3e-large | 768 / 1024 | 中文社区常用,性价比高 |
| text-embedding-ada-002 / text-embedding-3(OpenAI) | 1536 / 可调 | 效果稳但需云端 API,私有化场景受限 |
| GTE / E5 系列 | 768~1024 | 多语言泛化好 |
维度的影响:维度越高表达力越强,但存储和检索开销线性增长。768~1024 维是中文 RAG 的常见甜点区。注意 Embedding 模型一旦选定,向量库里所有向量必须同维度——中途换模型意味着全量重新 Embedding,这是工程上要锁死早期选型的根本原因。
向量库对比¶
| 向量库 | 定位 | 优势 | 适用 |
|---|---|---|---|
| Qdrant | 专用向量库(Rust) | 高性能、过滤强、Payload 元数据好用、部署简单 | 中大型 RAG 首选,彭超企业级 Agent 平台用 Qdrant |
| Milvus | 专用向量库(Go/C++) | 分布式、十亿级规模、生态成熟 | 超大规模(亿级以上)、需分布式集群 |
| FAISS | 库(Meta 开源,非服务) | 极致单机性能、纯内存、无网络开销 | 原型验证、单机、嵌入式;不是独立数据库,无 CRUD/持久化/过滤 |
| ES 向量字段(kNN) | 搜索引擎扩展向量能力 | 与已有 ES 文本检索/BM25 同库,天然支持混合检索 | 已有 ES 基础设施、想一套库搞定稀疏+稠密检索 |
| PostgreSQL + pgvector | 关系库扩展 | 与业务数据同库、事务一致 | 中小规模、向量和业务数据强关联 |
选型口诀:百万级、要元数据过滤 → Qdrant;十亿级、要分布式 → Milvus;已有 ES、想省一套基础设施 → ES kNN;纯单机原型 → FAISS;和业务数据强一致 → pgvector。许峰"百万级文档"用余弦 + BM25 混合,ES 或 Qdrant 都能落地。
HNSW 索引:ANN 的主流实现¶
暴力搜索不可行的根因是线性扫描。ANN 的核心思想是用图结构提前组织向量,让搜索只在"大概正确"的区域跳跃,而不遍历全部。当前工业界事实标准是 HNSW(Hierarchical Navigable Small World,分层可导航小世界图):
- 把所有向量构建成一张多层近邻图:上层稀疏(少数节点、长距离连接,用于快速跳到目标区域),下层稠密(所有节点、短距离连接,用于精确定位)。
- 查询时从最上层入口节点开始贪心搜索,逐层下沉,像走高速公路先到城市、再走小路到门牌。
- 结果是 O(logN) 的查询复杂度,召回率通常 95%+,延迟毫秒级。
HNSW 三个核心超参,直接决定"召回率 vs 内存/构建速度"的权衡:
| 参数 | 含义 | 调大效果 |
|---|---|---|
M |
每个节点的最大连接数 | 召回↑、内存↑、构建慢 |
ef_construction |
建图时候选列表大小 | 索引质量↑、构建更慢 |
ef_search |
查询时候选列表大小 | 召回↑、查询更慢(运行时可调) |
工程要点:ef_search 是查询时参数,线上可动态调高召回(牺牲一点延迟);M 和 ef_construction 是建索引时定的,调了要重建。另外 HNSW 是纯内存索引,百万级文档要算好内存预算,或开启磁盘卸载(Qdrant/Milvus 支持)。
// LangChain4j + Qdrant 集成示例
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.qdrant.QdrantEmbeddingStore;
// 1. 选定 Embedding 模型(维度必须和集合维度一致)
EmbeddingModel model = new BgeSmallZhV15EmbeddingModel(); // 512 维示例
// 2. 连接 Qdrant,指定集合与 HNSW 参数
QdrantEmbeddingStore store = QdrantEmbeddingStore.builder()
.host("localhost").port(6334)
.collectionName("kb_docs")
.build();
// 3. 入库:把文本块 Embedding 后写入,附带 metadata
TextSegment seg = TextSegment.from(text).toBuilder()
.metadata(Metadata.from("source", fileName).put("page", page).build())
.build();
store.addAll(List.of(seg), model.embedAll(List.of(seg)).content());
// 4. 检索:query Embedding → 余弦相似度 Top-K
Embedding q = model.embed(query).content();
List<EmbeddingMatch<TextSegment>> hits = store.findRelevant(q, 5, 0.7); // topK=5, minScore=0.7
# Qdrant 官方客户端示例
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
client = QdrantClient(host="localhost", port=6333)
# 创建集合:维度 1024、余弦相似度(Qdrant 自动建 HNSW 索引)
client.recreate_collection(
collection_name="kb_docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
# 入库:向量 + payload(metadata),用于过滤与引用回溯
client.upsert(
collection_name="kb_docs",
points=[PointStruct(id=1, vector=emb, payload={"source": f, "page": p})],
)
# 检索:query 向量 → 余弦 Top-K,可带 payload 过滤条件
hits = client.search(
collection_name="kb_docs",
query_vector=query_emb,
limit=5,
query_filter={"must": [{"key": "source", "match": {"value": "合同.docx"}}]},
)
实战要点¶
-
Embedding 模型早期锁死,维度全局统一:中途换模型必须全量重 Embedding,是 RAG 最贵的返工之一。先用 100 条评测集对比 bge / m3e / GTE 的召回,再定稿。
-
向量库 ≠ 全文检索,二者要分工:稠密向量擅长语义匹配(同义词、改写),稀疏检索(BM25)擅长精确关键词/专有名词/编号。生产 RAG 必须混合检索互补,单一向量检索召回有天花板(详见混合检索一节)。
-
HNSW 召回靠
ef_search在线调,内存靠M离线控:线上发现召回不足,先调大ef_search(几乎零成本);内存吃紧或构建慢,降M。百万级文档M=16、ef_construction=200、ef_search=128是常见起点。 -
payload metadata 必须随向量一起存:来源、页码、章节、权限标签都放进 payload,检索时能做硬过滤("只搜我有权限的文档")和引用回溯。没有 metadata 的向量库等于丢了出处,幻觉治理无从谈起。
-
百万级规模规划内存与分片:100 万条 1024 维 float 向量约 4GB 裸数据,加 HNSW 图索引后翻倍。提前算内存预算,单机扛不住就上 Qdrant 分片或 Milvus 分布式集群,别等线上 OOM 才补救。
本节相关题目¶
| 难度 | 题目 | 链接 |
|---|---|---|
| 基础 | 什么是 Embedding、常用相似度度量有哪些 | → 题库 |
| 进阶 | ES kNN 向量检索 vs 专用向量库如何选型 | → 题库 |
| 深度 | 百万级文档秒级检索的向量库与索引设计 | → 题库 |