生产环境 RAG 的七步流水线:PR 矛盾没被解决,只是被管理了

Precision-Recall 矛盾是检索式记忆系统的结构性问题。工业界的答案不是解决它,而是用七步流水线管理它。

在上一篇(《SOAR 的记忆蓝图》)里,我拆解了检索式记忆系统的 Precision-Recall 矛盾:

  • 提高召回率 → 必然引入噪声 → LLM 被无关信息干扰
  • 提高精度 → 降低召回率 → 遗漏相关信息
  • 加 Reranker/CRAG → 延迟和复杂度上升 → 本身也会失败

这不是"优化几个参数就能解决"的工程问题,这是高维空间的几何学、Transformer 的注意力机制、以及 LLM 的噪声敏感性共同决定的结构性约束

但有一个问题还没回答:如果这个矛盾没法解决,那在实际干活的人是怎么做的?

答案是:他们不解决矛盾,他们管理矛盾。

经过 2023-2026 年的快速迭代,工业界已经收敛到一个标准的"七步流水线"。每一步都在做不同的权衡,每一步都承认矛盾没有被消除。但这个管道叠加起来,把 PR 矛盾压到了一个可接受的范围。


第一步:查询变换(Query Transformation)

原始的用户查询很少适合直接检索。

用户说"帮我看看那个张三上次提到的项目进度",直接拿去向量检索,八成找不到对应的文档。需要先把查询转换成更适合检索的形式。

常用方案

方法做法效果
Multi-Query用 LLM 把一个问题改写为 3-5 个不同角度的检索词增加召回率,减少遗漏
HyDE (Gao, 2022)先生成一个"假设的理想文档",用它的嵌入去检索对需要精确匹配的场景效果好
Step-back Prompting先生成一个更抽象的宽泛问题,从宽到窄检索适合需要先理解上下文再找细节的场景
查询分解把复杂问题拆成多个子问题,分别检索适合多跳问题

这一步的权衡

查询变换提高了召回率(覆盖了查询的不同侧面),但代价是:

  • 可能偏离原始意图——改写后的查询抓住了相关的 false positive,漏掉了真正的目标
  • LLM 调用成本——每次查询变换都多了一次 LLM 调用
  • 延迟增加——Multi-Query 让一次检索变成 3-5 次

这是 PR 矛盾的第一层应对:用算力换召回率。


第二步:元数据预过滤(Metadata Pre-filtering)

纯向量检索在生产环境中已经没人用了。

你的知识库有各种属性——时间范围、文档来源、领域、权限级别。如果不在向量检索之前用这些属性做预过滤,你的向量搜索就会在一个巨大的、大部分不相关的空间里找相似度。

Pinecone、Weaviate、Qdrant 的生产指南里有一条共识:先过元数据,再过向量。

典型实现

查询: "2026年Q1的项目报告"
过滤: date >= 2026-01-01 AND date <= 2026-03-31
       AND type = "project_report"
向量检索: 在过滤后的子集里做 top-k

这一步的权衡

预过滤大幅提高精度(把搜索空间缩小到真正相关的子集),但代价是:

  • 如果过滤条件太激进,召回率会骤降——你过滤掉了你以为是无关、但实际上相关的内容
  • 预过滤 vs 后过滤的永恒争议:先过滤再向量搜索(丢失潜在匹配)vs 先向量搜索再过滤(浪费算力在不相关的结果上)
  • 两个过滤器之间的:元数据过滤掉的文档,向量搜索不会看到

这是 PR 矛盾的第二层应对:用先验知识减少搜索空间。


第三步:混合检索 + RRF(Hybrid Search + Reciprocal Rank Fusion)

纯稠密检索(Dense Retrieval)和纯稀疏检索(BM25)各有缺陷:

检索方式强项弱项
稠密检索(Embedding)语义匹配丢失精确关键词匹配,受嵌入质量影响
稀疏检索(BM25)精确关键词匹配无法处理同义、近义表达

