ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大厂RAG为何弃用纯向量检索?混合检索与多路召回实战解析

大厂RAG为何弃用纯向量检索?混合检索与多路召回实战解析

1. 从一个被问烂了的问题说起

“为什么大厂 RAG 从不用纯向量检索?”

这个问题我在不同场合被问过不下十次。有刚入行的朋友,有做了几年搜索转行做 RAG 的工程师,也有产品经理拿着技术方案来对需求。每次我都得从头讲一遍,因为这里面的坑,不自己踩过一遍,光看论文和博客是很难真正理解的。

先说结论:纯向量检索不是不能用,而是在真实业务场景下,它单独用会死得很难看。大厂不用它,不是因为向量检索不好,而是因为大厂的业务场景太复杂、数据量太大、用户预期太高,单一召回手段根本兜不住。

这篇文章我会把这件事彻底讲透。从向量检索的本质缺陷,到混合检索的设计思路,到多路召回怎么融合,到重排序怎么做,再到实际落地时的参数调优和踩坑经验。如果你正在做 RAG 相关的项目,或者准备做,这篇文章应该能帮你省下至少两三个月的试错时间。

适合谁看?做搜索的、做推荐的、做问答系统的、做知识库的,以及所有对 RAG 技术栈感兴趣的人。不需要你精通向量数据库,但最好对 embedding 和相似度计算有基本概念。没有也没关系,我会用生活化的例子来解释。

2. 纯向量检索到底在干什么,以及它为什么不够用

2.1 向量检索的本质:语义相似度匹配

先把这个概念说清楚。向量检索的核心逻辑是:把文本通过 embedding 模型转成一个高维向量,然后通过计算向量之间的距离(余弦相似度、内积、欧氏距离等)来判断两段文本在语义上是否相似。

举个例子。“今天天气真好”和“阳光明媚的一天”,这两句话字面上没有一个词重叠,但 embedding 之后它们的向量距离会很近,因为语义相近。这就是向量检索的威力——它能捕捉到字面之外的语义关联。

但问题恰恰出在这里。语义相似不等于用户想要的结果。这是两码事。

我举个真实的场景。假设你有一个企业知识库,用户搜“报销流程”。向量检索可能会返回“费用申请步骤”“财务审批指南”“差旅费管理办法”这些语义相近的文档。看起来不错对吧?但如果用户搜的是“差旅报销标准是多少钱”,向量检索可能返回一堆讲报销流程的文档,却没有精确命中那个写着具体金额的表格。

为什么?因为 embedding 模型在压缩信息的时候,把“多少钱”这个关键的数字信息给模糊掉了。向量检索擅长捕捉“大概在讲什么”,但不擅长精确定位“具体是什么”。

2.2 纯向量检索的四个致命伤

我把纯向量检索在实际业务中暴露的问题归纳为四类:

第一,精确匹配能力弱。用户搜“ERR-4032 错误码”,向量检索可能返回一堆讲错误处理的文档,但就是找不到那个专门讲 ERR-4032 的页面。因为 embedding 模型对数字、代号、专有名词的区分度很低。你搜“张三”,它可能给你返回“李四”,因为这两个名字在语义空间里距离很近——都是人名嘛。

第二,对罕见词和长尾 query 效果差。embedding 模型的训练数据是有偏的,高频词和常见表达学得好,罕见词和长尾表达就学得糊。用户搜一个非常专业的术语,向量检索可能完全找不到相关文档,因为模型在训练时就没怎么见过这个词。

第三,无法处理否定和逻辑关系。用户搜“不含糖的饮料”,向量检索可能返回一堆含糖饮料的文档,因为“饮料”这个语义占了主导,“不含糖”这个否定修饰在向量空间里几乎体现不出来。类似的还有“除了A以外的所有情况”这种逻辑,向量检索基本无能为力。

第四,可解释性差,调优困难。向量检索返回的结果,你很难解释为什么这个排第一那个排第五。出了问题也不好调,你只能换模型、调参数、加数据,但具体哪一步起了作用,很难说清楚。这在企业级应用里是个大问题,因为业务方会问你“为什么这个结果排在前面”,你总不能说“因为向量距离近”吧。

2.3 大厂场景为什么放大了这些问题

大厂的业务场景有几个特点:数据量大、用户量大、query 分布广、业务要求高。

