Skip to content
Ziwen
Go back

从召回到生成:一篇讲清 RAG 检索链路

目录

很多 RAG 系统的第一版只有三步:文档切块、向量检索、把结果交给大语言模型。这个方案可以运行,但效果往往不稳定:专有名词搜不到、相似内容大量重复、关键限制条件被切断,最终答案看似合理却缺乏证据。

RAG 由文档处理、候选召回、排序、上下文构建和答案生成等环节组成。Chunking 决定知识以什么粒度进入索引;BM25 和向量检索负责快速召回候选;近似最近邻索引控制大规模向量检索的延迟与召回损失;Hybrid Search 和 RRF 融合不同检索信号;Cross-Encoder 对候选进行精排;Parent-child Retrieval 恢复完整上下文;MMR 和 Context Compression 控制信息密度;分层评估负责定位错误发生在哪个阶段。

RAG 从文档切块、双路召回到精排和压缩生成的完整链路

图 1:RAG 是从文档处理到证据生成的多阶段链路。

整套系统遵循一个基本原则:召回阶段追求“不漏”,精排和上下文处理阶段追求“准确且紧凑”。

如果正确文档没有被召回,后面的 Reranker 和 LLM 都无法补救;如果召回很多但不做精排,LLM 又会被噪声和重复内容干扰。

Chunking:第一次检索决策

长文档不能直接作为一个检索单元。一篇产品手册可能同时讨论安装、计费、权限和故障排查。如果把整篇文档压缩成一个向量,多个主题会混在一起,查询很难准确匹配其中某个细节。

Chunking 的作用是把文档切成较小、主题相对集中的单元。

最简单的方法是固定长度切分,例如每个 Chunk 约 500 Tokens,并让相邻 Chunk 保留一部分重叠。Overlap 可以降低句子或条件刚好被切断的概率,但也会制造重复内容,增加索引规模。

更稳妥的做法是优先尊重原始结构,例如 Markdown 标题、HTML 节点、PDF 章节、自然段落、代码函数和表格。工程上通常先按结构切分,再对过长章节进行二次切分。

Chunk 大小存在天然矛盾:

  • 小 Chunk 主题集中,检索更精确,但容易丢失限制条件和上下文。
  • 大 Chunk 信息完整,但包含更多噪声,Embedding 表示也更模糊。

这个问题很难通过一个固定的“最佳 Chunk 大小”彻底解决。更通用的做法,是把检索粒度与生成粒度分开。

Parent-child Retrieval:小块检索,大块返回

Parent-child Retrieval 把文档组织成两层:Parent 保存完整章节,Child 保存较小段落。系统为 Child 建立向量索引,并通过 parent_id 记录它属于哪个 Parent。

在线查询时,系统先检索语义集中的 Child,再返回对应 Parent 或命中位置附近的窗口。这种设计也称为 Small-to-Big Retrieval:用小文本提高检索精度,用大文本恢复回答所需上下文。

假设退款政策包含三句话:

购买后 14 天内可以申请退款。已经消耗的 API 额度需要扣除。年度合同企业客户不适用该政策。

查询“年度合同企业客户能否在 14 天内退款”时,小 Chunk 可能只命中第一句话。直接把它交给 LLM,会遗漏第三句中的例外条件。Parent-child Retrieval 可以恢复整个政策段落,让模型看到完整约束。

Parent 也不能无限扩大。过大的 Parent 会重新引入噪声,并快速消耗上下文窗口。实际系统还要解决多个 Child 指向同一 Parent 时的去重、分数聚合和权限继承。

BM25 与向量检索:关键词与语义互补

BM25 和向量检索负责从大规模文档中快速召回候选,但二者使用的信号不同。

BM25 基于词语匹配。它综合考虑查询词是否出现在文档中、词语出现次数、词语在整个语料库中的稀有程度以及文档长度。

它擅长处理错误码、API 名称、产品型号、人名、法律条款和订单号。例如查询 ERR_CONNECTION_RESET 时,精确关键词通常比抽象语义更重要。

BM25 的弱点是同义表达。用户查询“如何退货”,文档只写“商品退回流程”,两者没有明显的词语重合。

向量检索使用 Embedding 模型分别编码查询和文档:

q=Eq(query),d=Ed(document)q=E_q(\text{query}),\qquad d=E_d(\text{document})

系统再通过点积或余弦相似度寻找语义接近的文档:

score(q,d)=qd\operatorname{score}(q,d)=q\cdot d

