从纯向量检索聊起。我最早做RAG(检索增强生成)的时候,也觉得向量检索是银弹——把所有文档切块、embedding入库,用户问一句,查一下向量库,取TopK塞给大模型,齐活。但真上了生产环境就发现,这个方案在Demo里跑得挺美,放到真实业务里,召回内容错得离谱,用户问个产品型号查不到,问一串型号中的数字也查不到,被业务方追着问“你这检索为什么不如搜索框好用”的次数,比我过去五年被问的都多。后来我特意去看了几家大厂公开分享的技术方案,发现一个共同点:所有人在生产级RAG里都没有只用纯向量检索,而是把向量检索和关键词检索、重排、元数据过滤做了混合。今天这篇就把我自己踩过坑之后的完整思路整理出来,聊聊为什么纯向量检索不上生产,以及混合检索到底应该怎么搭。
1. 一眼看上去很美的纯向量检索:优势与致命短板
1.1 为什么最初大家都迷上向量检索
向量检索的魅力在于它把文本变成了数学。2013年之后,词向量开始普及,文本不再是一个个孤立的词,而是映射到高维空间里的一个点。到了RAG时代,句子和段落也能变成向量,语义相近的内容会落在空间中相近的位置。
这个思路在概念层面几乎无可挑剔:用户可以不用纠结精确的关键词,用一段含糊的自然语言就能找到“差不多意思”的文档。比如用户问“这个月业绩为什么下滑”,文档里写的是“本月营收同比下降”,纯关键词检索大概率是抓不到“业绩下滑”和“营收下降”之间的关联的,但向量检索能。
所以在项目初期,我毫不犹豫地选择了纯向量检索作为唯一召回方式。技术栈很清爽,向量库选一个开源的,embedding模型挑一个,然后把文档全部切片丢进去。实现简单,效果在演示集上又特别唬人——十个问题能答对八个,领导看完当场拍板要上线。
但等到接上真实的知识库,问题就开始井喷。
1.2 实际效果翻车的地方:专有名词、数字、缩写、词序
真实业务文档里,大量信息不是靠“语义”承载的,而是靠“精确符号”承载的。
举个例子。某系统有个接口叫v2/user/info,文档里也许还出现过v1/user/info、v2/user/detail。用户问“查询用户信息的v2接口怎么调”,纯向量检索返回的top结果里很可能是v2/user/detail,因为“查询”“用户”“信息”“接口”这些词都在语义上高度相似,而路径字符串本身在向量空间里的区分度并不高。类似地,型号ABC-X100和ABC-X200,在语义空间里几乎就是两个相邻点,差一个数字根本分不出来。
再比如版本号、日期、金额、身份证号、工单编号,这些字段的特征是:差一个字符就是完全不同的东西。而向量检索做的是“模糊匹配”,它对“字符级差异”非常不敏感。你说要“2023年1月的数据”,它可能给你召回“2023年11月”的内容;你说要找“错误码E1001”,它给你召回“错误码E2001”。这种错误在关键词检索里几乎不可能出现,但在纯向量检索里出现的频率高得让人崩溃。
缩写同样是个坑。文档里写“在建工程”,用户问“在建项目”;文档里写“PA”,用户问“过程审核”。向量模型如果没在领域语料上微调过,根本学不到这些缩写和全称的等价关系。而关键词检索恰恰天然擅长这个——前提是词表里已经做了同义词扩展。
词序问题也值得单独说一句。中文里“北京烤鸭”和“烤鸭北京”在自然语言里前者更常见,向量模型能学会;但“张三起诉李四”和“李四起诉张三”意思完全相反,向量空间里却可能距离很近。很多团队告诉我他们的RAG连“谁告谁”这种基础事实都容易弄反,其实就是吃了纯向量检索对词序不敏感的亏。
1.3 本质原因:向量检索是“语义相似度匹配”,不是“信息检索”
把上面这些问题汇总一下,就能看到个共同根因:向量检索的目标函数是“文本语义相似”,而业务检索的目标函数是“是否满足用户信息需求”。两者有交集,但绝对不是包含关系。
用户搜索“v2接口怎么调”的时候,信息需求非常明确:必须是v2,必须是这个接口,必须是调用方式。向量模型把这句话编码成一个768维的向量,然后把数据库里所有文本都编码成向量,按余弦相似度排序。它并不知道“v2”和“v3”之间的差异在业务里是致命的,因为从向量空间的角度看,这不过是两个稍有区别的点。
如果单纯做相似度匹配,那召回结果就可以被理解成“最像的一句话”。但RAG场景里我们需要的是“能够回答用户问题的那一段文档”。很像不代表能回答,能回答往往需要精确命中某个关键实体、某个关键状态、某条操作命令。
所以大厂RAG架构里,纯向量检索不是“不用”,而是不能作为唯一召回方式。它会被安排成一路召回管线,但旁边一定还站着关键词召回、布尔过滤、甚至数据库查询,最后再做融合和重排。
2. 大厂生产级RAG的通用架构:混合检索是标配
2.1 混合检索到底混的是什么:向量召回加稀疏关键词召回
现在业界说的混合检索,通常是指把两个完全不同特性的召回通道结果合并:
- 稠密向量召回:将文本转成高维向量,通过ANN(近似最近邻)检索找到相似内容,擅长语义泛化、同义改写、跨语言;
- 稀疏关键词召回:基于BM25、TF-IDF或倒排索引,对精确词项进行匹配,擅长专有名词、型号、代码、数字、词序敏感场景。
二者互为补充。语义模糊的query交给向量,精确匹配的query交给关键词。生产系统中往往还有第三路、第四路召回,比如根据结构化字段做的布尔过滤、从业务数据库直接查询、或者调用内部搜索服务。
有一家做企业知识库的团队,他们公开的分享里提到过:通过增加一路BM25召回,整体RAG答案准确率从68%提升到了82%。提升的这部分,主要就是之前老是丢的精确型号、编号、日期。这个比例在我自己的实验里也得到过验证。纯向量检索对“大而全”的语义泛化问题效果好,但对“小而精确”的业务检索需求,确实拉胯。
所以混合检索不是“要不要上”的问题,而是“怎么把两条路融合得不出乱子”的问题。
2.2 为什么要加一层Reranker:用粗排换精度
混合检索只是把候选文档从多个通道捞出来,但每个通道返回的排序依据不同——向量的余弦相似度和BM25的分数根本没有可比性。直接把两条结果合并成一个list,谁在前谁在后是个大问题。
大厂的做法是:先做召回(Recall),再做重排(Rerank)。召回阶段看的是“海选”,只要别漏掉相关内容就行,精度可以低一点;重排阶段用更精细的模型,把召回的所有候选逐条和用户问题做交叉编码,计算一个统一的精细相关度分数,再重新排序。
常用的Reranker是Cross-Encoder。它不像向量检索那样把问题和文档分别编码成两个向量再算相似度,而是把“问题”和“文档”拼在一起作为一个序列,一起送进模型,输出得到一个0到1之间的相关性打分。这种方式能捕捉到更细腻的交互信息,比如词序、否定关系、实体匹配、上下文指代,精度远高于双塔式的向量相似度。
代价自然就是慢。Cross-Encoder比向量检索慢一到两个数量级,所以不能直接对全量文档跑Rerank,只能先通过混合检索把候选从几万篇缩小到几十篇,然后再对这几十篇做精细重排。
这也是大厂RAG的标准三段式:多路召回 + 粗排融合 + 精排Rerank。
2.3 还不是全部:元数据过滤、函数调用、数据库查询兜底
别以为有了混合检索和重排就万事大吉。我见过很多生产系统里,真正提升效果的不是哪个神级模型,而是元数据过滤。
知识库的每篇文档通常都带结构化信息:创建时间、文件类型、所属部门、文档状态、适用范围、版本号等。用户问“2024年的报销制度”,如果不先把时间范围过滤掉,你召回一堆2022年的政策,重排再好也救不回来。因为语义上“报销制度”都是相似的,但时间信息是过滤条件,不是排序条件。
所以生产级RAG里,用户query通常先经过一个意图识别和信息抽取模块,把候选过滤条件抽出来。比如识别出“2024年”“报销制度”“公司总部”这三个元素,先通过元数据过滤把年份锁定到2024,再用混合检索在剩余文档中召回“报销制度”,最后按相关性重排。
还有些问题根本不适合用文档检索来回答。比如“库存还有多少”,这应该查库存系统;“订单状态是什么”,应该查订单数据库。硬要在知识库文档里检索,大概率搜到一个过时的静态快照。
大厂的做法是在RAG框架里加“路由”模块:判断query是查文档,还是查数据库,还是调用API。只有真正需要文档知识的问题,才走向混合检索召回。
3. 混合检索落地:从召回、融合到重排的完整流程
3.1 双路召回怎么设计:向量索引加倒排索引
真正的落地实现其实不复杂,但这几个关键点必须想清楚。
第一路向量召回。需要选择向量库,比如开源的FAISS、Milvus,或者云厂商托管向量库。embedding模型建议用领域微调过的模型,如果条件不够,至少要选一个在中文上表现稳定的通用模型。文本切片时,既要考虑语义完整性,也要考虑检索单元大小。我一般控制在300到500字,带100字左右的重叠,避免切断关键上下文。
第二路关键词召回。最常用的实现是Elasticsearch或OpenSearch,因为它们自带BM25算法、倒排索引、分词器,还能和向量检索同时存在于同一套索引里。你可以在同一篇文档里同时定义keyword字段和dense_vector字段,这样一条记录既能被BM25命中,也能被向量近邻搜索命中。搜索时发出两个子查询,分别拿到两个候选列表,再在应用层做融合。
如果嫌自建麻烦,很多向量数据库现在也内置了稀疏向量能力,相当于把BM25做到索引里。但生产稳定性上,我还是倾向于直接用ES系产品,词法检索太成熟了,没必要自己造轮子。
3.2 融合排序的常用算法:RRF、加权、分布式融合
两路召回都返回了各自的top结果,怎么合成一个统一的列表?有三种常见方法。
第一种是加权分数相加。把向量相似度分数和BM25分数各自做归一化到0到1,然后按权重相加。优点是简单直接、权重可以调;缺点是向量分数和BM25分数的分布差异很大,加权后的结果往往被某一方主导,权重需要反复调参。
第二种是RRF(Reciprocal Rank Fusion,倒数排名融合)。公式是:对候选文档,在两个榜单里的排位分别取倒数,再加和。比如某文档在向量召回里排第2,在关键词召回里排第5,双边都不错,融合分数就是1/2 + 1/5 = 0.7;而某文档只在向量召回里排第1,关键词那边完全没出现,融合分数就是1/1 + 0 = 1,仍然会排在前面。
RRF的最大好处是不依赖分数绝对值,只看排名,所以两个差异巨大的系统也能比较公平地融合。我实测下来,RRF在多数场景下比简单加权更稳,不容易被极端高分带偏。
第三种是学习式融合。加一个排序模型,输入特征包括向量相似度、BM25分数、文档长度、领域权重等,输出融合排序。这个方案上限最高,但需要标注数据和训练成本。中小企业一般用不上,但大厂通常会在重排阶段做这件事,因为效果确实能再涨几个点。
3.3 Reranker怎么选、怎么用:交叉编码器与打分阈值
召回融合之后,Rerank阶段就需要一个精细排序器。目前主流选择还是Cross-Encoder类模型,具体选型上我会关注三点:
- 模型对长文本的支持能力,至少能处理512个token,理想是能处理更长的上下文;
- 中英文混合场景下的稳定性,因为很多技术文档中英混排;
- 推理速度,尽量选量化后能在CPU上几十毫秒内完成的模型。
Rerank的过程是把一个Batch的数据(query + 每条候选文档)分别组成输入对,送入模型得到相关度分数。这里要注意,Batch大小不能设置过大,否则显存或内存会炸;也不能太小,否则模型推理的并行优势发挥不出来。我常用的是32或者64。
重排后取TopK,一般取3到5条就够了。有些场景下,如果Rerank打分最高都不到某个阈值(比如0.35),说明召回质量整体不佳,宁可告诉用户“知识库中没有相关内容”,也不要硬把屁股答案塞给大模型。否则大模型会一本正经地根据不相关文档编答案,这比检索不到更可怕。
4. 实战中我踩过的坑与调优经验
4.1 召回数量调参:TopK不是越大越好
我最早做混合检索时,习惯把召回TopK设成50,觉得召回多一点总没坏处,反正还有Rerank兜底。后来发现,召回条数越多,Rerank的耗时线性增长,用户响应时间从800毫秒涨到2秒,被架构师点名约谈。
更隐蔽的问题是:当召回数量从20涨到50,虽然相关文档基本都进来了,但Rerank的输入里噪声比例也变高。Cross-Encoder在大量低相关文档干扰下,打分反而会不稳定,甚至把一些看起来像但实际不对的文档排到前面。
我现在一般遵循这个经验值:向量召回和关键词召回各取20条,融合去重后进Rerank的候选控制在30条以内,Rerank后取Top3到Top5喂给大模型。如果Top3的分数普遍低于0.4,说明知识库缺内容,该去补文档而不是盲目调参。
4.2 文本切片与索引字段对混合检索的影响
这是最容易被忽视、但影响最大的一个环节。切片太大,向量表示被平均化,专有名词被稀释;切片太小,上下文不完整,重排阶段拿到的信息不够模型判断。我做过一轮对比实验,同样一套知识库,用512字切片加128重叠的F1值,比用200字切片高出7个百分点左右。
关键词检索字段同样需要设计。不是把所有文本都塞进一个字段里让BM25跑就行,而是要考虑给哪些字段增加权重。比如“标题”字段权重设为5,“正文”设为1,命中标题的文档更容易排前面;代码片段的字段单独一个索引,这样用户搜api/v2时不会因为正文里“接口调用”权重高而埋没真正有用的代码块。
还有个细节:很多团队用默认标准分词器,结果把API拆成a、p、i,搜索彻底废了。必须针对技术文档配置自定义分词器,把常见英文大小写统一、保留专有名词、保留连字符和斜杠路径。这块的确不性感,但真的决定了RAG能不能用。
4.3 常见问题速查:查不到、精度低、时延长
根据我在不同项目和同行交流中遇到的高频问题,整理一张速查表。
| 问题现象 | 常见根因 | 解决思路 |
|---|---|---|
| 精确编号/型号查不到 | 纯向量召回对字符差异不敏感,关键词通道缺失或分词把编号切碎 | 增加BM25召回通道;调整分词器,保留编号和连字符 |
| 用户问题很短,召回结果却都不相关 | Embedding模型对短查询编码效果差,领域语料未微调 | 对query做改写扩写;使用领域微调的Embedding模型 |
| 融合后相关文档排位反而下降 | RRF或加权参数不合适,某一路劣质结果挤压另一路 | 调低某一通道TopK;改用带权重的RRF,给向量和关键词不同权重 |
| Rerank后结果不如粗排 | 重排模型太小、交叉编码质量差;候选太多噪声干扰 | 换更强的Cross-Encoder;压缩候选数量至20~30条 |
| 响应时间超过2秒 | 召回了太多候选;Rerank Batch太小;向量库没有用GPU索引 | 降低各通道召回数;增大Batch;向量索引换成HNSW速度优先模式 |
| 同一问题每次答案不一样 | 向量检索索引未持久化或召回有随机性;Rerank阈值不稳定 | 固定随机种子;对索引一致性做监控;结果缓存 |
这些坑我自己都至少踩过一遍。特别是“同一问题答案忽变”这个,最坑人,因为用户和业务方会立刻对系统失去信任。后来我把知识库索引改为版本化发布,每次更新后固定索引版本,query也做规范化处理,才彻底解决。
4.4 什么时候真的可以用纯向量检索
讲这么多混合检索,不是说纯向量检索一无是处。在特定场景下,纯向量检索反而是最优解。
第一种是知识库规模很小、内容以常识和概念为主。比如个人笔记助手、家庭相册描述检索。用户搜索“那年夏天海边的照片”,这种开放的、模糊的内容蕴含的是关联记忆,没有精确匹配要求,纯向量检索就够了。
第二种是问题属于“开放语义联想”型,比如“和AI伦理相关的资料有哪些”。这类查询本身就不存在一个唯一精确答案,用户要的是相关内容的罗列,纯向量检索的召回结果已经满足需求。
第三种是Demo和原型验证阶段。团队为了快速验证RAG流水线,先上纯向量检索跑通全流程完全没有问题,能让产品同学确认体验方向。但要明确知道,这只是为了验证流程,不是生产方案。
我自己在给朋友的小团队做咨询时,通常会这样建议:如果你们的场景里用户query大多超过10个字、搜索对象以长文内容为主、而且不需要精确匹配编号,那么先从纯向量检索起步是合理的;但只要检索内容里出现型号、名称、编号、日期、金额中的任意一类,请立刻把混合检索提上日程。
最后分享一个我的个人实践心得
踩过这么多坑之后,我最大的感悟是做RAG不要迷信“先进技术”,要回到“信息检索的本质”去看问题。纯向量检索放大了语义能力,却丢失了精确匹配和逻辑约束能力。混合检索说白了,就是把传统搜索引擎那套成熟方法论和深度学习语义能力重新组合,哪种能力适合解决什么问题就用哪个。
实际项目里还有个小细节值得留意:无论你用什么检索方案,最终喂给大模型的上下文质量,远比模型本身重要。我见过太多团队一头扎进调prompt、换大模型,却忘了先检查检索出来的文档到底是不是用户真正需要的那段。花半天时间把混合检索调好,可能比换一个更大参数的模型提升更明显。
最后再给一个非常具体的建议:上线RAG系统之前,一定整理一套“必测问题集”,里面至少包含10个精确匹配类问题(编号、日期、名称)、10个语义泛化类问题、10个否定和翻转类问题。每次改动检索链路,都用这套问题回归一遍。我亲眼见过某次升级把整个向量模型换掉,语义泛化大幅提升,结果所有型号查询全都召回错误。如果没有回归问题集,这种意外几乎没有可能被及时发现。
RAG的检索部分没有银弹,但混合检索本身就是最接近银弹的方案。希望能帮你少走一些我走过的弯路。