纯向量检索有个致命弱点:搜不准专有名词。这节记录我们怎么用混合检索补上这个洞。
可复现前提: 同一份 150 条标注集,其中 42 条含精确关键词(错误码、产品名、API 名)。
问题长什么样
用户搜 ERR_TIMEOUT_5031,向量检索返回的是一堆"关于超时的一般性描述",偏偏没有那篇专门讲 5031 的文档。因为在向量空间里,ERR_TIMEOUT_5031 和 ERR_TIMEOUT_5044 几乎一样近。
解法:BM25 + 向量,加权融合
def hybrid_search(query, k=5, alpha=0.5):
vec_hits = vector_search(query, k=30) # 语义
kw_hits = bm25_search(query, k=30) # 关键词
return rrf_fuse(vec_hits, kw_hits, k=k, alpha=alpha)
def rrf_fuse(a, b, k, alpha, c=60):
"""Reciprocal Rank Fusion:按排名倒数加权,不需要归一化两边分数。"""
scores = {}
for rank, hit in enumerate(a):
scores[hit.id] = scores.get(hit.id, 0) + alpha / (c + rank)
for rank, hit in enumerate(b):
scores[hit.id] = scores.get(hit.id, 0) + (1 - alpha) / (c + rank)
return sorted(scores.items(), key=lambda x: -x[1])[:k]用 RRF 而不是直接加权分数,是因为两边的分数量纲完全不同,归一化很难调稳。RRF 只看排名,省心。
效果
| 方案 | 整体 recall@5 | 含关键词的 42 条 |
|---|---|---|
| 纯向量 | 0.86 | 0.62 |
| 纯 BM25 | 0.71 | 0.93 |
| 混合(α=0.5) | 0.95 | 0.91 |
整体提升 9 个点,关键词场景提升 29 个点。
α 怎么调
我们扫了 0.1 到 0.9,发现 0.4~0.6 之间基本是平的,不用精调。如果你的场景专有名词特别多,往 0.3 调(更偏关键词)。