前言
为什么 RAG 离不开 Embedding 与向量数据库?
在上一篇文章中,我们已经讲过:
RAG(Retrieval-Augmented Generation)本质上是"先找资料,再让大模型回答问题"。
而"找资料"这一步,背后最关键的两个基础设施就是:
- 文本嵌入模型(Embedding Model)
- 向量数据库(Vector Database)
如果说大语言模型(LLM)是 RAG 的"大脑",
那么 Embedding 是把语言变成大脑能理解的数学信号 ,
向量数据库则是 RAG 的"长期记忆系统"。这一篇,我们就从最底层出发,把这两块彻底讲透。
一、文本嵌入模型:连接文本与向量的桥梁
1. 什么是 Embedding?
Embedding(嵌入) ,本质是一种映射关系:
把「人类语言」映射成「高维向量空间中的一个点」
举个最直观的例子:
假设我们有三句话:
- 「小猫在睡觉」
- 「小狗在玩球」
- 「量子力学中的薛定谔方程」
Embedding 模型会把它们变成类似这样的向量:
小猫在睡觉 → [0.21, 0.87, 0.12, ..., 0.44]
小狗在玩球 → [0.19, 0.82, 0.15, ..., 0.41]
薛定谔方程 → [0.91, 0.02, 0.77, ..., 0.03]
关键不在于数值本身,而在于 向量之间的"距离":
- 「小猫」和「小狗」的向量 距离更近
- 它们和「量子力学」的向量 距离更远
Embedding 把"语义相似性"转化成"几何距离"
2. 用"小猫 / 小狗"理解向量空间
可以把 embedding 空间想象成一个巨大的多维空间:
- 相似语义 → 空间中距离近
- 不相关语义 → 空间中距离远
比如在一个简化的二维示意中:
动物
▲
|
小猫 ● ● 小狗
|
|
● 量子力学
在真实系统中,这个空间往往是 768 / 1024 / 1536 维 ,
但原理完全一致。
3. Embedding 的本质:给语言编码
Embedding 并不是"理解语言",而是:
为语言建立一种可计算、可比较的数学表示
它解决了一个根本问题:
机器不懂"意思"
机器可以比较"向量距离"
这正是 RAG 能成立的前提。
二、Embedding 工程:不只是"调接口"
1. Embedding ≠ 一行 API 调用
很多教程会给出类似代码:
embeddings = embedding_model.embed_query("什么是 RAG?")
但在真实系统中,Embedding 是一个工程问题,而不是 API 问题。
Embedding 工程通常包括:
- 文本切分(Chunking)
- 清洗与规范化
- 批量嵌入
- 版本管理
- 向量一致性问题
2. 为什么要切分文本?
Embedding 模型通常有 最大 Token 限制,但更重要的是语义密度问题。
对一篇长文直接做 embedding 会导致:
- 一个向量包含太多语义
- 相似度检索不精准
实践经验:
- 技术文档:300~800 tokens
- 对话/FAQ:一个问题一个 chunk
- 代码文档:按函数 / 类拆分
3. 主流 Embedding 模型的演进
Embedding 模型的演进大致经历了三个阶段:
传统 NLP 向量
- TF-IDF
- Word2Vec / GloVe
无法理解上下文,效果有限
句向量模型(Sentence Embedding)
- Sentence-BERT
- Universal Sentence Encoder
开始具备语义能力
大模型时代的 Embedding
- OpenAI text-embedding-3-large
- BGE / E5 / GTE 系列
- 多语言 / 指令微调 embedding
当前 RAG 的主流选择
三、LangChain 中的 Embedding 实践
1. Embedding 接口设计思想
LangChain 并不关心你用的是哪家模型,它只定义了一个统一接口:
from langchain.embeddings import OpenAIEmbeddings
embedding = OpenAIEmbeddings()
vector = embedding.embed_query("RAG 是什么?")
核心抽象只有两个方法:
embed_query:用于用户查询embed_documents:用于知识库文本
2. 一个完整的 Embedding 流程示例
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
docs = load_documents()
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
chunks = splitter.split_documents(docs)
embedding = OpenAIEmbeddings()
vectors = embedding.embed_documents([c.page_content for c in chunks])
这一步的产出是:
一组文本 + 一组向量
接下来,就轮到向量数据库登场了。
四、向量数据库:RAG 的记忆基石
1. 什么是向量数据库?
向量数据库是一种专门为向量相似度检索设计的数据库。
它的核心能力只有一件事:
给定一个向量,快速找到最相似的 K 个向量
这正是 RAG 中「检索」阶段的核心能力。
2. 向量数据库 vs 传统数据库
| 对比维度 | 传统数据库 | 向量数据库 |
|---|---|---|
| 查询方式 | 精确匹配 | 相似度搜索 |
| 索引结构 | B+Tree | ANN / HNSW |
| 数据类型 | 标量 | 高维向量 |
| 典型场景 | OLTP | 语义检索 |
RAG 场景中,传统数据库完全不适用
3. 相似性检索算法基础
欧式距离(L2)
distance = √Σ(xᵢ - yᵢ)²
- 距离越小 → 越相似
- 适合未归一化向量
余弦相似度(Cosine Similarity)
cos(θ) = (A·B) / (|A||B|)
- 更关注方向而非长度
- Embedding 场景最常用
4. 为什么不用全量扫描?
假设你有 100 万条向量,每条 1536 维:
- 全量计算相似度 → 性能灾难
因此向量数据库几乎都使用:
- 近似最近邻(ANN)
- HNSW / IVF / PQ 等索引结构
牺牲一点精度,换取数量级的性能提升
五、Embedding + 向量数据库 = RAG 的地基
将前面的内容串起来:
- 文档 → 切分 → Embedding
- 向量 → 存入向量数据库
- 用户问题 → Embedding
- 向量检索 → TopK 文档
- 构建 Prompt → 交给 LLM
这套链路中:
- Embedding 决定 能不能找对
- 向量数据库决定 找得快不快
总结
Embedding:把语言转成可计算的向量表示
向量数据库:为 RAG 提供高效、可扩展的语义检索能力
RAG 的效果上限,往往取决于这两块的设计质量