第三篇:Embedding 与向量检索
前两篇聊了文档解析和切片,地基打好了,接下来进入核心环节:怎么把文本变成向量,怎么在百万级数据里快速找到最相关的那几条。
先说结论
- Embedding 模型选型比向量库选型重要得多。 模型不行,换什么库都救不回来。
- 中文场景首选 BGE / M3E,英文场景首选 OpenAI / Cohere。
- 向量库选型看数据量和运维能力: 小规模用 PGVector,中大规模用 Milvus / Qdrant,已有 ES 集群可以用 Elasticsearch 做混合检索。
- 纯向量检索不够用,必须搭配 BM25 做混合检索。 这一点下一篇细讲,这篇先铺垫。
一个实际案例
某企业内部知识库,上线后用户反馈"搜不到东西"。排查发现:
- 文档解析和切片没问题
- 向量库是 Milvus,性能也没问题
- 问题出在 Embedding 模型 :用的是
text-embedding-ada-002,但用户提问和文档都是中文, ada-002 对中文语义的理解明显弱于中文专用模型
换成 bge-large-zh-v1.5 后,检索命中率从 62% 提升到 87%。换模型比调参数有效 10 倍。
一、Embedding 模型怎么选?
Embedding 的作用是把文本映射到一个高维向量空间,语义相近的文本,向量距离也近。所以模型的质量直接决定检索的上限。
主流模型对比
| 模型 | 维度 | 语言 | 特点 | 适用场景 |
|---|---|---|---|---|
| bge-large-zh-v1.5 | 1024 | 中文 | 中文检索 SOTA,开源免费 | 中文知识库首选 |
| bge-m3 | 1024 | 多语言 | 支持稠密+稀疏+多向量,一模型多用 | 多语言混合场景 |
| m3e-base / m3e-large | 768 / 1024 | 中文 | 中文语义匹配效果好 | 中文问答/搜索 |
| text-embedding-3-large | 3072 | 多语言 | OpenAI 最新,效果强但收费 | 预算充足的英文/多语言项目 |
| text-embedding-ada-002 | 1536 | 多语言 | 上一代,中文偏弱 | 不推荐新项目中英文场景 |
| Cohere embed-multilingual-v3.0 | 1024 | 多语言 | 多语言效果好,支持自定义维度 | 国际化项目 |
选型建议
- 纯中文项目 →
bge-large-zh-v1.5(效果最好)或m3e-large - 中英混合 →
bge-m3(一个模型搞定) - 纯英文项目 →
text-embedding-3-large或Cohere embed-multilingual-v3.0 - 不想自托管 → OpenAI / Cohere API
- 必须本地部署 → HuggingFace 下载 BGE / M3E 权重,用 Sentence-Transformers 加载
代码示例
ini
# 方案 1:Sentence-Transformers(本地部署 BGE)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-large-zh-v1.5")
vectors = model.encode(["公司报销流程是什么", "员工手册第12页"])
print(vectors.shape) # (2, 1024)
ini
# 方案 2:OpenAI API
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
response = client.embeddings.create(
model="text-embedding-3-large",
input=["公司报销流程是什么", "员工手册第12页"]
)
vectors = [item.embedding for item in response.data]
ini
# 方案 3:FlagEmbedding(BGE 官方库,支持更多功能)
from FlagEmbedding import BGEM3FlagModel
model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True)
# 同时输出稠密向量、稀疏向量、多向量
output = model.encode(["公司报销流程"], return_dense=True, return_sparse=True, return_colbert_vecs=True)
二、向量库怎么选?
向量库的核心职责:存向量 + 快速检索。选型看三个维度:数据量、查询延迟、运维复杂度。
主流向量库对比
| 向量库 | 类型 | 数据规模 | 运维难度 | 特点 |
|---|---|---|---|---|
| PGVector | PostgreSQL 插件 | < 1000 万 | 低(已有 PG 就能用) | 适合小规模,SQL + 向量一体化 |
| Milvus | 专用向量数据库 | 亿级 | 中(需单独部署) | 功能最全,社区活跃,中文文档好 |
| Qdrant | 专用向量数据库 | 亿级 | 中 | Rust 编写,性能高,API 友好 |
| Chroma | 轻量向量库 | < 1000 万 | 低 | 开发友好,适合快速原型 |
| Elasticsearch | 搜索引擎 | 亿级 | 中(已有 ES 就能用) | 支持 BM25 + 向量混合检索,天然优势 |
| Faiss | 向量检索库 | 亿级 | 高(需自己封装服务) | Facebook 开源,性能最强但无服务化 |
选型建议
- 已有 PostgreSQL → 直接装 PGVector 插件,零额外运维
- 已有 Elasticsearch → 用 ES 的
dense_vector类型,天然支持混合检索 - 从零开始、数据量大 → Milvus(中文社区好)或 Qdrant(性能高)
- 快速原型 / 小规模 → Chroma
- 追求极致性能、有工程能力 → Faiss 自己封装
三、检索方式:不只是"相似度搜索"
3.1 相似度搜索(最基础)
ini
# Milvus 示例
from pymilvus import Collection
collection = Collection("knowledge_base")
collection.load()
results = collection.search(
data=[query_vector], # 查询向量
anns_field="embedding", # 向量字段
param={"metric_type": "COSINE", "params": {"nprobe": 10}},
limit=10, # 返回 Top-10
output_fields=["text", "source"]
)
相似度度量方式:
- COSINE(余弦相似度) :最常用,不受向量长度影响
- L2(欧氏距离) :归一化后等价于余弦
- IP(内积) :向量归一化后等价于余弦
3.2 过滤检索(Metadata Filter)
用户问"销售部的报销流程",可以在检索时加过滤条件,只搜销售部的文档:
ini
from pymilvus import AnnSearchRequest, RRFRanker
# 元数据过滤
expr = "department == 'sales'"
results = collection.search(
data=[query_vector],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"nprobe": 10}},
limit=10,
expr=expr, # 过滤表达式
output_fields=["text", "source"]
)
关键点: 切片时把章节、部门、日期等信息写入 metadata,检索时就能做精准过滤。这也是为什么第二篇强调要保留标题层级。
3.3 多路召回(下一篇重点)
同时用向量检索 + BM25 关键词检索,结果合并后再重排序。这是当前生产环境的标配,下一篇详细展开。
四、多语言场景怎么处理?
如果文档和提问混合了中英文,有两种方案:
方案 A:用多语言 Embedding 模型
bge-m3、Cohere embed-multilingual-v3.0、text-embedding-3-large 都天然支持多语言,中英文文本可以直接放在同一个向量空间里检索。
方案 B:查询翻译
用户用中文提问,先把问题翻译成英文,再用英文向量检索英文文档。适合文档以英文为主、用户以中文为主的场景。
ini
# 查询翻译示例
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "将用户问题翻译成英文,只输出翻译结果"},
{"role": "user", "content": "公司报销流程是什么"}
]
)
translated_query = response.choices[0].message.content
# "What is the company reimbursement process?"
建议: 优先用方案 A,简单可靠。方案 B 多一次 LLM 调用,增加延迟和成本,除非多语言模型效果确实不够好再用。
五、性能优化:百万级数据怎么保证低延迟
| 优化手段 | 说明 | 效果 |
|---|---|---|
| HNSW 索引 | 近似最近邻算法,检索速度比暴力搜索快 10-100 倍 | Milvus / Qdrant / PGVector 默认支持 |
| IVF 索引 | 倒排文件索引,适合大规模数据 | Milvus 支持,需调 nlist 和 nprobe |
| 量化(FP16 / INT8) | 降低向量精度,减少内存占用 | 内存减半,精度损失 < 1% |
| 缓存热门查询 | 相同 query 直接返回缓存结果 | 高重复查询场景效果显著 |
| 预计算 Embedding | 文档入库时提前算好向量,不要在线算 | 必须做 |
经验值: 100 万条向量、HNSW 索引、COSINE 度量,Top-10 检索延迟通常在 10-50ms 之间。瓶颈往往不在向量检索,而在 LLM 生成。
六、避坑清单
- 中文项目用 ada-002 → 中文语义理解弱,换 BGE 立竿见影
- 向量不做归一化就比内积 → 结果不准,统一用 COSINE 或先归一化
- 不建索引就直接搜 → 暴力搜索,数据量大时延迟爆炸
- metadata 不存章节/来源信息 → 无法做过滤检索,也无法追溯答案来源
- 在线计算 Embedding → 每次请求都调一次模型,延迟翻倍,务必预计算
- 忽略多语言问题 → 中英文混用同一个模型,效果可能很差
总结
Embedding 和向量检索是 RAG 的"搜索引擎",模型选对了、库选对了,后面才能谈优化。记住一个原则:先保证召回质量,再追求检索速度。