数据量大意味着你不能只靠一个 embedding 模型打天下,不同业务线的数据特征差异很大,一个模型很难在所有场景下都表现好。用户量大意味着任何一个小问题都会被放大——哪怕只有 1% 的 query 效果不好,在千万级日活下就是十万级的 bad case。query 分布广意味着长尾 query 特别多,而向量检索恰恰在长尾上表现最差。业务要求高意味着你不能说“大概差不多就行”,用户要的是精确答案,不是语义相近的文档。

所以大厂不是不想用纯向量检索,而是纯向量检索在真实业务场景下的召回率和准确率都达不到上线标准。这不是技术好不好,是场景匹不匹配的问题。

3. 混合检索:大厂 RAG 的标配方案

3.1 混合检索的核心思路

既然纯向量检索不够用,那怎么办?答案很简单:多种召回手段一起上,各取所长。

这就是混合检索(Hybrid Retrieval)的核心思路。它不是用一个方法解决所有问题,而是用多个方法分别解决不同类型的问题,然后把结果融合起来。

常见的召回手段包括:

召回方式擅长场景不擅长场景
向量检索语义相似、模糊匹配、跨语言精确匹配、数字代号、长尾词
关键词检索(BM25)精确匹配、专有名词、数字语义泛化、同义词、跨语言
倒排索引结构化字段过滤、范围查询非结构化文本语义匹配
知识图谱实体关系推理、多跳查询非结构化文本、模糊语义

大厂的 RAG 系统通常是向量检索 + 关键词检索(BM25)作为基础组合,再根据业务需要叠加其他召回手段。

3.2 为什么是 BM25,而不是其他关键词算法

BM25 是信息检索领域的经典算法,虽然老,但在实际业务中依然是最稳的关键词召回方案。它的核心思想是:一个词在文档中出现频率越高,文档越相关;但这个词在整个语料库中越常见,它的权重就越低。

用大白话说就是:如果某个词在很多文档里都出现,那它就不重要;如果某个词只在少数文档里出现,那它就很关键。

BM25 有几个优点:不需要训练,直接算就行;可解释性强,每个词的贡献都能算出来;对精确匹配特别有效,用户搜什么词就匹配什么词。这些优点恰好补上了向量检索的短板。

当然 BM25 也有缺点,它不理解语义。用户搜“手机”它不会返回“移动电话”的文档。但这没关系,因为向量检索会补上这一块。两者结合,覆盖的场景就广了。

3.3 多路召回怎么融合:从 RRF 到加权融合

有了多路召回,下一个问题就是:怎么把不同召回源的结果合并成一个排序?

最简单的方法是加权融合:给每个召回源的结果打分,然后加权求和。比如向量检索的分数占 0.6,BM25 的分数占 0.4。但这个方法有个问题:不同召回源的分数分布不一样,直接加权可能不公平。

更常用的方法是RRF(Reciprocal Rank Fusion,倒数排名融合)。它的思路是:不看具体分数,只看排名。每个文档在每路召回中的排名,取倒数后求和。

公式很简单:

RRF_score(d) = Σ (1 / (k + rank_i(d)))

其中 k 是一个常数(通常取 60),rank_i(d) 是文档 d 在第 i 路召回中的排名。

RRF 的好处是:不需要归一化分数,对不同召回源的分数分布不敏感,实现简单,效果稳定。大厂在融合多路召回时,RRF 是默认方案。

我实测下来,RRF 的 k 值取 60 是个比较稳的选择。k 太小,头部结果波动大;k 太大,尾部结果区分度不够。当然具体业务具体调,但 60 是个不错的起点。

3.4 重排序:最后一道质量关卡

多路召回融合之后,通常会得到一个比较大的候选集(比如 top 100)。这时候还需要一个重排序(Rerank)步骤,用更精细的模型对候选集重新打分,选出最终的 top k。

重排序模型通常比召回模型大,计算也更慢,但精度更高。常见的方案是用 Cross-Encoder 架构的模型,把 query 和文档拼在一起输入模型,直接输出相关性分数。

为什么召回和排序要分开?因为召回阶段要处理海量数据,必须快;排序阶段只处理少量候选,可以慢一点但要求准。这就是所谓的漏斗模型:召回粗筛,排序精排。

重排序这一步在实际业务中非常关键。我见过很多团队在召回上花了很多功夫,但忽略了重排序,最后效果就是上不去。召回决定上限,排序决定最终质量。两者都不能偏。

