标准回答
为什么要混合检索
向量检索把 query 和文档映射到语义空间,擅长同义、改写、语义近似,但对精确的术语、产品编号、专有名词、罕见词容易失准;关键词 BM25 基于词项精确匹配,恰好补上这块,却抓不住语义改写。当查询既含明确术语又需语义理解时(如「某型号设备的故障处理」既要命中型号又要理解「故障处理」语义),混合检索收益最大。
实现路径一:单库内置 hybrid
Elasticsearch / OpenSearch、Milvus、Weaviate、Qdrant 等都内置了混合检索:同一份数据既建稠密向量索引又建 BM25 倒排索引,一次查询同时跑两路,库内用 RRF 或加权融合返回结果。工程量最小,运维一套系统即可。
实现路径二:两套索引应用层合并
向量库(存 embedding)和关键词引擎各自独立检索,各取 Top-K,再在应用层合并。灵活、可独立扩展,但需要自己实现融合、归一与去重逻辑。
融合与归一的关键
核心难点是两路分数量纲不可比:余弦相似度取值在 [-1, 1](归一化文本向量实践中多落在正区间),BM25 是无上界的正分。两种处理:
- RRF:对每路按排名计 1/(k + rank)(k 常取 60)再相加,只用名次、绕开量纲,几乎不用调参,是稳健默认。
- 归一加权:先把每路分数 min-max 或 z-score 归一到同一区间,再按权重(如 0.5/0.5,或按 query 类型偏移)相加。
无论哪种,都要对同时被两路命中的文档去重合并,取合并后得分,最后截断到 Top-N。
伪代码(应用层合并 + RRF)
def hybrid_search(query, k=60, top_n=10):
vec_hits = vector_search(query, top_k=50) # [(doc_id, score), ...] 按相关度降序
kw_hits = bm25_search(query, top_k=50)
scores = {}
for rank, (doc_id, _) in enumerate(vec_hits):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
for rank, (doc_id, _) in enumerate(kw_hits):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return [doc_id for doc_id, _ in ranked[:top_n]]RRF 用排名天然完成了去重合并与跨量纲融合。若想进一步提升精度,可把融合后的 Top-N 再喂给交叉编码器 reranker 精排。
常见误区
⚠️ 常见踩坑
别把未归一的向量相似度和 BM25 分数直接相加——量纲差异会让某一路完全主导,混合检索退化成单路。要么用只看排名的 RRF,要么先归一再加权。另一个误区是无脑对所有查询都上混合检索:纯语义或纯精确匹配场景下,多一路只增延迟不增收益,应按 query 类型权衡。
追问
追问 1:混合检索的两路权重怎么定?
没有标注数据时从均等权重(或直接用免调参的 RRF)起步;有验证集时按各路在评测上的贡献调权。更进一步可按 query 类型动态切换——含编号、术语、专名的精确型查询调高 BM25,开放语义型调高向量,由轻量分类器在线判定。
追问 2:什么时候混合检索反而不划算?
当查询场景高度单一时:纯闲聊式语义问答里 BM25 几乎无贡献,纯代码/编号精确查找里向量召回噪声大。这两种情况下多一路只增加延迟、成本和融合复杂度。应先用评测集量化每路的边际增益,确认两路都有正贡献再上混合。
追问 3:混合检索后还需要 rerank 吗?
视精度要求而定。混合检索解决的是「召回覆盖面」,rerank 解决的是「排序精度」。对答案质量敏感、上下文窗口紧张的场景,建议对融合后的 Top-N 再用交叉编码器精排,把最相关的少数片段顶到前面;对延迟极敏感或候选已很准的场景可省略。
🔗 相似问题
同一考点的不同问法,面试官可能换着问,一起刷更稳
没找到想看的面试题?把你想看的告诉我们 →
延伸学习
按主题分类的相关资源,便于系统复习