问题出现得很突然,日志倒是留得挺全。haystack-ai 2.4 里把 BM25 和向量两路检索结果丢给 DocumentJoiner(join_mode="reciprocal_rank_fusion", top_k=10),合并后所有文档 score 都是 0.016 左右,后面接的 threshold 0.7 过滤把候选全清空了。
刚开始盯着「两路 top_k 设得太小,重叠区几乎没有」,折腾了半个多小时,排掉了。
把日志和配置对完,能解释这个现象的基本就这几处。RRF 输出的是名次分,不是原始相似度,量纲完全不同。另一种情况是join_mode 用了 concatenate 时没有归一化,分数不可比。
不容易想到的是,阈值组件沿用了向量检索的经验值。
动手时先把合并前后每条的分数打出来对比
python -c "from haystack.components.joiners import DocumentJoiner;print(DocumentJoiner(join_mode='reciprocal_rank_fusion', top_k=10))"
接着换成基于分布的融合,保留可比分数
python -c "from haystack.components.joiners import DocumentJoiner;j=DocumentJoiner(join_mode='distribution_based_rank_fusion', top_k=10);print(j)"
然后把两路召回各自放宽到 50 条再合并
python -c "print({'bm25_top_k':50,'embedding_top_k':50})"
阈值改成作用在单路检索上,不放在融合之后
python -c "print('threshold 0.35 on retriever, not on joiner')"
别只看报错消失,验证要多做一步。融合后的 score 分布在 0 到 1 之间且有区分度,取前五条与人工判断一致,阈值过滤不再把候选清空。
补一句。RRF 的分数量级只有零点零几,任何用 0.7 这种阈值去卡它的写法都会全军覆没,要么换成分布融合,要么把阈值前移到单路检索里。这类问题最好一次改到位,反复小改反而容易把原因搅混。
