多路召回合并是什么
多路召回从不同通道各取一批候选:稠密向量抓语义、关键词一路抓专名、字段检索抓结构化条件、查询改写变体抓措辞差异。各路分数口径不同、候选高度重叠,直接拼接会让同一段内容的好几个近似版本占满上下文。合并要做三件事:按内容指纹与父文档 id 去重、把不同量纲的分数归一到可比空间(或用只看排名的 RRF 融合)、最后交给重排统一排序。
基本信息
| 常见通道 | 稠密向量、稀疏/关键词、字段过滤、查询改写变体 |
|---|---|
| 去重键 | chunk_uid 或内容指纹 + 父文档 id |
| 融合方式 | RRF(按排名)、加权归一化分数、直接交给重排 |
| 代价 | 候选量放大数倍,检索与重排开销同步上升 |
| 必备动作 | 记录每一路各自的召回结果,便于归因 |
典型使用场景
- 中文技术问答:向量一路抓语义、BM25 一路抓型号与命令名
- 多模型并行:两三个 embedding 模型各取候选,合并后重排
- 查询改写变体:原问题与改写问题同时检索,提高召回覆盖面
- 权限与业务过滤:结构化条件走字段检索,语义部分走向量检索
- 消歧:同一文档的小块密集命中时按父文档聚合选代表块
候选合并去重
候选合并去重
def merge(candidates):
seen, merged = set(), []
for c in candidates:
key = c.meta.get('chunk_uid') or hash(c.text)
if key in seen:
continue
seen.add(key)
merged.append(c)
return merged
all_hits = merge(hits_original + hits_rewrite1 + hits_rewrite2)合并策略
| 策略 | 适用 | 注意点 |
|---|---|---|
| 按 uid 去重后重排 | 候选来自同一文档的不同路径 | 去重键要稳定,不能用随机 id |
| RRF 融合排名 | 多路分数不可直接比较 | 只看排名,对量纲不敏感 |
| 加权分数融合 | 各路分数已归一化 | 权重需评测确定 |
| 按父文档聚合选代表块 | 小块密集命中同一长文 | 代表块选择规则要固定 |
多路召回会把候选量放大数倍,检索与重排的耗时、费用都要按放大后的规模估算;上线前用压测确认延迟没有突破预算。