RAG 中的混合检索(Hybrid Search)是什么?为什么要用混合检索而不是纯向量检索?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
- 概念理解深度:面试官想知道你是否清楚稠密检索(Dense Retrieval)和稀疏检索(Sparse Retrieval)各自的能力边界——哪些场景向量检索搞不定,哪些场景关键词检索不够用。
- 工程实践意识:你是否在生产环境真正搭过混合检索?RRF 融合怎么做?权重怎么调?选型考量是什么?
- 架构设计能力:混合检索不是简单地把两种检索结果拼在一起就完事了。怎么融合、怎么重排序、怎么评估效果,这些才是区分 "了解概念" 和 "真正落地" 的分水岭。
核心答案
混合检索(Hybrid Search)= 稠密向量检索(Dense/Semantic Retrieval)+ 稀疏关键词检索(Sparse/Lexical Retrieval),再把两路结果融合排序。
为什么要搞两路?因为单独哪一路都有盲区:
| 检索方式 | 擅长 | 搞不定 |
|---|---|---|
| 稠密向量检索 | 语义相似、同义词、抽象概念 | 精确关键词匹配(如产品型号 iPhone 15 Pro Max、错误码 ERR-4023、函数名 findByIdAndName) |
| 稀疏关键词检索(BM25) | 精确匹配专有名词、代码标识符、编号 | 语义理解(用户搜 "怎么退订",文档里写的是 "取消订阅") |
| 混合检索 | 两者兼顾 | 实现复杂度略高,需要融合策略 |
一句话概括:向量检索理解 "意思",关键词检索抓住 "字面",混合检索把两者结合起来,补齐各自的短板。
到 2025-2026 年,混合检索已经成为 RAG 系统的生产级标配。Milvus 的官方文档甚至直接说 "hybrid search is the 2026 production baseline",这已经不是什么前沿探索了,而是工程上的最佳实践。
深度解析
一、为什么纯向量检索不够用?
向量检索的原理是把文本通过 Embedding 模型编码成高维向量,然后用余弦相似度或内积来衡量语义相关性。这对 "理解意思" 很在行,但它有一个致命弱点:对精确关键词的匹配能力很弱。
举个实际的例子。假设你的知识库里有这样一段文档:
Spring AI整合Milvus向量数据库时,需要在pom.xml中引入spring-ai-milvus-store依赖,版本号1.0.0-M4。
用户提问:"spring-ai-milvus-store 的版本号是多少?"
向量检索可能会返回一堆跟 Spring AI、Milvus、依赖配置语义相关的文档,但精确包含 spring-ai-milvus-store 这个 artifactId 的那段文档,排名可能反而靠后——因为 Embedding 模型把这段文本编码后,"精确匹配 artifactId" 这个信号被稀释到整个语义空间里了。
而 BM25 关键词检索呢?它看到 spring-ai-milvus-store 这个精确 token,直接就能命中目标文档,简单粗暴但有效。
反过来,用户问:"Spring AI 怎么连接向量数据库?"BM25 可能搜不到好结果,因为文档里写的是 "整合 Milvus",没有 "连接" 这个词。但向量检索能理解 "连接" 和 "整合" 在这个语境下是一个意思。
这就是为什么需要混合检索——两种检索方式是互补的,不是替代关系。
二、混合检索的整体架构
上图是混合检索的完整流程。核心就三步:
- 两路并行检索:用户查询同时进入稠密检索通道和稀疏检索通道,各自返回 Top-K 个结果(通常 K=10~20)
- 结果融合:用 RRF(Reciprocal Rank Fusion)或加权分数融合,把两路结果合并成统一的排序
- 重排序(可选但推荐):用 Cross-Encoder 模型对融合后的结果做精排,进一步提升相关性
三、RRF 融合算法——混合检索的核心魔法
混合检索最关键的步骤是怎么把两路结果合并。你不能简单地把向量检索的分数和 BM25 的分数相加——因为它们的分数量纲完全不同(向量相似度是 0~1 的余弦值,BM25 是基于词频的评分,可能到几十甚至上百)。
RRF(Reciprocal Rank Fusion)巧妙地避开了分数归一化的问题,它只看排名位置,不看原始分数:
RRF_score(d) = Σ 1 / (k + rank_i(d))
rank_i(d):文档 d 在第 i 路检索结果中的排名k:平滑常数,通常取 60
举个直观的例子:假设文档 A 在向量检索中排第 1 名,在 BM25 中排第 5 名:
RRF_score(A) = 1/(60+1) + 1/(60+5) = 1/61 + 1/65 ≈ 0.031
文档 B 在向量检索中排第 3 名,在 BM25 中排第 1 名:
RRF_score(B) = 1/(60+3) + 1/(60+1) = 1/63 + 1/61 ≈ 0.031
RRF 的设计哲学是:两种检索方式都给高排名的文档,最终得分就高。即使某一路的分数偏低,只要另一路排名靠前,文档仍然能获得不错的最终分数。这个算法最早由 Cormack, Clarke & Büttcher 在 2009 年提出,到今天已经成为 Elasticsearch、Azure AI Search、OpenSearch 等主流搜索引擎的默认融合策略。
四、代码示例:用 LangChain4j + Elasticsearch 实现混合检索
目前 LangChain4j 框架中,通过 Elasticsearch 集成实现混合检索是最成熟的方案。以下是基于 Elastic 官方博客(2026 年 3 月发布)的完整示例:
Maven 依赖:
<dependencies>
<!-- LangChain4j 核心 -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<!-- LangChain4j Elasticsearch 集成(支持混合检索) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-elasticsearch</artifactId>
<version>1.0.0-beta2</version>
</dependency>
<!-- LangChain4j Ollama(本地 Embedding 模型) -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>1.0.0-beta2</version>
</dependency>
</dependencies>
Java 代码:
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.ollama.OllamaEmbeddingModel;
import dev.langchain4j.rag.content.Content;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.elasticsearch.ElasticsearchEmbeddingStore;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import java.util.List;
public class HybridSearchDemo {
public static void main(String[] args) {
// 1. 配置 Elasticsearch 客户端
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost("localhost", 9200, "http")
);
// 2. 配置 Embedding 模型(这里用 Ollama 本地模型)
EmbeddingModel embeddingModel = OllamaEmbeddingModel.builder()
.baseUrl("http://localhost:11434")
.modelName("nomic-embed-text")
.build();
// 3. 创建 Elasticsearch 向量存储
ElasticsearchEmbeddingStore embeddingStore = ElasticsearchEmbeddingStore.builder()
.restClientBuilder(restClientBuilder)
.embeddingModel(embeddingModel)
.indexName("knowledge_base")
.build();
// 4. 纯向量检索(作为对比)
ContentRetriever vectorRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
// 默认使用 kNN 向量检索
.build();
// 5. 混合检索(BM25 + kNN + RRF 融合)
ContentRetriever hybridRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(5)
// 开启混合检索:自动使用 BM25 + kNN,通过 RRF 融合结果
.searchType(ElasticsearchEmbeddingStore.SearchType.hybrid())
.build();
// 6. 执行检索对比
String query = "怎么在 Spring AI 中整合 Milvus 向量数据库?";
System.out.println("=== 纯向量检索结果 ===");
List<Content> vectorResults = vectorRetriever.retrieve(query);
vectorResults.forEach(c -> System.out.println(c.textSegment().text()));
System.out.println("\n=== 混合检索结果 ===");
List<Content> hybridResults = hybridRetriever.retrieve(query);
hybridResults.forEach(c -> System.out.println(c.textSegment().text()));
}
}
这段代码的核心区别就在第 5 步——设置 .searchType(ElasticsearchEmbeddingStore.SearchType.hybrid()) 就能开启混合检索模式。Elasticsearch 底层会自动执行 BM25 + kNN 双路检索,然后用 RRF 融合排序。
五、主流向量数据库的混合检索支持情况
| 数据库 | 混合检索支持 | 稀疏检索方式 | 融合策略 |
|---|---|---|---|
| Elasticsearch | 原生支持 | BM25 | RRF(内置) |
| Milvus 2.5+ | 原生支持 | Sparse-BM25 / SPLADE | 加权融合 |
| Weaviate | 原生支持 | BM25 | 加权融合 |
| Qdrant | 原生支持 | 稀疏向量 | RRF / 加权 |
| OpenSearch | 原生支持 | BM25 | RRF(内置) |
| Chroma | 有限支持 | 需外部配合 | 手动融合 |
如果你用的是 Milvus 2.5,它原生支持 Sparse-BM25 全文检索,可以直接在一个 Collection 中同时存储稠密向量和稀疏向量,然后用 HybridSearchRequest 执行混合查询。Spring AI 框架中通过 MilvusVectorStore 可以对接 Milvus,不过目前 Spring AI 对混合检索的原生抽象还在社区讨论阶段。
六、生产环境的最佳实践
说几个我实际踩过的坑和经验:
1. 混合检索 + Rerank 是黄金组合
Azure AI Search 做过一组对比实验,结论很明确:
| 方案 | 召回准确率 |
|---|---|
| 纯关键词检索(BM25) | 基准 |
| 纯向量检索 | +8% |
| 混合检索(BM25 + 向量) | +15% |
| 混合检索 + Rerank | +25% |
加了 Rerank 之后提升非常明显。推荐用 bge-reranker-v2-m3 或 Cohere Rerank 这类 Cross-Encoder 模型,在融合结果的基础上做精排。
2. 权重需要根据场景调整
两路检索的权重不是固定的。如果你的知识库中专业术语、编号、代码片段比较多,BM25 的权重应该适当提高;如果是概念性、描述性的内容为主,向量检索的权重可以大一些。一般建议从 0.5:0.5 开始,然后根据实际效果调优。
3. Embedding 模型的选择很关键
中文场景别用英文 Embedding 模型,效果会差很多。推荐 bge-large-zh-v1.5、text-embedding-3-large(支持中文)或者国产的 Acme_Embedding。模型选对了,向量检索这路的基线就稳了。
七、常见误区
-
误区一:"向量检索已经理解语义了,不需要关键词检索。"
现实是向量检索对精确匹配天然不敏感。用户搜
ERR-4023这种错误码,向量检索很可能给你一堆跟 "错误" 语义相关但完全不包含这个编号的文档。 -
误区二:"混合检索就是把两路结果拼在一起。"
不是简单拼接,需要通过 RRF 或加权融合来统一排序。直接拼接会导致两路结果各排各的,相关性混乱。
-
误区三:"混合检索一定比单路检索好。"
不一定。如果查询是纯语义类的(比如 "如何提高团队效率"),向量检索可能就够了。混合检索的价值在于应对复杂多样的查询场景,确保 "不管用户怎么问,都能找到相关文档"。
面试高频追问
-
RRF 中的参数 k 怎么选?调大了调小了分别有什么影响?
k 是平滑常数,默认 60。k 越大,排名靠前的文档优势越不明显(分数更平滑);k 越小,头部排名的权重越大。一般不需要改,60 是经过大量实验验证的经验值。
-
混合检索的性能开销大吗?怎么优化?
确实会有额外开销——要执行两次检索。优化手段包括:两路并行执行(异步)、控制 Top-K 数量(每路 10-20 条即可)、在融合阶段做去重、用缓存加速高频查询。
-
除了 BM25,还有哪些稀疏检索方案?
SPLADE(Sparse Lexical and Expansion Model)是另一个选择。它不仅能做精确匹配,还能做词汇扩展——自动为文档补充语义相关的词。效果比 BM25 更好,但计算成本更高。
常见面试变体
- "你在项目中是怎么优化 RAG 检索效果的?"(引出混合检索话题)
- "向量检索和关键词检索各自的优缺点是什么?"
- "什么是 RRF?它解决了什么问题?"
- "
Spring AI/LangChain4j怎么实现混合检索?"
记忆口诀
混合检索三步走:双路并行(稠密 + 稀疏) → RRF 融合(只看排名不看分) → Rerank 精排(Cross-Encoder 加持)。记住 "向量抓语义,关键词抓精确,RRF 做融合,Rerank 做精排"
总结
混合检索的本质是取长补短——向量检索擅长语义理解但忽略精确匹配,关键词检索擅长精确匹配但不理解语义,RRF 把两路结果基于排名融合,再加上 Rerank 精排,形成生产级 RAG 系统的检索标配方案。面试中重点讲清楚 "为什么需要" 和 "怎么融合",基本就能拿高分。
