RAG 完整学习笔记:基础原理、检索公式与工程流程
- 0. RAG 总体原理与完整流程
- 1. 数据加载与解析(Data Loading)
- 2. 数据清洗与文本切块(Chunking)
- 2.1 数据清洗
- 3. Embedding 模型
- 4. 向量数据库与 ANN 索引
- 5. 检索系统(Retrieval)
- 5.1 检索的目标
- 5.2 倒排索引和关键词检索
- 5.3 BM25 关键词检索 ✅
- 5.4 Dense Vector Retrieval ✅
- 5.5 Sparse Neural Retrieval
- 5.6 混合检索(Hybrid Search)✅
- 5.7 Metadata Filtering ✅
- 5.8 Query Rewrite 与 Query Expansion
- 5.9 高级检索策略(对应 HelloAgents 8.3.5)
- 5.10 Parent-Child Retrieval 与邻居扩展
- 5.11 Multi-Vector / ColBERT Retrieval
- 5.12 Graph Retrieval / GraphRAG
- 5.13 SQL / Text-to-SQL Retrieval
- 5.14 多模态检索
- 6. 重排(Rerank)
- 7. 上下文组装与生成
- 8. RAG 评估
- 8.1 评估集
- 9. 推荐的第一版 RAG 方案
0. RAG 总体原理与完整流程
RAG(Retrieval-Augmented Generation,检索增强生成)的核心思想是:不要只依赖大模型参数中的旧知识,而是在回答前从外部知识库中取回证据,再让 LLM 基于证据回答。
RAG 主要解决:
- 企业私有资料无法直接进入通用大模型的问题。
- 知识经常更新,重新训练模型成本太高的问题。
- 大模型可能编造事实,答案缺少来源的问题。
- 不同用户只能读取自己有权限资料的问题。
0.1 离线知识库构建流程
数据源
→ 数据加载与解析
→ 数据清洗
→ 结构识别
→ 文本切块
→ 添加 Metadata 和权限
→ Embedding 向量化
→ 写入向量数据库、全文索引或其他数据库
→ 建立 ANN 索引
→ 质量检查和版本发布
离线阶段的目标是:把原始文件变成可以被快速检索、可以追踪来源、可以进行权限过滤的 Chunk。
0.2 在线问答流程
用户问题
→ 查询理解与改写
→ 选择检索通道
→ 关键词、向量、图或 SQL 检索
→ 合并和去重候选结果
→ Rerank 重排
→ 邻居扩展与上下文压缩
→ Prompt 组装
→ LLM 生成
→ 引用检查、事实检查与输出
在线阶段的目标是:在较短时间内找到足够正确的证据,并把证据放进适合 LLM 阅读的上下文。
0.3 RAG 系统的三个核心质量
- 召回率:正确资料有没有被找到。
- 排序质量:最相关资料有没有排在最前面。
- 生成忠实度:LLM 有没有只根据证据回答。
1. 数据加载与解析(Data Loading)
1.1 数据加载的作用
数据加载不是简单的“读取文字”。它需要把 PDF、Word、网页、表格、图片等转换成统一的 Document 结构,并尽量保留:
- 标题层级
- 段落顺序
- 页码和坐标
- 表格行列关系
- 图片和图注
- 来源 URL
- 创建时间、作者、版本和权限
如果解析阶段丢失结构,后面的切块、检索和引用都会受到影响。
1.2 不同输入数据的处理
1. PDF
PDF 分成两类:
- 文本型 PDF:内部已经保存文字,可以直接抽取。
- 扫描型 PDF:每页本质是图片,需要 OCR。
主要困难:
- 双栏阅读顺序错误。
- 页眉页脚反复出现。
- 表格被拆成无意义的文本。
- 图片中的信息无法通过普通文本提取获得。
2. Word、PPT 和 Markdown
应按标题、段落、列表、表格、文本框和幻灯片进行解析,不要把所有文本直接拼成一段。
3. HTML 和网页
处理流程:
网页下载
→ 去除导航栏、广告和页脚
→ 提取正文
→ 保留标题、链接、发布时间和 URL
→ 去除网页模板产生的重复内容
4. Excel、CSV 和关系数据库
小表可以转为带表头的文本;大表不适合把每一行都盲目向量化。
更好的方式:
- 表结构和字段解释进入知识库。
- 数值查询通过 SQL 或数据分析工具执行。
- 文字描述列可以单独建立向量索引。
5. 图片、音频和视频
图片可以使用 OCR、图像描述模型和视觉 Embedding。音频和视频通常先使用 ASR 转写,并保留时间戳和说话人信息。
1.3 常用解析工具
1. PyMuPDF4LLM ✅
主要用于把 PDF 转换成适合 LLM 和 RAG 的 Markdown 或结构化文本。
优点:
- PDF 处理速度较快。
- 能保留部分标题、表格和页面结构。
- 适合论文、技术文档和说明书。
缺点:复杂扫描件和特殊版面仍可能需要 OCR 或版面模型。
2. Unstructured ✅
通用文档解析框架,可以把 PDF、Word、PPT、HTML 等转换为 Title、NarrativeText、Table 等统一元素。
优点:多格式统一接入,适合企业知识库。
缺点:复杂文档的解析质量需要单独测试,依赖组件较多。
3. Docling
强调 PDF、Office、表格和版面结构转换,适合需要较强文档结构保留的场景。
4. OCR 工具
常见思路是:页面图片 → 文字检测 → 文字识别 → 版面排序 → 输出文本。
OCR 结果应保留置信度。低置信度页面可以进入人工检查队列,而不是直接写入知识库。
2. 数据清洗与文本切块(Chunking)
2.1 数据清洗
常见清洗步骤:
- 统一 Unicode、编码、换行和空格。
- 去掉重复页眉、页脚、导航和版权声明。
- 修复 PDF 断行、单词连字符和乱码。
- 内容哈希去除完全重复文档。
- 使用 MinHash、SimHash 或向量相似度识别近重复文档。
- 恢复表格表头、单位和行列关系。
- 识别并脱敏手机号、身份证、密钥等敏感信息。
- 保留原始文档 ID、版本、页码和处理日志。
2.2 为什么需要切块?
不能总是把整篇文档直接向量化,原因包括:
- 文档可能超过 Embedding 模型的输入长度。
- 一个长向量会混合多个主题,检索不精确。
- 检索到整篇文档会给 LLM 带来大量无关内容。
- LLM 上下文长度和 Token 成本有限。
切块需要平衡:
- Chunk 太小:检索精确,但信息不完整。
- Chunk 太大:上下文完整,但相似度被无关内容稀释。
2.3 常见切块方法
1. 结构感知切块 + 递归切块 ✅
先根据标题、章节、段落、列表、表格和代码函数边界切分。如果某一块仍然太长,再使用递归分隔符继续切。
过程:
文档
→ 按标题分章节
→ 按段落或元素分块
→ 超长块依次尝试双换行、换行、句号、空格、字符
→ 生成最终 Chunk
优点:保留语义和结构,同时实现简单稳定。
2. Parent-Child Chunking ✅
建立两种粒度:
- 小 Chunk 用于检索,定位更准确。
- 大的父 Chunk 用于送给 LLM,信息更完整。
过程:
父段落 P
→ 切成子块 c1、c2、c3
→ 只为子块建立向量
→ 命中 c2 后,根据 parent_id 取回 P
它解决“检索希望小、生成希望大”的冲突。
3. Sentence Window
以句子为索引单位,但命中后取回前后若干句。
例如命中第 10 句,最后返回第 8~12 句。适合事实分散在相邻句子的资料。
4. Semantic Chunking
先计算相邻句子的 Embedding。当相邻语义变化明显时切开。
一种简单定义:
change_i = 1 - cosine(e_i, e_(i+1))
当 change_i > threshold 时,在句子 i 和 i+1 之间切分
优点:主题完整。
缺点:需要额外计算 Embedding,阈值很敏感。
5. 固定 Token 切块
例如每 500 Token 切一块。速度最快,但可能把一句话、一个表格或一个知识点切断。
适合结构弱、格式统一的日志或简单文本,可作为基础基线。
2.4 Chunk Overlap(切块重叠)✅
Chunk Overlap 也叫 Sliding Window。它不是独立算法,而是让相邻块重复保留一段内容。
例如:
- Chunk Size = 500 Token
- Chunk Overlap = 100 Token
结果:
- Chunk 1:1~500
- Chunk 2:401~900
- Chunk 3:801~1300
步长公式:
stride = chunk_size - chunk_overlap
作用:
- 避免关键句刚好落在切块边界。
- 保留前后文联系。
- 提高边界知识被召回的概率。
缺点:
- 增加 Embedding、存储和索引成本。
- 多个召回结果可能高度重复。
常见起点:Overlap 设置为 Chunk Size 的 10%~20%,但应通过评估调整。
2.5 Chunk 应保存的 Metadata
每个 Chunk 建议保存:
- chunk_id
- document_id
- parent_id
- chunk_index
- title_path
- page_number
- source_url
- created_at 和 version
- tenant_id、department、ACL
- parser_version 和 embedding_model_version
3. Embedding 模型
3.1 Embedding 的基本原理
Embedding 模型把输入文本映射到 d 维向量空间:
E(text) -> e ∈ R^d
训练目标是让语义相关的 Query 和 Document 向量更接近,让无关内容更远。
例如正样本是问题 q 和正确文档 d+,负样本是无关文档 d-。一种对比学习目标可以写成:
L = -log[ exp(sim(q,d+)/τ) / Σ_j exp(sim(q,d_j)/τ) ]
其中:
- sim 是相似度函数。
- τ 是温度参数。
- 分母包含正样本和多个负样本。
模型通过大量这样的样本学习“什么内容应该相似”。
3.2 Bi-Encoder 编码结构
普通 Dense Embedding 通常使用 Bi-Encoder:Query 和 Document 分开编码。
q_vec = E_q(query)
d_vec = E_d(document)
score(q,d) = similarity(q_vec, d_vec)
文档向量可以提前计算和保存,因此能搜索大规模文档库。
优点:速度快、可以预计算。
缺点:Query 和 Document 在编码阶段没有逐 Token 交互,因此精细判断能力低于 Cross Encoder。
3.3 相似度公式
1. Cosine Similarity ✅
cos(q,d) = (q · d) / (||q|| × ||d||)
它主要比较方向,值越大越相似。文本向量检索中最常见。
2. Dot Product
dot(q,d) = q · d = Σ_i q_i d_i
当向量都做了 L2 归一化后,点积和余弦相似度等价。
3. Euclidean Distance
distance(q,d) = sqrt[ Σ_i (q_i - d_i)^2 ]
距离越小越相似。
必须按照模型训练说明选择距离函数,不能随意更换。
3.4 Embedding 的主要类型
1. Dense Embedding ✅
每个文本输出一个稠密向量。它擅长理解同义词、改写和整体语义。
弱点:对型号、数字、缩写、专有名词的精确匹配可能不稳定。
2. Sparse Embedding
输出高维稀疏向量,只有少量词或概念的权重非零。它保留更多关键词信号。
3. Dense + Sparse ✅
同时保存 Dense 和 Sparse 表示,分别检索后融合。适合既需要语义理解,又需要精确术语匹配的企业 RAG。
4. Multi-Vector / Late Interaction
不把整段文本压缩成一个向量,而是保留多个 Token 向量。以 ColBERT 的 MaxSim 为例:
Score(q,d) = Σ_i max_j (q_i · d_j)
意思是:对 Query 中的每个 Token,寻找 Document 中最匹配的 Token,再把这些最大匹配分数相加。
优点:比单向量表达更精细。
缺点:存储和计算成本更高。
5. Multimodal Embedding
将文本、图片等映射到可比较的空间,用于图文搜索、商品搜索和 PDF 图表检索。
3.5 常见模型与选择
- ✅ BGE-M3 / BGE 系列:中文和多语言能力好;BGE-M3 可以支持 Dense、Sparse 和 Multi-vector 思路,适合中文企业 RAG。
- ✅ Qwen Embedding 系列:中文、多语言和不同模型规模选择较丰富。
- OpenAI Embedding:API 使用简单、无需部署,适合快速开发,但需要考虑费用和隐私。
- Multilingual-E5 / E5:开源、多语言检索能力强;部分模型要求给 Query 和 Passage 添加指定前缀。
- Jina Embeddings:覆盖长文本、多语言和多模态方向。
- Cohere Embed:托管 API,区分 Query 和 Document 输入类型。
- Sentence-BERT:经典句向量生态,适合学习、轻量项目和自定义微调。
选择时要比较:
- 业务语言和专业领域。
- Recall@K、MRR、nDCG 等检索指标。
- 输入长度和向量维度。
- 单条和批量编码速度。
- GPU 显存、API 费用和隐私要求。
- 是否要求 Query/Passage 前缀。
- 是否支持 Dense、Sparse 或多向量。
4. 向量数据库与 ANN 索引
4.1 向量数据库保存什么?
通常同时保存:
- 文本 Chunk
- Dense 或 Sparse 向量
- document_id、chunk_id、parent_id
- 标题、页码、来源和时间
- 租户、用户、部门和权限字段
- 模型版本、数据版本和删除标记
查询过程:
Query
→ Embedding
→ Metadata 预过滤
→ ANN 向量搜索
→ Top-N 候选
→ Rerank
→ Top-K
4.2 为什么需要 ANN?
精确搜索需要把 Query 向量与所有 N 个文档向量逐一计算,复杂度大约是:
O(N × d)
当 N 很大时速度太慢。ANN(Approximate Nearest Neighbor)牺牲少量准确率,快速找到“近似最近”的向量。
4.3 常见索引
1. HNSW ✅
HNSW 把向量组织成多层邻接图:
- 上层节点少,用于快速跳到大致区域。
- 下层节点多,用于局部精细搜索。
查询从顶层入口开始,每层寻找更接近 Query 的邻居,再向下移动。
重要参数:
- M:每个节点保存的邻居数。越大,Recall 和内存通常越高。
- efConstruction:构建索引时搜索范围。
- efSearch:查询时候选范围。越大,Recall 更高但延迟更大。
优点:在线查询快、召回高。
缺点:内存占用较高,删除和大量更新需要注意维护。
2. Flat / Brute Force
与所有向量精确比较,没有近似误差。
优点:结果最准确,适合小数据和建立评估基线。
缺点:大数据时很慢。
3. IVF_FLAT
先使用聚类把向量分到多个中心区域。查询时只搜索最接近的若干区域。
重要参数:
- nlist:聚类中心数量。
- nprobe:查询时搜索多少个区域。
nprobe 越大,Recall 越高,速度越慢。
4. IVF_PQ
在 IVF 基础上,使用 Product Quantization 压缩向量。
优点:节省内存,适合海量向量。
缺点:量化会损失精度。
5. DiskANN 类索引
让大量向量保存在磁盘,只把关键结构放在内存中,适合内存放不下的超大规模数据。
4.4 Metadata Filter
例如:
tenant_id = 当前公司
AND department = finance
AND year >= 2025
AND access_level <= 当前用户权限
权限过滤应在检索阶段执行。不能先取回其他用户的私密文档,再在回答阶段删除。
4.5 常见数据库选择
- ✅ PostgreSQL + pgvector:关系数据、事务、权限和向量在同一个系统,适合已有 PostgreSQL 的中小型企业 RAG。
- ✅ Qdrant:开源、向量搜索和 Metadata Filter 能力强,API 清晰,适合中型到大型自建服务。
- ✅ Milvus:专业分布式向量数据库,索引选择多,适合海量向量和高吞吐。
- Elasticsearch / OpenSearch:全文检索、BM25、过滤和向量检索可放在同一搜索系统,适合混合检索。
- ChromaDB:简单、Python 友好,适合学习、Demo 和小型知识库。
- Pinecone:托管服务,减少运维,但需要考虑持续费用和厂商锁定。
- Weaviate:提供向量、过滤和多模态扩展,功能丰富但概念较多。
- FAISS:高性能向量搜索库,但不是完整数据库;权限、服务、备份、持久化和分布式需要自己实现。
选择数据库时要检查:
- 数据规模和向量维度。
- QPS、P95/P99 延迟。
- Metadata Filter 能力。
- 是否支持混合检索。
- 实时写入、更新和删除。
- 备份、恢复、高可用和多租户。
- 索引构建时间、内存和磁盘成本。
5. 检索系统(Retrieval)
5.1 检索的目标
检索阶段通常不是直接找最后 5 个结果,而是先从大量资料中快速找出 Top-N 候选,例如 Top-50 或 Top-100,再交给更慢的 Reranker。
检索需要平衡:
- Recall:不要漏掉正确文档。
- Precision:不要取回太多无关文档。
- Latency:用户不能等待太久。
- Cost:查询扩展和多路检索会增加计算。
5.2 倒排索引和关键词检索
倒排索引记录“每个词出现在哪些文档中”。
例如:
Python -> 文档1、文档3、文档8
RAG -> 文档2、文档3
查询包含 Python 时,不需要扫描所有文档,只读取 Python 的 Posting List。
TF-IDF 基础
TF 表示一个词在文档中出现的频率;IDF 表示这个词在整个集合中有多稀有。
TF-IDF(t,d) = TF(t,d) × IDF(t)
IDF(t) = log[N / df(t)]
常见词出现在很多文档中,IDF 较低;稀有词更有区分度。
5.3 BM25 关键词检索 ✅
BM25 在 TF-IDF 基础上增加:
- 词频饱和:同一个词出现 20 次,不应该比出现 10 次重要两倍。
- 文档长度归一化:长文档更容易包含关键词,需要进行惩罚。
公式:
BM25(q,d) = Σ_(t∈q) IDF(t) ×
[ f(t,d)(k1+1) /
( f(t,d) + k1(1-b+b×|d|/avgdl) ) ]
其中:
- f(t,d):词 t 在文档 d 中出现次数。
-
| d | :文档长度。 | | — | — |
- avgdl:平均文档长度。
- k1:控制词频饱和程度,常见起点约 1.2~2.0。
- b:控制长度归一化,常见起点约 0.75。
IDF 的一种形式:
IDF(t) = log[ (N - df(t) + 0.5) / (df(t) + 0.5) + 1 ]
BM25 流程:
Query 分词
→ 在倒排索引中找到包含查询词的文档
→ 计算每个词的 BM25 分数
→ 所有查询词分数求和
→ 按分数返回 Top-N
优点:
- 型号、姓名、数字、缩写和专业术语匹配强。
- 速度快、结果较容易解释。
缺点:
- 不理解同义词和改写。
- Query 与文档用词不同时可能漏召回。
5.4 Dense Vector Retrieval ✅
原理:分别用 Bi-Encoder 把 Query 和文档转换成向量,再搜索相似向量。
过程:
q_vec = E(query)
对每个候选文档计算 score(q,d)
通过 HNSW / IVF 等 ANN 索引返回 Top-N
常用分数:
score(q,d) = cosine(q_vec,d_vec)
优点:
- 能识别同义词、改写和隐含语义。
- 用户表述与文档用词不同也可能命中。
缺点:
- 对精确关键词、罕见实体和数字可能不稳定。
- 一个向量压缩整段文本,可能丢失细粒度信息。
5.5 Sparse Neural Retrieval
通过神经网络给词项生成稀疏权重,例如模型可能自动把“汽车”扩展到“车辆”等相关词。
它仍可以使用倒排索引,但比传统 BM25 具有更强的语义扩展能力。
优点:关键词信号强、可使用倒排索引。
缺点:索引可能更大,训练和部署更复杂。
5.6 混合检索(Hybrid Search)✅
混合检索并行执行:
- BM25 / Sparse:负责精确关键词。
- Dense Vector:负责整体语义。
过程:
Query
→ BM25 Top-N
→ Dense Top-N
→ 分数融合或排名融合
→ 去重
→ 候选池
→ Rerank
方法 1:加权分数融合
先把不同检索器的分数归一化:
normalized_score = (score - min) / (max - min)
再加权:
final_score(d) = α × dense_score(d) + (1-α) × bm25_score(d)
问题:不同通道的分数分布可能不稳定,归一化方式会影响结果。
方法 2:RRF 排名融合 ✅
RRF 不直接比较原始分数,只看每个结果在各检索器中的排名:
RRF(d) = Σ_r 1 / (k0 + rank_r(d))
其中 rank_r(d) 是文档 d 在检索器 r 中的排名,k0 是平滑常数。
优点:不需要把 BM25 和向量分数校准到同一范围,通常比较稳健。
5.7 Metadata Filtering ✅
Metadata Filter 根据确定条件缩小搜索范围,例如时间、文档类型、部门和权限。
过程:
用户问题
→ 从用户身份和问题中提取过滤条件
→ 在满足条件的子集合中执行 BM25 或向量检索
注意:
- Filter 太严格会漏掉正确文档。
- Filter 太宽会增加无关候选。
- 日期和权限等确定条件适合硬过滤;不确定主题适合软排序。
5.8 Query Rewrite 与 Query Expansion
1. 查询清洗
修正拼写、统一简称、去掉无意义语气词、恢复上下文中的代词。
例如:
“那它多少钱”
→ 根据对话历史改写为
“iPhone 16 Pro 当前价格是多少”
2. 查询分解
复杂问题可以拆成多个子问题。
例如:
“比较 A 和 B 的价格、性能和续航”
→ A 的价格、性能、续航
→ B 的价格、性能、续航
→ 汇总比较
适合多跳问题,但会增加检索和生成次数。
5.9 高级检索策略(对应 HelloAgents 8.3.5)
5.9.1 多查询扩展(MQE)✅
MQE(Multi-Query Expansion)的核心是:同一个问题可以有多种表达,不同表达可能命中不同文档。
例如原始 Query:
“如何学习 Python”
LLM 扩展为:
- Python 入门教程
- Python 学习方法
- Python 编程指南
完整过程:
- 把原始 Query 交给 LLM。
- LLM 生成 n 个语义等价或互补的查询。
- 原始查询和扩展查询分别执行检索。
- 合并所有候选文档。
- 根据 document_id 或 chunk_id 去重。
- 使用最大分数、加权分数、RRF 或 Reranker 排序。
扩展集合:
Q_expanded = { q, q1, q2, ..., qn }
候选集合:
C = union[ Retrieve(q_i, per_k) ]
简单最大分数融合:
Score(d) = max_i score(q_i,d)
也可以使用 RRF:
Score(d) = Σ_i 1 / (k0 + rank_i(d))
优势:
- 处理用户和文档用词不同的问题。
- 对模糊查询、简称和专业术语有效。
- 提升 Recall。
风险:
- LLM 可能生成偏离原意的扩展查询,引入噪声。
- 检索次数增加,延迟和成本上升。
- 扩展过多会让候选池失控。
控制方法:限制扩展数量、保留原始 Query、设置主题一致性检查,并在最后 Rerank。
5.9.2 假设文档嵌入(HyDE)✅
HyDE(Hypothetical Document Embeddings)的核心是“用可能的答案去找真实答案”。
传统 Dense Retrieval 使用问题向量搜索文档:
E(question) -> search documents
但问题通常是疑问句,知识库通常是陈述句,它们在向量空间中可能存在表达差异。
HyDE 过程:
- LLM 根据 Query 生成一段假设性答案 h。
- 不把 h 当成最终事实,只把它当作检索查询文档。
- 对 h 计算 Embedding。
- 使用 h 的向量搜索真实文档。
- 真实文档经过 Rerank 后交给最终 LLM。
公式:
h = LLM_generate(query)
h_vec = E(h)
Score(d) = cosine(h_vec, E(d))
如果生成多个假设文档,可以取平均:
hyde_vec = (1/m) × Σ_j E(h_j)
为什么有效:
- 假设答案和真实文档都是陈述式文本,语体更接近。
- 假设答案可能包含正确的领域术语和相关概念。
- 即使具体事实有错误,语义方向仍可能帮助找到正确文档。
风险:
- LLM 生成严重错误或错误领域内容时,会把检索引向错误方向。
- 增加一次生成调用,延迟和费用较高。
- 简单精确关键词查询不一定需要 HyDE。
适合:专业领域、概念解释、问题与文档表达差异较大的 Query。
5.9.3 统一扩展检索框架 ✅
统一框架把原始 Query、MQE 和 HyDE 放进同一个“扩展—检索—合并”流程。
第一步:构建扩展列表
expansions = [original_query]
如果 enable_mqe:加入 q1...qn
如果 enable_hyde:加入 hypothetical_document
去除空值和重复扩展
第二步:扩大候选池
pool_size = max(top_k × candidate_pool_multiplier, 20)
per_query = max(1, pool_size / number_of_expansions)
例如 top_k=8,candidate_pool_multiplier=4,则总候选池至少约 32。
第三步:分别检索
对每个扩展文本 e:
e_vec = Embedding(e)
hits_e = VectorSearch(e_vec, limit=per_query, filter=where)
第四步:Metadata 和权限过滤
例如只检索:
memory_type = rag_chunk
is_rag_data = true
data_source = rag_pipeline
rag_namespace = 当前知识库
第五步:去重与合并
使用 memory_id、document_id 或 chunk_id 作为唯一键。同一文档被多条扩展命中时,可以:
- 保留最高分。
- 累加不同查询的命中信号。
- 使用 RRF。
第六步:排序并返回 Top-K
merged = deduplicate(all_hits)
merged = sort_by_score(merged)
return merged[:top_k]
推荐:
- 一般自然语言查询:原始检索 + MQE。
- 专业领域、语义鸿沟明显:MQE + HyDE。
- 延迟敏感:只使用基础混合检索,或只生成少量 MQE。
5.10 Parent-Child Retrieval 与邻居扩展
小 Chunk 命中后,取回父段落、同标题内容或相邻 Chunk。
过程:
检索子块 c_i
→ 根据 parent_id 取回父块 P
→ 根据 chunk_index 取回 c_(i-1)、c_i、c_(i+1)
→ 去重并限制总 Token
它不是提高初始召回,而是提高最后上下文的完整性。
5.11 Multi-Vector / ColBERT Retrieval
Query 和 Document 都保留 Token 级向量,使用 MaxSim 进行局部匹配:
Score(q,d) = Σ_i max_j(q_i · d_j)
它比单一 Dense 向量更能识别“Query 中每个词分别对应文档中的哪部分”,但索引更大、检索更复杂。
5.12 Graph Retrieval / GraphRAG
适合关系型、多跳问题,例如:
“A 公司投资了哪些公司,这些公司的创始人毕业于哪里?”
过程:
实体识别
→ 实体链接
→ 在图中搜索邻居、路径或子图
→ 取回节点和边的来源文本
→ LLM 汇总
图检索没有一个统一的固定公式。常见路径分数可以综合边权、节点相关性和路径长度,例如:
Score(path) = Σ edge_weight + Σ node_relevance - λ × path_length
优点:关系清晰、多跳推理强。
缺点:建图和实体消歧成本高,图谱不完整会漏信息。
5.13 SQL / Text-to-SQL Retrieval
适合精确数值和结构化条件问题,例如:
“2026 年销售额最高的三个地区是什么?”
过程:
用户问题
→ 检索数据库 Schema 和字段解释
→ LLM 生成只读 SQL
→ SQL 安全校验
→ 数据库执行
→ 返回结果和来源
这里不是根据文本相似度直接得到答案,因此没有统一相似度公式。关键是 Schema Linking、SQL 正确性、权限和只读限制。
5.14 多模态检索
文本 Query 可以检索图片,图片也可以检索文本。基本过程是使用多模态 Embedding 得到可比较的向量,再通过向量相似度搜索。
6. 重排(Rerank)
6.1 为什么检索后还要重排?
第一阶段检索追求速度和 Recall,会返回较多候选。Rerank 使用更强但更慢的模型,对候选进行精细判断。
典型两阶段结构:
大规模知识库
→ BM25 / Dense / Hybrid 取 Top-50 或 Top-100
→ Reranker 重新计算相关性
→ 返回 Top-5 或 Top-10
6.2 Bi-Encoder 和 Cross Encoder 的区别
Bi-Encoder
Query 和 Document 分开编码:
q_vec = E_q(q)
d_vec = E_d(d)
score = cosine(q_vec,d_vec)
文档向量可以预计算,所以适合搜索全库。
Cross Encoder ✅
把 Query 和一个候选 Document 拼成一个序列,一起输入 Transformer:
[CLS] Query [SEP] Document [SEP]
内部过程:
- Query Token 和 Document Token 进入同一个 Transformer。
- Self-Attention 允许每个 Query Token 直接关注每个 Document Token。
- 模型获得细粒度交互信息,例如否定词、数字、实体关系和条件是否对应。
- 取 [CLS] 隐状态或分类头输出一个相关性分数。
简化公式:
h_cls = Transformer([CLS], q, [SEP], d, [SEP])
score(q,d) = W · h_cls + b
如果需要 0~1 概率:
p(relevant|q,d) = sigmoid(score(q,d))
为什么更准确:Query 与 Document 在编码过程中进行了完整 Token 交互,而不是先压缩成两个独立向量。
为什么不能直接搜索全库:每一个 Query-Document 对都要重新运行一次 Transformer。假设有 100 万个文档,就要进行 100 万次成对推理,成本无法接受。因此 Cross Encoder 只处理第一阶段取回的小候选集。
6.3 Cross Encoder 的训练方式
1. Pointwise
每个 Query-Document 对有标签 y,相关为 1,不相关为 0。
L = -[ y log(p) + (1-y) log(1-p) ]
2. Pairwise
希望正确文档分数高于错误文档:
L = -log sigmoid( score(q,d+) - score(q,d-) )
3. Listwise
同时考虑一组候选的整体排序,训练目标更接近最终排名,但实现更复杂。
6.4 Cross Encoder 重排过程
- Retriever 返回候选 d1…dN。
- 构造 N 个输入对
(q, d_i)。 - Reranker 分批推理,得到 s1…sN。
- 按分数降序排序。
- 应用 score_threshold 和 Top-K。
- 把最终 Chunk 交给上下文组装。
优点:准确率高,能识别细节和否定关系。
缺点:延迟高,候选越多越慢;长文档可能超过模型输入长度。
6.5 LLM Rerank
让 LLM 阅读 Query 和候选文档,输出相关性评分或排序。
优点:能够理解复杂要求和多条件问题。
缺点:成本高、速度慢、输出稳定性较弱。
适合少量高价值候选,不适合作为大规模第一阶段检索。
6.6 MMR 多样性重排
普通排序可能返回很多内容相似的 Chunk。MMR(Maximal Marginal Relevance)同时考虑相关性和多样性。
MMR(d) = λ × sim(q,d)
- (1-λ) × max_(d'∈Selected) sim(d,d')
第一项奖励与 Query 相关,第二项惩罚与已选结果重复。
λ 越大越重视相关性,越小越重视多样性。
6.7 Rerank 的推荐顺序
- ✅ Cross Encoder:通用效果最好,生产常用。
- ColBERT / Late Interaction:速度和精度之间的折中。
- LLM Rerank:复杂高价值任务。
- 规则排序:时间、新旧版本、权威来源等业务规则作为补充。
7. 上下文组装与生成
7.1 候选去重
去除:
- 相同 chunk_id。
- Overlap 产生的高重复 Chunk。
- 同一内容的不同文件版本。
可以使用文本哈希、Jaccard、MinHash 或向量相似度识别重复。
7.2 邻居扩展和父块回填
命中小块后,可以补充:
- 前后 Chunk。
- 父章节。
- 表格标题和表头。
- 图片图注。
但要控制 Token,防止上下文越来越大。
7.3 Context Compression
目标是从召回 Chunk 中保留与问题有关的句子。
过程:
候选 Chunk
→ 句子切分
→ 根据 Query 进行相关性判断
→ 删除无关句子
→ 保留来源信息
风险:压缩模型可能删除限定条件,因此高风险场景应保留原文引用。
7.4 上下文排序
常见顺序:
- 最相关和最权威证据放前面。
- 相关证据可以按文档顺序排列。
- 避免最重要证据全部放在很长上下文中间。
- 冲突资料要同时保留,并标出时间和来源。
7.5 Prompt 结构
System:角色、回答边界和安全规则
Instructions:只能基于 Context;证据不足时说明不知道
Context:带编号的检索结果和来源
Question:用户问题
Output Format:答案格式、引用格式
生成要求:
- 每个关键事实给出来源。
- 不使用证据中不存在的数字和结论。
- 资料冲突时明确说明冲突。
- 没有足够证据时拒绝猜测。
8. RAG 评估
8.1 评估集
至少需要:
- 用户问题。
- 正确文档或正确 Chunk。
- 参考答案。
- 问题类型和难度。
- 权限、时间和来源要求。
评估集应包含:简单事实、同义改写、精确关键词、多跳、时间敏感、无答案和对抗问题。
8.2 检索指标
1. Hit@K
Top-K 中只要出现一个正确文档就记为 1。
Hit@K = 1 if TopK contains relevant document else 0
2. Recall@K ✅
Recall@K = TopK 中正确文档数量 / 全部正确文档数量
适合检查是否漏召回。
3. Precision@K
Precision@K = TopK 中正确文档数量 / K
适合检查候选中无关内容是否过多。
4. MRR
只关注第一个正确结果的位置:
RR = 1 / rank_of_first_relevant
MRR = 所有 Query 的 RR 平均值
正确文档排第 1 得 1,排第 5 得 0.2。
5. MAP
对每个正确文档出现的位置计算 Precision,再取平均,适合一个 Query 有多个正确结果。
6. nDCG@K
同时考虑相关性等级和排名位置:
DCG@K = Σ_i [ (2^rel_i - 1) / log2(i+1) ]
nDCG@K = DCG@K / IDCG@K
高相关文档排得越靠前,分数越高。
8.3 Rerank 评估
比较 Rerank 前后:
- MRR 是否提升。
- nDCG 是否提升。
- 正确文档是否进入最终 Top-K。
- 增加了多少延迟和成本。
不要只看准确率,还要计算每提升一点指标需要多少额外延迟。
8.4 生成评估
1. Faithfulness(忠实度)✅
答案中的结论是否能被 Context 支持。
2. Answer Relevance
答案是否真正回答用户问题,而不是只复述文档。
3. Answer Correctness
与人工参考答案相比是否正确。
4. Context Precision
提供给 LLM 的 Context 中,有多少内容真正相关。
5. Context Recall
回答问题所需要的证据是否都被检索到。
6. Citation Precision / Recall
引用是否真的支持对应结论,以及重要结论是否都有引用。
8.5 系统指标
- P50、P95、P99 延迟。
- Embedding、检索、Rerank 和 LLM 各阶段耗时。
- 每次请求 Token 和费用。
- 缓存命中率。
- QPS、错误率和超时率。
- 索引大小、内存和磁盘。
- 用户满意度和人工抽检通过率。
常用工具包括 RAGAS、LangSmith、Arize Phoenix 和自建评估脚本,但工具不能替代真实业务测试集。
9. 推荐的第一版 RAG 方案
- ✅ 使用格式专用解析器,保存页码、标题和来源。
- ✅ 结构感知切块 + Recursive Splitter,设置 10%~20% Overlap。
- ✅ 小块检索 + Parent-Child 回填。
- ✅ 选择支持业务语言的 Embedding,并建立私有评估集。
- ✅ PostgreSQL + pgvector、Qdrant 或 Elasticsearch,根据现有系统选择。
- ✅ BM25 + Dense Hybrid Retrieval,优先使用 RRF 融合。
- ✅ 初始取 Top-50 左右候选,再使用 Cross Encoder 重排到 Top-5~10。
- 对用词差异明显的问题启用 MQE;专业概念问题可增加 HyDE。
- 对最终 Context 去重、扩展邻居、压缩并保留引用。
- 分开评估 Retrieval、Rerank、Generation、延迟和成本。