向量检索可以识别“如何退货”和“商品退回流程”的语义关系,也适合自然语言问题和跨语言检索。它的弱点是精确匹配:编号、缩写和罕见专有名词可能被编码得不够稳定;语义相似也不意味着文档满足查询中的全部条件。

维度BM25向量检索
主要信号关键词重合语义相似
数据表示稀疏词项稠密向量
常用索引倒排索引HNSW、IVF 等向量索引
精确名称与编号可能较弱
同义表达较弱
是否需要语义模型

生产系统通常组合使用两者,同时覆盖精确关键词和语义表达。

近似最近邻搜索:用可控误差换取规模

向量已经编码完成,不代表系统可以低成本找到相似文档。Exact KNN 会把查询向量与全部 NNdd 维文档向量比较,单次查询的计算量约为 O(Nd)O(Nd)。数据规模增大后,向量扫描和内存带宽会成为主要瓶颈。

Approximate Nearest Neighbor(ANN)先通过索引缩小候选范围,再计算候选与查询的距离。这里的“近似”发生在候选搜索阶段:系统仍使用余弦相似度、点积或欧氏距离评分,但不保证访问真正的全部最近邻。

技术缩小候选的方式主要取舍常见参数
HNSW从稀疏上层进入图,再沿邻接边搜索局部候选高召回、低延迟,但图结构占用额外内存,建索引成本较高MefConstructionefSearch
IVF用聚类中心把向量分桶,查询时只搜索最接近的若干分区内存结构简单,适合大规模和批量查询,但需要训练聚类中心nlistnprobe
PQ把向量拆成子空间并量化为短编码,用近似距离筛选候选显著降低内存和带宽,但会引入量化误差子空间数量、每段编码位数

HNSW 和 IVF 主要决定“到哪里找”,PQ 主要决定“如何保存和近似计算向量”。它们不是互斥选项,可以组合成 IVF-PQ 或 HNSW-PQ。HNSW 的分层邻接图来自 原始论文;常见索引组合和参数可以参考 Faiss Index 文档

ANN 调参是在召回率、延迟和资源之间选择工作点。提高 efSearchnprobe,通常会检查更多候选,提高召回率,也增加查询延迟;增大 HNSW 的 M,通常提高图的连通性和召回率,同时增加内存与构建成本;压缩 PQ 编码可以节省内存,但量化误差会增大。

评估 ANN 时,应先在一组代表性查询上运行 Exact KNN,得到真实最近邻,再比较近似结果:

ANNRecall@K(q)=ANNK(q)ExactK(q)K\operatorname{ANNRecall@K}(q)= \frac{\left|\operatorname{ANN}_K(q)\cap\operatorname{Exact}_K(q)\right|}{K}

这个指标只测量索引造成的候选损失。RAG 的 Recall@K 检查标注相关文档是否进入结果,还包含 Chunking、Embedding 和查询表达等影响。两者需要分开测量,再结合 P95 延迟、吞吐量、索引内存、构建时间和更新成本选择参数。

元数据过滤也会改变 ANN 的实际效果。租户、权限、时间和文档类型等条件如果在召回后过滤,可能导致最终候选不足;如果在召回前过滤,不同引擎对图和分区的搜索效率也不同。因此,生产基准需要覆盖真实过滤比例,不能只测试无过滤查询。

Hybrid Search 与 RRF:融合两条召回路径

Hybrid Search 指同时使用多种检索方法。例如,BM25 和向量检索分别召回一批候选,再由融合算法生成统一排名。

直接把两种分数相加并不可靠。BM25 分数可能是 13.2,向量相似度可能是 0.84,它们没有天然统一的尺度。系统可以先归一化再加权,但结果会依赖归一化方法和权重。

RRF,即 Reciprocal Rank Fusion,绕过了分数尺度问题。它不比较原始分数,只根据文档在每个列表中的名次计算融合分数:

RRF(d)=i1k+ranki(d)\operatorname{RRF}(d)=\sum_i\frac{1}{k+\operatorname{rank}_i(d)}

如果某篇文档同时出现在 BM25 和向量检索的前列,它会获得较高的融合分数。

RRF 实现简单、对极端分数不敏感,很适合融合关键词与向量结果。但它会丢失原始分数差距:第一名略胜第二名和远胜第二名,在 RRF 中没有区别。如果各检索器的分数已经良好校准,学习型融合模型可能更合适。

Bi-Encoder 与 Cross-Encoder:召回和精排的分工

向量检索通常采用 Bi-Encoder。查询和文档分别编码,文档向量可以提前计算并建立索引。在线查询只需要编码一次 Query,再执行近似最近邻搜索,因此可以处理百万级甚至更大规模的语料。