工业界的答案是:两个都做,然后把结果融合。

Reciprocal Rank Fusion(RRF)

RRF 的公式极其简单:

score(d) = Σ 1 / (k + rank(d))

对每个文档,取它在两种检索方式中的排名,加起来算一个综合分数。k 是一个平滑常数,通常取 60。

这一步的权衡

混合检索在召回率上有明显的提升(5-15% 的 recall@10 改善),但代价是:

  • 两个索引都要维护——稠密索引 + 稀疏索引,存储翻倍
  • RRF 权重难调——k 值、稠密 vs 稀疏的权重比例,对不同的领域和查询类型有不同的最优值
  • 两个引擎都可能出问题——一个引擎的失败结果会通过 RRF 污染最终排名

这是 PR 矛盾的第三层应对:用多个正交的检索信号互相补充。


第四步:小 Chunk 检索(Small Chunk Retrieval)

这是整个流水线里最微妙的一步。

你面临一个根本性的矛盾:

Chunk 大小嵌入精度上下文完整性
小(~200 tokens)高——每段聚焦一个主题,嵌入向量干净低——切碎了语义边界
大(~1000 tokens)低——一段包含多个主题,嵌入向量的平均化稀释了语义高——保留了上下文

Anyscale 在 2023 年做了一个有影响力的实验:把 chunk_size 从 100 逐步增加到 900,观察检索分数和生成质量的变化。

结果是两条方向相反的曲线:

  • 检索分数单调递增——更大的 chunk 更容易被命中(里面有更多关键词)
  • 生成质量先升后降——在 300-500 tokens 达到峰值,之后更多的上下文开始引入噪声

工业界的共识

小 chunk 用于检索,大 chunk 用于阅读。

具体来说:嵌入和检索用 256-512 tokens 的小 chunk(保证精度),检索到之后把整个 parent document 或相邻 chunk 一起拉上来送给 LLM。

这被称为 “small-to-retrieve, big-to-read” 模式。

这一步的权衡

小 chunk 让检索精度大幅提升,但引入了一个新的复杂度:你需要额外维护 chunk ↔ parent 的映射关系,且 chunk 边界的切割永远会破坏一些语义边界。

这是 PR 矛盾的第四层应对:把"检索"和"阅读"解耦,让它们用不同的粒度。


第五步:上下文展开(Context Expansion)

第四步检索到的是小 chunk。但单独把小 chunk 扔给 LLM 是不够的——它只看到了切片,看不到上下文。

上下文展开的做法是:拿到命中的小 chunk 后,把它的邻居 chunk 或 parent 文档一起带上来。

实现方式

  • SentenceWindowRetrieval:命中一个句子,拉它前后 N 个句子作为上下文窗口
  • ParentDocumentRetriever:命中一个子 chunk,拉对应的父文档
  • Sliding Window:把 chunk 边界滑动一下,重新拼接上下文

这一步的权衡

展开把精度↔完整性的天平往回拉了拉——你放弃了部分精度,换回了上下文。但展开范围越大,你重新引入了多少噪声就越不可控:

chunk 精度: 高
展开后: 上下文完整了,但噪声回来了

这是 PR 矛盾的第五层应对:用"检索精度换阅读完整性".


第六步:重排序(Re-ranking)

前五步产出的结果是一堆"roughly 相关"的候选。它们之间的相关性差异很小,而向量相似度在这个阶段已经丧失了分辨力。

Reranker(通常是一个交叉编码器)把 query 和每个候选文档配对,算一个精确的相关性分数。

效果

方案精度提升
不加 Reranker基线
加 Cross-encoder Reranker+5-15pp nDCG(在 BEIR 基准上)

Cohere Rerank、BGE-Reranker 是生产中最常用的选择。

这一步的权衡

Reranker 是目前最有效的精度提升手段,但它有一个你不能忽视的限制:

它不能挽救没有被前面步骤命中到的文档。

