RAG 学习笔记
记录 RAG(Retrieval-Augmented Generation,检索增强生成)的完整流程、三种范式、以及生产级 RAG 的优化手段(query 改写、多路检索、rerank)。配带数字的完整例子,便于理解每一步"谁和谁比、比出什么"。
一、为什么需要 RAG
LLM 的知识有两个局限:
- 静态:训练完成后不知道新信息(今天的新闻、当前时间)
- 不私有:不知道你的内部文档、业务数据
RAG 的思路:检索相关文档 → 喂给 LLM → LLM 基于文档回答。这样 LLM 就能回答它本来不知道的问题,且答案有据可查(可溯源到文档)。
二、基础 RAG 流程(4 步)
1. 切片(chunking): 把文档切成小块
2. 入库(indexing): 每块文本 → embedding → 向量存入向量库
3. 检索(retrieval): 用户问题 → embedding → 和库里向量比相似度 → 取最相似的 k 块
4. 生成(generation): 把检索到的文本块拼进 prompt → LLM 生成回答
下面逐步拆解。
三、切片(Chunking)
把长文档切成小块,因为:
- embedding 对短文本效果更好(长文本语义被稀释)
- 检索要精准(返回整篇文档不如返回最相关的一段)
- LLM context 有限
常见切法
| 方法 | 说明 |
|---|---|
| 固定长度 | 每 N 字符切一块,带 overlap(相邻块重叠,保证上下文不断裂) |
| 递归字符切分 | 按段落 → 句子 → 字符递归切,尽量在自然边界切 |
| 按标题切分 | Markdown 按 # ## 标题切,保留语义结构 |
典型参数
chunk_size:每块字符数(如 800~1600)chunk_overlap:相邻块重叠字符数(如 100),避免切断上下文- 小块合并:太小的块合并到上一块,避免碎片
四、Embedding 与向量库
Embedding
把文本转成定长向量(如 1024 维),让语义相近的文本向量也相近。
"禅院飞鸟介绍" → [0.12, -0.34, 0.56, ..., 0.78] (1024个数)
"禅院飞鸟的背景" → [0.11, -0.32, 0.55, ..., 0.77] (向量很接近)
"CPU排查方法" → [-0.45, 0.23, -0.11, ..., 0.02] (向量差很远)
向量库
存文档向量,支持高效相似度检索。常见:Milvus、Faiss、Chroma、Pinecone。
入库时文档向量预先算好存着,检索时只算 query 向量,和库里现成的比,所以快。
相似度度量
- 余弦相似度:算两个向量夹角,越接近 1 越像
- L2 距离(欧氏距离):算向量直线距离,越小越像
五、向量检索
流程
query → embed → query向量
↓
和库里所有文档向量算相似度
↓
取相似度最高的 top-k 个文档
top-k vs top-p(易混)
| 含义 | 用在哪 | |
|---|---|---|
| top-k | 取相似度最高的 k 个文档 | 检索阶段(RAG 用这个) |
| top-p | 从累积概率达 p 的词里采样(nucleus sampling) | 生成阶段(LLM 采样,和检索无关) |
RAG 检索用 top-k(如 top-3),取最相似的 3 个文档。
六、三种 RAG 范式
| 范式 | 流程 | 检索时机 | 适用 |
|---|---|---|---|
| Naive RAG(经典) | 问题 → 检索 → 拼 prompt → 生成 | 每次必检索 | 简单 QA,问题明确 |
| Agentic RAG | LLM 决定调不调检索工具、用什么 query | LLM 自己判断 | 问题多样,有时不需要检索 |
| Advanced RAG | query改写 + 多路检索 + rerank + 拼 prompt | 优化检索质量 | 生产级,追求准确率 |
Agentic RAG vs 传统 RAG 的关键区别
| 传统 RAG | Agentic RAG | |
|---|---|---|
| 检索时机 | 每次必检索 | LLM 决定调不调 |
| 检索词 | 用户原话 / 改写 | LLM 自定(作为工具参数) |
| 检索结果去哪 | 拼进 prompt context | 作为 ToolMessage 在 ReAct 循环里给 LLM |
Agentic RAG 把检索做成 Agent 的一个工具,LLM 自己决定何时检索、用什么词检索。灵活,但检索结果走 ToolMessage,LLM 可能不如"直接拼 context"那样充分使用文档。
七、Advanced RAG 的优化手段
7.1 Query 改写
用户原话未必是好的检索词。用 LLM 把问题改写成更适合检索的词。
用户原话: "我想了解禅院飞鸟,还有禅院业是什么"
↓ LLM 改写
q1: "禅院飞鸟 介绍"
q2: "禅院飞鸟 出处 背景"
q3: "禅院业 含义"
多查询(Multi-Query)
生成多个不同角度的检索词,每个都去检索一遍,合并结果(不是选最好的 query)。
q1 → 检索 → 文档 A, B, C
q2 → 检索 → 文档 B, D, E
q3 → 检索 → 文档 F, G
合并去重: A, B, C, D, E, F, G
为什么要多个:单个 query 召回不全,多角度检索覆盖更广。合并的是文档,不是 query。
HyDE(假设文档检索)
让 LLM 先生成一个"假设性答案文档",用这个假设文档的向量去检索。思路:答案和答案更像(比"问题和答案"更接近)。
7.2 多路检索(混合检索)
向量检索(语义匹配)+ 关键词检索(BM25,精确匹配)结合。向量擅长语义,关键词擅长精确词匹配,混合更稳。
7.3 Rerank(重排序)
向量检索召回的 top-N 文档,用 cross-encoder 重新打分排序,取最相关的 top-k。
为什么叫"重"排序:向量检索已经按相似度排过一次,rerank 是用更准的模型再排一次。
八、Bi-encoder vs Cross-encoder(核心区分)
这是理解 rerank 的关键。两种打分方式:
Bi-encoder(双编码器)—— 向量检索用
query → 编码 → query向量
doc → 编码 → doc向量
分数 = 余弦相似度(query向量, doc向量)
- query 和 doc 分别编码,各得向量,再算距离
- doc 向量可预先算好存库,检索时只算 query 向量
- 快,但 query 和 doc 没有交互,精度有限
Cross-encoder(交叉编码器)—— rerank 用
[query + doc] 一起输入模型 → 一个相关性分数
- query 和 doc 拼一起过模型,直接输出分数
- 不能预计算,每个 query-doc 对都要现跑一次
- 慢,但准——query 和 doc 在模型内部做 attention 交互,能捕捉细粒度匹配
对比
| Bi-encoder | Cross-encoder | |
|---|---|---|
| 输入 | 单条文本 | (query, doc) 对 |
| 输出 | 向量 | 分数 |
| 能否预计算 | 能(doc 向量存库) | 不能 |
| 速度 | 快 | 慢 |
| 精度 | 一般 | 高 |
| 用途 | 初筛(从海量文档召回 N 个) | 精排(从 N 个挑 k 个) |
类比
- Bi-encoder = 给 query 和 doc 各拍张照片,比两张照片像不像(快但粗)
- Cross-encoder = 把 query 和 doc 摆一起仔细通读,判断多相关(准但慢)
九、完整 Advanced RAG 例子(带数字)
设定:知识库有 5 个文档(入库时已向量化)
D1: 禅院飞鸟介绍
D2: 禅院飞鸟出处
D3: 禅院飞鸟内容
D4: CPU排查方法(无关)
D5: 长尾效应(无关)
用户问题
"我想了解禅院飞鸟,还有禅院业是什么"
步骤 1:LLM 改写出 3 个检索词
q1: "禅院飞鸟 介绍"
q2: "禅院飞鸟 出处 背景"
q3: "禅院业 含义"
步骤 2:每个 query 各自向量化,各自和 5 个文档比相似度
q1 向量 vs D1~D5:
| 文档 | 相似度 | |
|---|---|---|
| D1 | 0.92 | 最像 |
| D3 | 0.80 | |
| D2 | 0.71 | |
| D4 | 0.12 | |
| D5 | 0.08 |
取 top-2 → 挑出 D1, D3
q2 向量 vs D1~D5: 挑出 D2, D1
q3 向量 vs D1~D5: 挑出 D1, D2
步骤 3:合并挑出的文档(去重)
{D1,D3} ∪ {D2,D1} ∪ {D1,D2} = {D1, D2, D3}
候选文档池:D1, D2, D3
步骤 4:rerank(cross-encoder 打分)
把原问题 + 每个候选文档一起输入 cross-encoder,输出分数:
cross-encoder(原问题, D1) → 0.95
cross-encoder(原问题, D3) → 0.88
cross-encoder(原问题, D2) → 0.72
排序:D1(0.95) > D3(0.88) > D2(0.72),取 top-2 → D1, D3
步骤 5:拼进 prompt,LLM 生成回答
prompt:
系统: 基于以下资料回答
资料: [D1] 禅院飞鸟是... [D3] 禅院飞鸟讲述...
用户: 我想了解禅院飞鸟,还有禅院业是什么
LLM → 基于D1、D3生成回答
流程图
用户问题
↓ LLM 改写
q1, q2, q3
↓ 每个各自向量化,各自和知识库比相似度
q1 → 挑出 D1,D3
q2 → 挑出 D2,D1
q3 → 挑出 D1,D2
↓ 合并文档去重
候选池: D1, D2, D3
↓ rerank(原问题+文档 一起输入cross-encoder打分)
D1(0.95) > D3(0.88) > D2(0.72)
↓ 取top-2
D1, D3
↓ 拼进prompt
LLM 生成回答
十、三个模型协作
完整 RAG 系统用到三个独立模型,各司其职:
| 模型 | 职责 | 怎么工作 |
|---|---|---|
| embedding 模型(bi-encoder) | 文本 → 向量,用于检索 | 单条文本输入,输出向量 |
| rerank 模型(cross-encoder) | 给 query-doc 对打分,用于精排 | query+doc 一起输入,输出分数 |
| LLM | 生成最终回答 | 对话生成 |
不能合并成一个,因为训练数据、模型结构、优化目标都不同:
- embedding 模型擅长"把文本压成向量"
- rerank 模型擅长"判断两段文本相不相关"
- LLM 擅长"生成自然语言"
十一、关键认知清单
- RAG 解决 LLM 的静态和不私有问题:检索外部文档喂给 LLM,让它回答原本不知道的问题。
- 基础流程四步:切片 → embedding 入库 → 向量检索 → 拼 prompt 生成。
- 切片带 overlap:相邻块重叠,保证上下文不断裂。
- 向量检索是 query 向量 vs 文档向量:对比一次,挑出最相似的文档。检索结果是挑出的文档,不是再拿去对比的输入。
- top-k 是检索参数,top-p 是生成参数,两者无关。
- 三种范式:Naive(每次检索)、Agentic(LLM 决定检索)、Advanced(加改写+rerank)。
- Agentic RAG 把检索做成工具:LLM 自定 query,结果走 ToolMessage。灵活但未必充分使用文档。
- query 改写不是选最好的 query:多 query 各自检索,合并的是文档。
- rerank = 重排序:向量检索排过一次,cross-encoder 用更准的模型再排一次。
- bi-encoder vs cross-encoder:前者分别编码比向量(快,初筛),后者一起编码出分数(准,精排)。
- 完整 RAG 用三个模型:embedding + rerank + LLM,职责不同不能合并。
- 性能与精度平衡:先用快的 bi-encoder 筛掉绝大多数(海量→N),再用准的 cross-encoder 精排少数(N→k)。
附:简化版对照
学习项目通常是简化版 RAG(Agentic RAG,无改写无 rerank):
简化版: embedding → 向量检索 top-k → (ToolMessage) → LLM
完整版: query改写 → 多路检索 → 合并 → rerank → (拼prompt) → LLM
简化版省了:多 query 改写、多路检索、rerank 精排。代价是检索精度有限。理解了完整版,就知道简化版省了什么、为什么、代价是什么。
评论区