代价是查询和文档在编码过程中没有直接交互。模型必须把整段文本压缩成一个固定长度向量,否定关系、时间条件和数字差异可能被弱化。

Cross-Encoder 则把查询和单个候选文档共同输入模型。查询 Token 可以直接与文档 Token 进行 Attention,因此模型更容易判断候选是否真正回答了问题。

但每一个查询—文档组合都需要单独推理,Cross-Encoder 无法直接扫描整个文档库。它通常只处理召回后的几十或几百个候选。

关键词召回和语义召回经过排名融合,再由精排模型筛选

图 2:BM25 与 Bi-Encoder 负责扩大召回覆盖,RRF 合并两路排名,Cross-Encoder 负责精细排序。

这形成了典型的多阶段检索:

  1. BM25 与 Bi-Encoder 从海量文档中快速召回候选。
  2. RRF 融合两路结果。
  3. Cross-Encoder 对较小候选集重新评分。
  4. 系统选出少量高质量证据交给 LLM。

Bi-Encoder 决定检索上限,Cross-Encoder 改善候选顺序。两者分别承担召回和精排职责,共同组成多阶段检索链路。

Pointwise、Pairwise 与 Listwise:三种排序形式

Pointwise、Pairwise 和 Listwise 定义排序的学习或推理方式,可以应用于不同模型。

Pointwise 独立判断每个候选的相关性。模型为每个查询—文档组合输出一个分数,系统按分数排序。Cross-Encoder Reranker 最常采用这种方式,因为它容易批量推理。

Pairwise 比较两个候选,学习哪个应该排在前面。它可以只用于训练:损失函数要求正样本分数高于负样本,在线推理时仍然逐个打分。也可以在推理阶段直接让 LLM 两两比较,但候选数量增加后,计算成本会迅速上升。

Listwise 同时观察一组候选并优化整体顺序。它能够考虑候选之间的相对关系,但受到上下文长度、输入顺序和推理稳定性的影响。LLM Reranker 通常需要通过分组或滑动窗口处理较长列表。

排序形式单次观察对象核心目标常见使用方式
Pointwise一个候选预测相关性Cross-Encoder 打分
Pairwise两个候选判断相对优先级排序训练、LLM 比较
Listwise一组候选优化整体顺序Learning to Rank、LLM 排序

这些形式可以和不同模型组合。例如,Cross-Encoder 可以采用 Pointwise 推理,也可以通过 Pairwise 损失训练;LLM 则可以逐个打分、两两比较或直接排列一组候选。

MMR:相关之外还要避免重复

Reranker 负责把相关内容排到前面,却不保证前几名彼此不同。如果知识库存在多个版本或重复页面,Top K 可能都在表达同一件事。

MMR,即 Maximal Marginal Relevance,在选择候选时同时考虑相关性和多样性:

MMR(d)=λSim(q,d)(1λ)maxsSSim(d,s)\operatorname{MMR}(d)= \lambda \operatorname{Sim}(q,d) - (1-\lambda)\max_{s\in S}\operatorname{Sim}(d,s)

第一项奖励文档与查询的相关性,第二项惩罚它与已选文档的重复程度。当 λ\lambda 接近 1 时,系统更强调相关性;降低 λ\lambda 会增加结果多样性。

MMR 适合需要覆盖多个方面的问题,例如同时检索退款条件、操作流程和例外情况。但对于需要多个独立来源交叉验证的任务,过度去重可能删除有价值的重复证据。

因此,MMR 通常位于 Rerank 之后:Reranker 先保证候选相关,再由 MMR 从高质量候选中选择互补内容。

Context Compression:提高上下文的信息密度

MMR 减少候选之间的重复,Context Compression 删除候选内部的无关内容。

假设检索到的文档同时包含公司历史、产品版本、试用期、退款方式和办公地点,而用户只问“企业版试用期如何退款”。上下文压缩器可以只保留试用期、退款条件和操作方式。

常见压缩方法有三种:

  • 过滤式:删除低相关 Chunk。
  • 抽取式:保留相关句子或原文片段。
  • 生成式:让 LLM 生成查询相关摘要。

抽取式压缩更容易审计和引用。生成式压缩率更高,但可能遗漏条件、改变原意,甚至产生原文不存在的信息。法律、医疗和财务等高可信场景应优先保留原文片段。

从小块命中、父块展开到去重补全和证据压缩

图 3:检索粒度、上下文范围、内容多样性和 Token 预算需要分别处理。

