问题出现得很突然,日志倒是留得挺全。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 这种阈值去卡它的写法都会全军覆没,要么换成分布融合,要么把阈值前移到单路检索里。这类问题最好一次改到位,反复小改反而容易把原因搅混。

作者 9823189