--- name: rag-pipeline description: 构建或调优 RAG 检索管道时使用——涵盖文档分块策略与参数、稠密嵌入(BGE-M3、余弦相似度、ANNOY/HNSW 选型)、稀疏嵌入(TF-IDF 到 BM25)、混合检索三阶段(并行召回、RRF 融合、跨编码器重排序)与 recall@k/MRR/nDCG 指标,含稀疏 vs 稠密的胜负判断。 --- # RAG 检索管道 ## 何时使用 - 从零搭建知识库检索(分块 → 索引 → 检索 → 注入生成) - 选型检索路线:稠密 vs 稀疏 vs 混合;ANN 索引 ANNOY vs HNSW - 检索质量不达标时定位调优(分块大小、融合方式、是否加重排序) - 建立检索质量的度量与验收标准 - 判断某类查询为何召回失败(语义相似 vs 精确匹配) - 为记忆系统或知识组织层提供底层检索能力 ## 核心原则 - RAG 核心流程只有三步:**检索相关片段 → 注入上下文 → LLM 基于上下文生成**。检索器决定答案上限,生成器决定兑现程度;答不好先在检索层定位,别急着调 prompt。 - RAG 的存在理由:模型训练数据有截止日期,知识库可随时更新——用外部知识库的广度和时效性补模型之短。 - 系统构成就两块:**检索器**(从知识库找相关片段)+ **生成器**(通常是 LLM,拿片段作上下文生成答案)。调优对象主要是检索器。 - 分块必不可少,原因有二:嵌入模型有输入长度限制,整篇文档压成一个向量会多主题混杂、语义被稀释;检索的目标是只注入相关部分,块太大会连带引入无关内容、浪费窗口稀释注意力。 - 稠密懂语义、稀疏懂字面,各有盲区:搜"HTTP-403",稀疏精确命中而稠密可能返回泛泛的"服务器错误";搜"kitty",稠密能找到只写"cat"的文档而稀疏不能。没有单一策略在所有场景可靠——生产级默认混合检索。 - 分块有固有缺陷:任何切分策略都会切断块与原始上下文的联系("该公司收入增长了 3%"——哪家公司?哪个季度?)。要么在索引期为每块生成上下文前缀再索引(`book/chapter3.md`「RAG 技巧:上下文感知检索」),要么靠重排序补救。 - 混合检索三阶段各司其职、层层递进:**并行检索**(双路各自召回)→ **结果融合**(统一候选池)→ **神经重排序**(候选池精排)。融合不替代重排,重排也不替代融合。 - 两路得分不可直接比较:余弦相似度(0-1)与 BM25(0 到几十)量纲分布迥异,融合必须抛开原始得分。 - 稀疏 vs 稠密的胜负判断(按查询类型选路或混路): | 查询类型 | 稠密 | 稀疏 | 结论 | |------|------|------|------| | 同义/换说法(kitty vs cat) | 胜 | 负 | 靠稠密 | | 精确代码/编号(HTTP-403) | 易泛化 | 胜 | 靠稀疏 | | 技术术语、人名 | 一般 | 胜 | 靠稀疏 | | 多语言/跨语言查询 | 胜 | 负 | 靠稠密 | | 概念性、描述性问题 | 胜 | 一般 | 靠稠密 | 混合检索 + 重排序是覆盖全部类型的生产默认。 ## 实践模式 - 分块策略选择: - **固定大小切分**:按固定 token 数(如 512)切分,相邻块重叠 50-100 token 防关键句子被拦腰截断。简单可预测,但完全无视文档结构——段落、代码、表格都可能被截断,仅作基线。 - **递归/结构感知切分**:按章节标题→段落→句子的自然边界递归降级,Markdown/HTML 首选,生产系统最常用的默认选择。 - **语义切分**:计算相邻句子嵌入相似度,在语义"断崖"处下刀,块内主题尽量单一。质量更高,代价是额外嵌入计算。 - 参数起点:每块 **256-1024 token、重叠 10%-20%**,再按检索质量实测调优。 - 块大小的权衡没有免费午餐:太小则单块信息不完整、脱离上下文语义模糊;太大则多主题混杂、嵌入被稀释、命中后带入无关内容。 - 稠密路线:用 BGE-M3 等上下文感知模型(768 维以上;Word2Vec 类静态词向量无法处理一词多义,BERT 类早期模型 512 token 上限不适合长文本)。余弦相似度比的是向量**方向**(语义)而非长度,内容相同长度不同的文档能正确判相似。 - 嵌入空间可做语义运算:"国王" - "男性" + "女性" ≈ "女王"——语义关系以线性可计算的方式编码在向量空间中;同一词在不同语境获得不同向量("苹果公司" vs "两斤苹果"),实现从词汇级到语境级的飞跃。 - ANN 索引选型: - **ANNOY(树)**:构建快、内存低,但不支持增量更新(需完全重建)——适合不常变的静态数据集。 - **HNSW(图)**:支持增量插入、查询精度极高,但构建较慢、内存较高,长期增量后建议定期重建保精度——适合需实时索引新信息的动态场景。 - 索引策略与嵌入模型同等重要,直接决定性能、成本和可维护性。 - 稀疏路线:TF-IDF 的核心直觉是"词在当前文档出现越多、在语料中越少见,越重要"(IDF = ln(N/DF)),但原始词频线性增长且不校文档长度。BM25 的修正:k1 控制**词频饱和**(重复出现的边际贡献递减),b 控制**长度归一化**(常用 k1=1.5、b=0.75);IDF 换成 ln((N-DF+0.5)/(DF+0.5)),注意 DF > N/2 时取值为负,实现需设下限。倒排索引是"词→文档"的反向映射,如同书末术语索引页。 - 分词是稀疏路线的前置质量项:数字(404、3.14、2.0.1)、代码(XK9-2B4-7Q1、API_KEY_123)、技术术语(C++、.NET、Node.js)、混合大小写(JavaScript、PyTorch)、邮箱、十六进制(#FF5733、0x1234)、缩写(API、HTTP)都要能正确切出,并去停用词。 - 融合与重排: - **RRF(倒数排名融合)**:得分 = Σ 1/(k+rank),k 常取 60 平滑头部差距。完全抛开原始得分、只看排名,简单稳健,但丢失得分中的相关性信号。 - **神经重排序**:对融合候选池前约 50 个用跨编码器(查询与文档拼接成一段文字逐词交互)精排,精度远高于双编码器(查询和文档各自编码再算向量相似度)的初筛。比喻:双编码器是猎头快速筛简历,跨编码器是面试官深谈。可用 bge-reranker-v2-m3。 - 双编码器 vs 跨编码器是速度/精度的结构性取舍:初筛要覆盖海量数据必须快,精排只处理少量候选才舍得慢。 - 度量与验收(均在带标注答案的测试查询集上计算): - **recall@k**:本书口径实为命中率(前 k 个结果里有任一篇相关即算命中);学术标准口径是相关文档召回比例,跨来源比较时需注意定义差异。这是最贴近 RAG 需求的指标——相关文档进入上下文,LLM 就有机会利用它。 - **MRR**:每个查询取第一个相关文档排名的倒数再平均——排第 1 得 1 分,排第 10 只得 0.1 分,回答"找得够不够靠前"。 - **nDCG**:综合考虑所有相关文档的排名与相关程度,回答"整个排序列表质量如何"。 - **检索失败率**:正确信息未出现在 top-20 结果中的查询比例。 - 分阶段验收:流水线每一级(稠密、稀疏、融合、重排)都单独报告指标,才能定位是哪一级丢的召回。 ## 常见陷阱 - 不度量检索直接调生成 prompt;recall@k 不过关时后续全是空中楼阁。 - 块太小(脱离上下文后语义模糊)或太大(多主题稀释、命中后带入无关内容)。 - 把 RRF 和重排序二选一——重排序不是为了"补救 RRF 丢掉的得分"而存在,它换用了更强的匹配范式,无论前步如何融合都值得加。 - 只上稠密或只上稀疏,遇到对偶的失败模式(语义查询 vs 精确代码/编号/人名/多语言)才补救。 - 用稠密得分与 BM25 得分直接加权求和(量纲不同);或在 ANNOY 上做频繁增量更新导致反复全量重建。 - 分块后不检查块与原始文档的上下文脱钩问题,孤立块嵌入后语义严重损失。 - 拿一整篇文档只压一个向量:多主题混杂,向量无法精确表达任何一个主题。 - 只优化 recall 不看排序:相关文档排在第 50 名,进了候选池也进不了最终 top-k 上下文。 - 索引建成后从不重建:知识更新后向量库不同步,检索到的是过期内容。 ## 配套代码 - `chapter3/retrieval-pipeline/` — 稠密 + 稀疏 + RRF 融合 + BGE-Reranker 重排的完整流水线;test_client.py 按查询挑战类型(语义相似如 kitty/feline、精确名称、多语言、技术代码)分类对比两路胜负,可观察重排后的排名变化。 - `chapter3/dense-embedding/` — BGE-M3 稠密检索服务,ANNOY/HNSW 可切换;离线 CLI 计算 recall@k/precision@k/MRR,并对比 ANN 与暴力精确检索的 recall、构建时间、查询时延。 - `chapter3/sparse-embedding/` — 从零实现 BM25 + 倒排索引,分词覆盖数字、代码、技术术语、混合大小写;日志逐步展示分词、倒排命中、TF/IDF 计算与最终排序。 ## 深度阅读 - `book/chapter3.md`「RAG 基础:构建 Agent 的知识获取管道」