一条常见的上下文处理链路是:先检索和精排小 Child,再扩展对应 Parent;随后去重并用 MMR 选择互补证据;最后进行查询相关的抽取式压缩,并在 Token 预算内组织上下文。

RAG 评估:分开检查检索与生成

只评估最终答案无法定位问题。RAG 至少需要检查检索、上下文和生成三个层级。

检索层

检索层回答“正确证据有没有被找到”。

  • Recall@K:全部相关文档中,有多少进入 Top K。
  • Precision@K:Top K 中有多少真正相关。
  • MRR:第一个相关结果出现得多靠前。
  • NDCG:高相关结果是否排在更前面。

其中 Recall@K 决定召回上限。Reranker 只能重排已有候选,不能恢复未被召回的文档。

上下文层

上下文层评估 LLM 最终收到的内容。它经过 Parent 扩展、MMR、压缩和截断等处理,已经不同于数据库返回的原始候选:

  • 回答所需证据是否完整。
  • 无关内容和重复内容占比。
  • 是否覆盖问题的不同方面。
  • Parent 扩展是否引入过多噪声。
  • Context Compression 是否删除限制条件。
  • 引用能否定位回原文。
  • 有效证据消耗了多少 Tokens。

检索到了正确文档,不代表最终上下文一定正确。Parent 扩展、MMR、压缩和截断都会改变证据。

生成层

生成层回答“模型是否正确使用了上下文”。

  • Answer Correctness:答案是否正确。
  • Answer Relevance:是否回应了用户问题。
  • Faithfulness:答案中的事实是否受到上下文支持。
  • Citation Precision:引用是否支持对应结论。
  • Citation Recall:需要证据的结论是否都有引用。

例如,上下文只说“标准退款期为 14 天”,模型回答“所有客户都可以无条件退款”。答案表面相关,但“所有客户”和“无条件”没有证据支持,因此 Faithfulness 较低。

系统层

生产环境还要测量 P50、P95 和 P99 延迟、单次查询成本、文档更新延迟、无答案时的拒答能力、权限过滤、多语言稳定性和 Prompt Injection 防护。

用指标定位错误

现象更可能的问题
正确文档没有进入 Top KChunking、Embedding、BM25 或召回参数
正确文档召回了,但排名很低Hybrid 融合或 Reranker
Top K 内容高度重复去重或 MMR
找到相关段落,但缺少限制条件Parent-child Retrieval
上下文正确但 Token 浪费严重Context Compression
上下文包含答案,模型仍答错Prompt 或生成模型
答案看似合理但证据不支持Faithfulness 与引用机制
离线指标很好,线上体验较差测试集与真实查询分布不一致

一个基本测试样本至少应包含问题、参考答案、相关文档和回答必须覆盖的证据。有引用需求时,还应标注预期引用位置。

一套可落地的基线

对于中等规模的企业知识库,可以从以下配置开始:

  1. 按标题和段落进行结构化 Chunking。
  2. 保存 Parent,并把较小 Child 作为检索单元。
  3. 为 Child 向量建立 ANN 索引,并用 Exact KNN 样本校准召回损失。
  4. BM25 与 Bi-Encoder 分别召回候选。
  5. 使用 RRF 融合两路排名。
  6. 使用 Cross-Encoder 进行 Pointwise 精排。
  7. 根据 parent_id 恢复上下文并去重。
  8. 使用 MMR 控制重复。
  9. 使用抽取式压缩保留相关原文。
  10. 把最终证据和来源交给 LLM。
  11. 分别评估 ANN Recall、检索 Recall、上下文质量和 Faithfulness。

候选数量可以从“BM25 与向量检索各召回 50 条、RRF 融合后保留 50 条、Cross-Encoder 精排前 20 条、最终选择 5~8 条证据”开始调试。这些数字只是起点,最终配置需要根据语料规模、查询类型、模型速度和延迟预算确定。

结语

RAG 在有限的时间和上下文预算内,为模型提供正确、完整、低噪声且可追溯的证据。

Chunking 决定知识如何进入系统;ANN 索引决定向量候选如何在规模与误差间取舍;召回决定答案质量的上限;Rerank 与上下文处理决定证据质量;生成与评估决定系统能否可靠落地。

当 RAG 效果不稳定时,继续更换大模型未必是最有效的做法。先确认正确证据是否被切出来、召回来、排到前面并完整送入上下文,通常更容易找到真正的瓶颈。


Share this post:

Previous Post
Claude Code 的上下文压缩:从缓存编辑到完整总结
Next Post
动态工具加载:四种 Agent Harness 的协议与实现