向量检索和关键词检索的混合搜索怎么做?
面试官问你:“向量检索和关键词检索的混合搜索怎么做?”如果你回答“两路各搜一遍,向量相似度乘 0.7,BM25 分数乘 0.3,加起来排个序”,恭喜你,出门右拐回家等通知吧。
为什么?
因为这两个分数根本不在一把尺子上。余弦相似度落在一个固定的区间里,BM25 的分数没有上界,命中的查询词越多,分数就越高。Qdrant 官方在讲混合搜索的文章里专门提醒过,同一组权重,这个查询刚好合适,换一个查询,BM25 就可能把向量的分数整个压下去。

我翻了派聪明 HybridSearchService 的源码、RRF 的原始论文和 Elasticsearch 的官方文档,可以自信地、大方地、光明磊落地帮你搞清楚这三件事。
- 一路检索明明就能出结果,为什么非要两路混合?
- 两路的分数没法相加,结果到底怎么合并?
- 合并完就能直接交给大模型了吗?
哈喽大家好,我是二哥呀。今天用 4 分钟,给你讲透 RAG 里的混合搜索。
先说第一件事,为什么一路检索不够?
向量检索是先把文本转成 Embedding,也就是一串表示语义的数字,再去找意思最接近的分块。它听得懂意思,但认不准字面。Anthropic 在《Contextual Retrieval》这篇文章里举过一个例子,用户在技术支持文档里查“Error code TS-999”,Embedding 能找到一堆讲错误码的内容,却可能偏偏漏掉 TS-999 这一条。
关键词检索用的是 BM25 算法,按查询词在文档里出现的次数和这个词的稀有程度打分。它正好反过来,字面认得准,同义的说法却不认识。用户问“怎么退款”,文档里写的是“退货流程”,BM25 就匹配不上。
所以派聪明的做法是两路一起上。BM25 和 KNN 向量检索在两个线程里并行召回,每一路取 topK 乘 30 条,上限 300 条,topK 等于 5 的时候,每一路就是 150 条。
KNN 这一路还把 userId、orgTag、public 三个权限条件放进了预过滤。预过滤的意思是 Elasticsearch 在做向量搜索的过程中,就把当前用户没有权限的分块排除掉。如果先搜索再过滤,150 条候选里可能一大半是别人的文档,过滤完剩下的就不够用了。

向量负责听懂意思,BM25 负责认准字面,少了哪一路都会漏答案。
那聪明的你肯定想到了:两路的分数没法相加,结果到底怎么合并?
答案是 RRF,全称 Reciprocal Rank Fusion,倒数排名融合。它干脆不看分数,只看名次。一个分块在某一路排第几名,就得到 1 除以 60 加名次的分数,两路的分数再加起来。
这个 60 不是随便定的。它出自 2009 年 SIGIR 会议上 Cormack 等人的论文,作者在试点实验里把这个常数从 0 调到 500,效果差别很小,60 接近最优,就固定了下来。
算一下就明白了。一个分块在两路都排第一,得分是 2 除以 61,大约 0.0328。只在一路排第一、另一路没出现,得分只有一半,大约 0.0164。两路都认可的分块,自然会排到前面。

那派聪明为什么要在 Java 里手写 RRF,不直接用 Elasticsearch 自带的?因为派聪明部署的是 Elasticsearch 8.10.4,用的是免费的 basic license。Elasticsearch 的 retriever 接口从 8.14 版本才开始提供,原生 RRF 还需要 Enterprise 商业授权。RRF 的核心公式在代码里只有一行,自己实现的成本很低。
RRF 不管两路的分数各有多高,只看分块在每一路排第几。
那聪明的你肯定又要问了:合并完就能直接交给大模型了吗?
还不行,这也是面试官最想听到的部分。
RRF 只是按名次粗排,它并不知道分块的内容和问题到底有多相关。所以派聪明会把 RRF 排名前 50 的分块交给 gte-rerank-v2 这个 Rerank 模型,也就是重排序模型。Rerank 模型把问题和每个分块放在一起逐条打分,只保留 0.1 分以上的结果,再取前 5 条交给大模型。
这个 0.1 是评测出来的。10 月 11 日,派聪明用 50 道带标准答案的题做评测。阈值原来是 0.2,分块级的 Recall@5 只有 56.5%,Recall@5 就是正确分块出现在前 5 条结果里的比例,还有 13 道题一条结果都没有检索到。原因是用户口语化提问的时候,正确分块的 Rerank 分数只有 0.14 到 0.19,全被 0.2 挡在了门外。阈值降到 0.1 以后,Recall@5 升到 76.1%,空检索降到 0 道。

10 月 13 日,派聪明又做了一轮分阶段评测,召回阶段的 Recall@50 已经达到 100%,所有正确分块都在候选里,短板在重排序。
还有一个容易踩的坑是降级。旧版代码里,任何一路召回超时,两路的结果会一起丢掉,退回纯 BM25 检索。现在改成了一路失败,就用另一路的结果继续做 RRF 和 Rerank。Rerank 超时或者出错,就按 RRF 的顺序返回,再用一个分数阈值过滤掉排名太靠后的分块。
召回决定答案在不在候选里,重排决定答案能不能排进前五。
最后简单总结下。
混合搜索不是把两个分数加权相加,而是两路各自召回、按名次融合、再交给 Rerank 精排。
另外给大家一条实用建议,调整任何阈值之前,先建一份带标准答案的评测集。每改一个参数,就用 Recall@5 和 MRR 说话,MRR 看的是第一个正确结果平均排得有多靠前。
这个知识点你学会了吗?想解锁更多 Agent 硬核知识,点赞关注,我是二哥,咱们下期见!
