RAG检索增强生成:原理、数据切片与向量化详解#
大语言模型(LLM)虽然拥有海量的通用知识,但它的知识边界止于训练数据的截止日期,也无法访问企业内部文档、私有数据库这类"没见过"的信息。想让模型回答这些问题,通常有两条路:**微调(Fine-tuning)把新知识"炼"进参数里,或者RAG(Retrieval-Augmented Generation,检索增强生成)**在回答前先把相关资料"喂"给模型。本文重点讲后者——它的基本原理、数据切片(Chunking)与向量化(Embedding)的具体做法,以及一个经常被混淆的问题:向量化和大模型训练到底是不是一回事。
一、为什么需要RAG#
大模型的知识本质上是训练阶段被压缩进参数矩阵里的统计规律,这带来两个绕不开的限制:
- 知识过时:模型上线后,世界仍在变化,但参数不会自动更新。
- 知识盲区:私有代码库、内部Wiki、最新的产品文档,模型从未见过。
- 幻觉问题:面对未知问题,模型倾向于"编"一个看似合理的答案,而不是承认不知道。
RAG的思路很朴素:既然模型脑子里没有,那就在提问时把答案相关的资料一并递给它,让它"开卷考试"。 这样既不需要重新训练模型(成本低、迭代快),又能让答案有据可查(可追溯到原始文档),是目前企业知识库、智能客服、文档问答类应用最主流的技术方案。
二、RAG的核心原理#
RAG系统通常分为两个阶段:离线的知识库构建和在线的检索生成。
2.1 整体流程#
离线阶段(构建索引):
原始文档 → 数据切片(Chunking) → 向量化(Embedding) → 存入向量数据库
在线阶段(问答):
用户提问 → 向量化(Embedding) → 向量数据库中检索相似片段 → 拼接Prompt → LLM生成答案用一张更具体的图来看在线问答的过程:
用户提问:"我们系统的订单超时是怎么处理的?"
↓
Embedding模型把问题转成向量 q
↓
在向量数据库中做相似度检索(如余弦相似度、ANN近邻搜索)
↓
召回Top-K个最相关的文档片段(如订单超时相关的3段代码注释/文档)
↓
把这些片段拼进Prompt:"根据以下资料回答问题:[片段1][片段2][片段3]\n问题:xxx"
↓
LLM基于给定资料生成回答(而不是凭参数记忆瞎编)可以看到,RAG本质上是**信息检索(IR)+ 生成(Generation)**的组合,模型的角色从"知识的存储者"变成了"资料的阅读理解者",这也是它能大幅缓解幻觉问题的原因——回答有据可依,而不是靠参数里模糊的统计记忆。
2.2 RAG vs 微调#
| 维度 | RAG | 微调(Fine-tuning) |
|---|---|---|
| 更新知识的方式 | 更新向量库中的文档,无需改模型 | 需要重新训练/微调模型参数 |
| 成本 | 低,主要是Embedding和检索的算力 | 高,需要标注数据和训练算力 |
| 时效性 | 高,文档改了检索结果立刻生效 | 低,每次更新知识都要重新训练 |
| 可解释性 | 强,可以指出答案来自哪个文档片段 | 弱,答案来自参数,无法溯源 |
| 适用场景 | 知识频繁变化、需要溯源、私有数据 | 需要改变模型的语气/风格/推理能力 |
两者并不互斥,实践中也常常结合使用:用微调让模型更好地理解某个领域的术语和回答风格,用RAG保证具体事实的准确性和时效性。
三、数据切片(Chunking)#
在把文档灌进向量库之前,第一步是切片:把一篇长文档切成一个个较小的片段。这一步看似简单,实际上直接决定了检索质量的上限。
3.1 为什么不能整篇文档直接向量化#
- Embedding模型有输入长度限制:大多数Embedding模型的上下文窗口在512~8192 token之间,超长文档会被截断。
- 粒度太粗,检索精度差:如果把一整篇几千字的文档编码成一个向量,那这个向量表达的是"全文大意",当用户只问一个具体细节时,这个向量和问题向量的相似度会被大量无关内容"稀释",检索不准。
- 拼接进Prompt的成本:即使检索到了,把一整篇文档塞进Prompt也会占用大量上下文窗口,挤占LLM推理的"预算",还会增加token成本。
所以合理的粒度是:每个片段尽量只包含一个完整、独立的语义单元——太大则精度下降、太小则丢失上下文,两者都会拉低最终答案的质量。
3.2 常见的切片策略#
1. 固定长度切片(Fixed-size Chunking)
按字符数或token数切分,通常会保留一定的重叠(overlap)以避免关键信息刚好被切在两段的交界处丢失语义连贯性。
def fixed_size_chunk(text, chunk_size=500, overlap=50):
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunks.append(text[start:end])
start += chunk_size - overlap # 保留overlap长度的重叠
return chunks优点是实现简单、速度快;缺点是完全不考虑语义边界,可能把一句话、一个代码块硬生生切成两半。
2. 按语义结构切片(Structure-aware Chunking)
优先沿着文档天然的结构切分——按段落、按Markdown的标题层级、按代码的函数/类边界,尽量保证每个片段是一个完整的语义单元。
def markdown_chunk(text):
# 按二级标题切分,每个chunk保留自己的标题作为上下文
sections = text.split("\n## ")
chunks = []
for i, sec in enumerate(sections):
prefix = "## " if i > 0 else ""
chunks.append(prefix + sec.strip())
return chunks这也是本站这类技术博客最适合的方式:一篇文章天然按##/###分节,沿着标题切片能最大程度保留语义完整性。
3. 递归切片(Recursive Chunking)
先按最大的结构单元(如章节)切,如果切出来的片段仍然超过长度限制,再依次按段落、句子递归细分,是LangChain等框架里最常用的默认策略,兼顾了语义完整性和长度可控性。
4. 语义切片(Semantic Chunking)
用Embedding模型逐句计算向量,在相邻句子的语义相似度出现明显下降的地方切分——本质上是"哪里意思变了,就在哪里切一刀"。效果通常更好,但需要额外的Embedding计算,成本更高,适合对检索精度要求高的场景。
3.3 切片粒度的权衡#
| 片段大小 | 优点 | 缺点 |
|---|---|---|
| 较小(如100~200 token) | 检索精度高,命中片段更聚焦 | 容易丢失上下文,需要更多Top-K才能覆盖完整信息 |
| 较大(如500~1000 token) | 保留更多上下文,语义更完整 | 检索精度下降,Prompt占用更多token |
实践中一般会配合**overlap(重叠窗口)和元数据(如标题、来源、章节路径)**一起存储,检索到片段后还能带着元数据一起拼进Prompt,帮助模型理解这段内容的上下文出处。
四、向量化(Embedding)#
切好片段后,下一步是把每个文本片段转换成一个固定维度的数值向量——这就是向量化,也叫Embedding。
4.1 向量化的核心思想#
向量化的目标是把"语义"映射到几何空间里:语义相近的文本,向量在空间中的距离也相近。比如"订单超时怎么处理"和"超时的订单如何应对"这两句话字面上差异不小,但语义几乎一致,一个好的Embedding模型会把它们映射到空间中很接近的两个点。
有了这个性质,检索问题就变成了一个几何问题——给定一个查询向量,找出向量库里离它最近的K个向量,常用的相似度度量是余弦相似度(Cosine Similarity):
similarity(A, B) = (A · B) / (|A| × |B|)值越接近1,代表两个向量方向越一致,语义越相似。
4.2 向量数据库中的检索#
真正的向量库不会对每个查询做暴力全量比对(数据量大了之后代价太高),而是用**近似最近邻(ANN, Approximate Nearest Neighbor)**算法,比如HNSW(分层可导航小世界图)、IVF(倒排文件索引)等,用可接受的精度损失换取数量级的检索速度提升。常见的向量数据库有Milvus、Pinecone、Weaviate、Qdrant,也有像PostgreSQL的pgvector这种插件式方案。
4.3 向量化模型怎么来的#
Embedding模型本身也是一个神经网络(通常基于Transformer的Encoder结构,如BERT系列,或专门训练的Embedding模型如OpenAI的text-embedding-3、开源的BGE、GTE系列)。它的训练目标很直接:通过对比学习(Contrastive Learning),拉近语义相近文本对的向量距离,推远不相关文本对的向量距离。
训练样本:(锚点文本, 正样本, 负样本)
例:("如何处理订单超时", "订单超时的解决方案", "如何注册新用户")
训练目标:
distance(锚点, 正样本) 尽量小
distance(锚点, 负样本) 尽量大五、向量化和大模型训练原理的关系#
这是很多刚接触RAG的人容易混淆的地方:向量化用的Embedding模型和ChatGPT/Claude这类生成式大模型,是不是同一种东西训练出来的? 答案是:底层架构同源(都基于Transformer),但训练目标和产出完全不同。
5.1 共同的根:Transformer与自监督预训练#
无论是Embedding模型还是生成式LLM,起点通常都是同一套技术——Transformer架构上做自监督预训练(Self-supervised Pretraining):拿海量无标注文本,通过"预测被遮盖/预测下一个词"这类任务,让模型学会捕捉语言的统计规律和语义结构。这一步产出的是一个理解语言、编码语义的"基座",无论后续要拿它做生成还是做向量表示,都是站在这个基座上。
区别从这里开始分叉:
5.2 生成式LLM:训练目标是"预测下一个词"#
ChatGPT、Claude这类大模型属于Decoder-only架构,训练目标是经典的自回归语言建模(Autoregressive Language Modeling):
给定前面的词序列,预测下一个词的概率分布
P(词_n | 词_1, 词_2, ..., 词_n-1)之后再叠加指令微调(SFT)、人类反馈强化学习(RLHF)等阶段,让模型学会"听懂指令、给出有帮助且安全的回答"。整个过程优化的核心始终是生成质量——模型要能一个词一个词地把连贯、正确、符合意图的文本"写"出来。这个模型的产物是逐词生成的文本序列。
5.3 Embedding模型:训练目标是"把语义压缩成一个定长向量"#
而Embedding模型通常是Encoder(或双向)架构,它不关心怎么"往下写",只关心怎么把一段变长的文本,压缩成一个固定维度、能代表其语义的向量(比如1536维)。训练目标不是预测下一个词,而是前面提到的对比学习:让语义相似的文本在向量空间中靠近,不相似的推远。这个模型的产物是一个静态的数值向量,本身不能"说话",只能用来做相似度计算和检索。
5.4 一张表看清区别#
| 维度 | 生成式LLM(如GPT/Claude) | Embedding模型(如BGE/text-embedding-3) |
|---|---|---|
| 架构 | 通常是Decoder-only Transformer | 通常是Encoder(双向)Transformer |
| 训练目标 | 自回归预测下一个词 + SFT/RLHF对齐 | 对比学习,拉近/推远语义相似度 |
| 输出形式 | 逐词生成的文本序列 | 固定维度的数值向量 |
| 核心能力 | 理解意图、推理、生成连贯文本 | 度量语义相似度 |
| 在RAG中的角色 | 阅读检索到的资料,生成最终回答 | 把文本/问题编码成向量,供检索用 |
5.5 为什么理解这个区别很重要#
搞清楚这个区别,会直接影响RAG系统的工程决策:
- 不要用生成式LLM直接输出的隐藏层向量去做检索——它的训练目标是"预测下一个词",向量空间的几何结构并未被显式优化为"语义相似度可比较",直接拿来做余弦相似度检索效果通常不如专门训练的Embedding模型。
- Embedding模型和生成模型可以独立选型、独立升级——比如可以用一个轻量的开源Embedding模型做检索,同时用能力更强的大模型做最终生成,两者解耦、各司其职,这也是RAG架构天然模块化的原因之一。
- 两者都依赖同一批"预训练常识"——这也是为什么很多Embedding模型是在通用预训练模型的基础上,用对比学习"继续训练(continue training)“出来的,而不是从零开始训练,本质上是复用了Transformer在预训练阶段学到的语言理解能力,再把优化目标"掰"向语义相似度这个方向。
六、结语#
RAG把"模型该记住什么"和"模型该怎么表达"这两件事拆开了:知识存在可以随时更新的向量库里,模型只负责基于检索到的资料做理解和表达。切片决定了知识被拆成什么粒度,向量化决定了这些知识能不能被准确地"找到”,而生成模型负责把找到的资料组织成一个连贯、可信的回答。
理解向量化模型和生成式大模型"同源但目标不同"这一点,能帮助我们在设计RAG系统时做出更合理的选型:不必迷信"参数越大越好",而是让每个组件专注于自己被训练要做的事。
通过邮件回复