4. 实际落地时的关键细节与踩坑记录

4.1 向量模型选型:不是越大越好

选 embedding 模型的时候,很多人第一反应是选最大的、最新的。但实际业务中,模型大小和推理成本是直接挂钩的。

我做过一个对比测试:在一个百万级文档的知识库上,用不同大小的 embedding 模型做检索。结果发现,在 top 10 的召回率上,大模型确实比小模型高 3-5 个百分点。但推理时间上,大模型是小模型的 5-8 倍。如果业务对延迟敏感,这个差距是不可接受的。

我的建议是:先用小模型跑通流程,确认效果瓶颈在哪里,再决定要不要换大模型。很多时候瓶颈不在 embedding 模型上,而在数据质量、切分策略、召回融合这些环节。

另外,embedding 模型的领域适配也很重要。通用模型在通用场景下表现好,但在垂直领域(医疗、法律、金融)可能就不行了。这时候要么用领域数据微调,要么选专门针对该领域训练的模型。

4.2 文档切分:最容易被忽视的环节

文档切分(Chunking)是 RAG 系统里最不起眼但影响最大的环节之一。切分策略直接决定了召回的质量。

我见过太多团队在这个环节偷懒:固定长度切分,512 个 token 一刀切。结果就是:有的 chunk 把一句话切成两半,语义不完整;有的 chunk 包含多个主题,向量表示模糊;有的 chunk 太短,信息量不够。

好的切分策略应该考虑:

  • 语义完整性:尽量在段落、章节边界切分,不要切断完整的语义单元。
  • 重叠窗口:相邻 chunk 之间保留一定的重叠(比如 10-20%),避免边界信息丢失。
  • 元数据附加:每个 chunk 带上来源、标题、时间等元数据,方便后续过滤和排序。
  • 层级结构:如果文档有层级结构(章节、小节),保留这个结构信息。

我自己的经验是:切分策略没有标准答案,必须根据业务数据特点来调。技术文档、法律合同、客服对话,这三种数据的切分策略完全不同。花时间在这个环节上,回报率比调模型参数高得多。

4.3 查询改写:让用户的问题更好被理解

用户的 query 往往很短、很模糊、有歧义。直接拿去做检索,效果很难好。查询改写(Query Rewriting)就是解决这个问题的。

常见的查询改写手段包括:

  • 同义词扩展:把“手机”扩展成“手机 OR 移动电话 OR 智能手机”。
  • 拼写纠错:把“aple”纠正成“apple”。
  • 意图识别:判断用户是想找文档、找答案、还是找操作步骤。
  • 多轮改写:在多轮对话中,把当前 query 和上下文结合,改写成完整的查询。

大厂的 RAG 系统通常会用一个小模型来做查询改写,成本低、速度快、效果稳定。这一步做得好,召回质量能提升 10-20%。

4.4 常见问题速查表

问题现象可能原因排查方向解决方案
精确匹配搜不到纯向量检索,缺少关键词召回检查是否有 BM25 召回加入关键词召回,用 RRF 融合
长尾 query 效果差embedding 模型覆盖不足分析 bad case 分布查询改写 + 关键词召回兜底
否定 query 失效向量模型不理解否定测试否定类 query查询改写 + 规则过滤
召回结果重复多路召回未去重检查融合逻辑加去重步骤,按 doc_id 去重
排序效果不稳定重排序模型未调优对比不同 rerank 模型换模型或加训练数据
延迟太高召回路数太多或模型太大分析各阶段耗时减少召回路数,换小模型

5. 从零搭建一个混合检索 RAG 系统的实操路径

5.1 整体架构设计

一个完整的混合检索 RAG 系统,通常包含以下模块:

  1. 数据预处理:文档解析、切分、元数据提取。
  2. 索引构建:向量索引 + 倒排索引。
  3. 查询处理:查询改写、意图识别。
  4. 多路召回:向量召回 + 关键词召回。
  5. 结果融合:RRF 或加权融合。
  6. 重排序:Cross-Encoder 精排。
  7. 答案生成:LLM 基于召回结果生成答案。

这个架构不是固定的,可以根据业务需要增减模块。但核心思路是一样的:多路召回 + 融合 + 精排。

5.2 关键参数计算与选择

向量维度:取决于 embedding 模型,常见的有 768、1024、1536。维度越高,表达能力越强,但存储和计算成本也越高。百万级文档用 768 维通常够用。