Reranker 做的是"从候选里挑出最相关的",候选名单在上一步就已经定了。如果前面六步都没有命中那个真正相关的文档,Reranker 也帮不了你。

Reranker 提高精度,召回率天花板由前序步骤设定。

另外,交叉编码器比双编码器慢 2-5x。

这是 PR 矛盾的第六层应对:用更多的算力(交叉编码)换取精度,但承认召回率的天花板已经定了。


第七步:LLM 生成

最后一步,把前六步精挑细选出来的上下文送给 LLM,让它生成答案。

这一步本身也能做一些事情来管理 PR 矛盾:

  • 系统提示约束:明确告诉 LLM"如果检索到的内容不足以回答,请说不知道"
  • 引用锚定(Citation Anchoring):要求 LLM 在回答的每个关键事实后面注明来源 chunk
  • 置信度声明:如果模型的回答基于不充分的检索结果,要求它声明不确定性

这一步的权衡

这一步的约束越严格,精度越高(更少幻觉),但用户可能得到更多"我不知道"的回复——这是用用户满意度换精度

这是 PR 矛盾的第七层应对:用生成时的约束来兜底。


全景:七步的得失

把七步串起来,每一层的权衡一目了然:

步骤主要 tradeoff状态
查询变换召回率 ↑ / 意图偏离风险 ↑生产标配
元数据过滤精度 ↑ / 过滤过头则召回率骤降生产标配
混合检索 + RRF召回率 ↑ / 维护成本 ↑ / 权重难调生产标配
小 chunk 检索精度 ↑ / 上下文完整性 ↓生产标配
上下文展开完整性 ↑ / 噪声回归生产标配
重排序精度 ↑ / 延迟 ↑ / 召回率天花板已定生产标配
LLM 生成约束精度 ↑ / 用户满意度 ↓按需采用

每一个步骤都引入了一个新的 tradeoff,下一个步骤又来部分弥补这个 tradeoff。整个流水线不是在解决问题,而是在不断转移问题。


局限:七步之后,矛盾仍在

必须坦诚地说:即使走了七步,PR 矛盾也没有被消灭。

  • Query 理解是根本瓶颈——如果你的查询本身就是模糊的,整个管道的上限就定死了
  • Chunk 边界永远在丢失信息——无论切多小,信息的连续性都会在切割点中断
  • 递归检索没有好的停止条件——搜了一次觉得不够,再搜一次,什么时候该停?
  • 新鲜度检测没人做好——系统无法可靠判断检索到的信息是否已经过时

工业界的七步流水线是一个"足够好"的方案,不是一个"解决了问题"的方案。


结论:管理矛盾,而不是解决矛盾

Precision-Recall 矛盾不是一个 bug,是一个 feature——它是检索式记忆系统在数学层面的固有属性。你不可能消除它,就像不可能消除重力。

但你可以在重力存在的情况下建房子。七步流水线就是那个房子。

每一步都在做权衡,每一步都在承认矛盾没有被消除。但这个管道叠加起来,把 PR 矛盾压到了一个工程上可接受的范围内,让生产系统能够稳定运行。

如果你在做 RAG 或 Agent 记忆系统,我的建议是:

  1. 先走完前六步,看看效果。大部分人走到第三步就觉得够了。
  2. 在每一步记录你的权衡。 清楚你在哪里丢了精度、哪里丢了召回率。
  3. 接受’足够好’。 追求完美的 PR 平衡会让你走进死胡同。

回到上一篇的核心论点:记忆系统有用,但它有天花板。七步流水线就是工业界在天花板之下能找到的最佳实践。

理解了这个框架,你就理解了为什么 Harness 工程(工具设计、反馈质量、状态控制)可能是比记忆系统更大的杠杆——因为你在七步之内能做的最大的改变,往往不在检索这一步。

🚀 加载中...
一起找点有趣的事做做吧
使用 Hugo 构建 | 主题 StackJimmy 设计