跳转至

向量库

一句话:向量库是 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 是查询时参数,线上可动态调高召回(牺牲一点延迟);Mef_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"}}]},
)

实战要点

  1. Embedding 模型早期锁死,维度全局统一:中途换模型必须全量重 Embedding,是 RAG 最贵的返工之一。先用 100 条评测集对比 bge / m3e / GTE 的召回,再定稿。

  2. 向量库 ≠ 全文检索,二者要分工:稠密向量擅长语义匹配(同义词、改写),稀疏检索(BM25)擅长精确关键词/专有名词/编号。生产 RAG 必须混合检索互补,单一向量检索召回有天花板(详见混合检索一节)。

  3. HNSW 召回靠 ef_search 在线调,内存靠 M 离线控:线上发现召回不足,先调大 ef_search(几乎零成本);内存吃紧或构建慢,降 M。百万级文档 M=16ef_construction=200ef_search=128 是常见起点。

  4. payload metadata 必须随向量一起存:来源、页码、章节、权限标签都放进 payload,检索时能做硬过滤("只搜我有权限的文档")和引用回溯。没有 metadata 的向量库等于丢了出处,幻觉治理无从谈起。

  5. 百万级规模规划内存与分片:100 万条 1024 维 float 向量约 4GB 裸数据,加 HNSW 图索引后翻倍。提前算内存预算,单机扛不住就上 Qdrant 分片或 Milvus 分布式集群,别等线上 OOM 才补救。

本节相关题目

难度 题目 链接
基础 什么是 Embedding、常用相似度度量有哪些 → 题库
进阶 ES kNN 向量检索 vs 专用向量库如何选型 → 题库
深度 百万级文档秒级检索的向量库与索引设计 → 题库