Embedding 与向量检索

Embedding 是 RAG 的基石,理解它才能理解为什么 RAG 能让 LLM”知道”私有知识。


什么是 Embedding?

Embedding 是将文本、图像、声音等数据转换为高维浮点数向量的过程。

1
"苹果" → [0.23, -0.45, 0.67, 0.89, -0.12, ...]  # 1536维向量

目标:语义相近的内容在向量空间中距离近。

1
2
3
4
5
6
7
8
9
10
11
12
向量空间示意(简化到2维):
Y

"猫" |
\ |
\ |
"苹果手机"-----+------"水果"
|
"香蕉"
|
"橙子" |
+----------------→ X

余弦相似度

衡量两个向量”方向”的相似度,取值范围 [-1, 1]:

相似度含义
1.0完全相同
> 0.8语义相近(通常认为相关)
0.5-0.8有一定关联
< 0.5几乎无关
< 0语义相反
1
2
3
4
import numpy as np

def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

Embedding 模型

主流模型(2025 年)

模型维度说明
text-embedding-3-small1536OpenAI 性价比方案
text-embedding-3-large3072OpenAI 最强性能
text-embedding-ada-0021536老版,稳定
BGE-M31024开源,支持多语言
M3E1536国产开源,支持中文
nomic-embed-text768Ollama 内置,开源

中文 Embedding 推荐

1
2
3
4
5
6
7
8
9
10
# 中文优化:BGE-M3 或 M3E
from langchain_community.embeddings import HuggingFaceBgeEmbeddings

embedding = HuggingFaceBgeEmbeddings(
model_name="BAAI/bge-m3",
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True}
)

vector = embedding.embed_query("什么是大模型")

API 调用方式

1
2
3
4
5
6
7
8
9
# OpenAI
from openai import OpenAI
client = OpenAI()

response = client.embeddings.create(
model="text-embedding-3-small",
input="要向量化的文本"
)
vector = response.data[0].embedding # 1536维浮点数列表

向量数据库

选型对比

数据库优点缺点适用场景
Qdrant高性能,支持过滤需要单独部署生产级 RAG
Milvus功能全,可扩展部署复杂海量向量
pgvector基于 Postgres,运维简单性能一般中小规模
Chroma轻量,开发友好不适合生产原型/测试
Weaviate混合搜索强资源占用高混合检索
FAISS内存级,最快无持久化实验/离线

向量检索核心操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Qdrant 示例
from qwraps_qdrant import Qdrant

# 存储
qdrant.upsert(
collection_name="docs",
points=[
{"id": 1, "vector": embedding, "payload": {"text": "Spring AI是什么"}},
{"id": 2, "vector": embedding2, "payload": {"text": "LangChain怎么用"}},
]
)

# 检索
results = qdrant.search(
collection_name="docs",
query_vector=user_query_embedding,
limit=5 # Top-K
)

ANN 算法(近似最近邻)

暴力计算所有向量距离成本太高,用 ANN 在可接受精度损失下快速检索:

算法原理速度精度
HNSW图遍历最快
IVF聚类 + 搜索
PQ向量压缩取决于压缩率

生产环境推荐 HNSW,Qdrant / Milvus / Weaviate 均支持。


Embedding 在 RAG 中的全流程

1
2
3
4
5
6
7
8
9
10
11
12
文档处理:
原始文档 → 分块(Chunking)→ 每个块 → Embedding模型 → 向量 → 存入向量数据库
↕ 元数据(来源/标题/时间)

用户查询:
用户问题 → Embedding模型 → 查询向量

向量数据库:Top-K 相似度搜索 → 召回 K 个相关块

上下文拼接:用户问题 + 召回块 → 构造 Prompt

LLM 生成回答

分块策略(Chunking)

策略方法适用场景
固定大小每 N 个 Token 一块通用,快速
段落切分按段落(换行)切语义完整
语义切分LLM 检测断点质量最高,代价大
标题切分按 Markdown 标题树结构化文档
递归字符按换行→句号→词递归切平衡质量与成本
1
2
3
4
5
6
7
8
9
# LangChain 文本分割器
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块 Token 数(注意是 Token 不是字符)
chunk_overlap=50, # 块之间重叠,防止边界切断
separators=["\n\n", "\n", "。", " "] # 分割优先级
)
chunks = splitter.split_text(long_document)

混合检索(Hybrid Search)

单一向量检索有局限,结合关键词搜索(BM25)效果更好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 典型混合检索
query_vector = embedding.embed_query(user_question)
query_keywords = extract_keywords(user_question) # BM25

# 1. 向量搜索 Top-100
vector_results = vector_db.search(query_vector, limit=100)

# 2. 关键词搜索 Top-100
keyword_results = bm25_index.search(query_keywords, limit=100)

# 3. Reciprocal Rank Fusion (RRF) 合并排序
combined = rrf_merge(vector_results, keyword_results, k=60)

# 4. 取 Top-5 作为 RAG 上下文
final_context = combined[:5]