索引类型:HNSW 是目前最常用的向量索引,查询快、召回率高。关键参数是 M(每个节点的连接数)和 efConstruction(构建时的搜索范围)。M 通常取 16-64,efConstruction 取 100-500。

BM25 参数:k1 和 b。k1 控制词频饱和点,通常取 1.2-2.0;b 控制文档长度归一化,通常取 0.75。

RRF 的 k 值:通常取 60,可根据业务调整。

重排序候选数:通常取 50-100,太少可能漏掉好结果,太多延迟高。

5.3 实操步骤与代码示例

以下是一个简化的混合检索实现示例,用 Python 演示核心逻辑:

# 伪代码示例,展示混合检索的核心流程 def hybrid_retrieval(query, top_k=10): # 第一步:查询改写 rewritten_query = query_rewrite(query) # 第二步:多路召回 vector_results = vector_search(rewritten_query, top_k=100) bm25_results = bm25_search(rewritten_query, top_k=100) # 第三步:RRF 融合 fused_results = rrf_fusion( [vector_results, bm25_results], k=60 ) # 第四步:重排序 reranked_results = rerank( query, fused_results[:50], top_k=top_k ) return reranked_results def rrf_fusion(result_lists, k=60): scores = {} for results in result_lists: for rank, doc in enumerate(results): doc_id = doc['id'] if doc_id not in scores: scores[doc_id] = 0 scores[doc_id] += 1 / (k + rank + 1) # 按分数排序 sorted_docs = sorted( scores.items(), key=lambda x: x[1], reverse=True ) return [doc_id for doc_id, _ in sorted_docs]

这个示例展示了核心流程,实际生产中还需要考虑:并发召回、超时控制、降级策略、缓存等。

5.4 效果评估与迭代

上线之后怎么评估效果?不能只看感觉,要有量化指标。

常用的指标包括:

  • Recall@K:前 K 个结果中包含正确答案的比例。
  • MRR:第一个正确答案排名的倒数均值。
  • NDCG:考虑排序位置的加权指标。
  • 人工评估:抽样人工判断相关性。

我建议的做法是:先建一个评估集,包含 100-500 个 query 和对应的正确答案,每次改动都跑一遍评估集,看指标变化。没有评估集,调优就是盲人摸象。

6. 几个容易被忽略但很重要的经验

6.1 不要迷信单一指标

召回率、准确率、延迟、成本,这四个指标要一起看。我见过团队为了提升 1% 的召回率,把延迟从 100ms 拉到 500ms,这在生产环境是不可接受的。技术方案的选择永远是权衡,不是追求单点最优。

6.2 数据质量比模型重要

再好的模型,喂给它垃圾数据,也出不来好结果。文档解析错误、切分不合理、元数据缺失,这些问题比模型选型影响大得多。先把数据质量搞上去,再考虑换模型。

6.3 从简单方案开始

不要一上来就搞最复杂的架构。先用最简单的方案跑通,确认瓶颈在哪里,再逐步加东西。我见过太多团队一开始就上多路召回 + 重排序 + 查询改写,结果每个环节都没调好,整体效果还不如单路 BM25。

6.4 监控和反馈闭环

上线不是终点,是起点。要有监控看 bad case,要有反馈渠道收集用户反馈,要有机制把反馈转化成改进。没有反馈闭环的系统,会随着时间推移越来越差。

7. 回到最初的问题

大厂 RAG 从不用纯向量检索,不是因为向量检索不好,而是因为真实业务场景太复杂,单一手段兜不住。混合检索、多路召回、重排序,这些不是炫技,是被业务逼出来的。

如果你正在做 RAG 项目,我的建议是:先把 BM25 跑通,再加上向量检索,用 RRF 融合,最后加重排序。这个路径稳、可控、每一步都有明确的收益。不要一上来就追求最先进的方案,先把基础打牢。

我在实际项目中的体会是:RAG 系统的效果,20% 靠模型,80% 靠工程。数据预处理、切分策略、召回融合、参数调优,这些脏活累活才是决定成败的关键。模型选型当然重要,但它不是全部。

最后分享一个小技巧:如果你不确定要不要上混合检索,先做个简单测试——拿 100 个真实 query,分别用纯向量和混合检索跑一遍,人工对比结果。如果混合检索明显更好,那就上;如果差不多,那就先别折腾。用数据说话,别用感觉做决策。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进