多查询扩展是什么
让模型把一个问题改写成多条角度不同的查询,各自送去检索,再把各路的候选合并去重,按排名融合算法排序。它解决的是单条查询视角单一导致的漏检:用户问「超时怎么处理」,文档里可能写成「请求重试策略」「连接池等待超时」,一条查询很难同时命中这几种表述。代价是多花若干次检索和一次生成调用,而且必须做融合,否则多路结果只是简单堆叠。
基本信息
| 基本做法 | 一次生成 3 到 5 条改写查询,各自检索后合并去重 |
|---|---|
| 融合方式 | 常用倒数排名融合,只依赖名次,不需要统一打分口径 |
| 主要收益 | 召回覆盖面更广,对多义词与口语化提问改善明显 |
| 主要成本 | N 次检索加一次生成调用,延迟随条数上升 |
| 常见失效 | 多条改写高度重复,等于用更高成本做了同一件事 |
| 与改写关系 | 查询改写负责补全语义,多查询扩展负责扩角度,两者可叠加 |
典型使用场景
- 口语化提问:用户用日常说法,文档使用正式术语,需要多角度覆盖
- 多义词查询:同一词在不同模块含义不同,靠多条查询分流到各自语境
- 长尾问题:没有明确关键词,只能从多个角度逼近目标段落
- 中英混排资料库:同一问题分别用中英文表述检索,再合并结果
- 与重排配合使用:先靠多路召回扩大候选池,再用重排压缩到最终几条
生成与融合
生成与融合
把下面的问题改写成 3 条检索查询,要求:
1) 分别从不同角度表述,彼此不要高度重复;
2) 保留关键实体、数字与编号,不许改写术语;
3) 每行一条,除编号外不要任何解释。
问题:{{query}}
# 生成后先做相似度去重,重复的查询直接丢弃,避免白花检索成本倒数排名融合
倒数排名融合
def rrf(result_lists, k=60):
scores = {} # 只累计名次贡献,不比较原始分数
for results in result_lists: # 每一路检索结果都是一个有序列表
for rank, doc_id in enumerate(results, start=1):
scores[doc_id] = scores.get(doc_id, 0) + 1.0 / (k + rank)
return sorted(scores, key=scores.get, reverse=True) # 融合后的最终顺序
# 说明:k 是平滑常数,用于削弱头部名次的绝对优势,常见取值 60 附近
# 多路分数口径不同时不要加权求和,排名融合更稳,也不需要对分数做归一化效果不及预期时的排查
| 表现 | 原因 | 处理 |
|---|---|---|
| 与单查询结果差别不大 | 改写之间高度重复 | 在提示词里强制要求角度不同并做相似度去重 |
| 召回变多但准确率下降 | 多路噪声一并进了候选池 | 加一层重排,压缩到最终几条 |
| 关键实体丢失 | 改写时被同义替换掉 | 提示词要求原样保留术语与编号 |
| 延迟明显上升 | 查询条数过多 | 限制在 3 到 5 条,实测边际收益 |
| 结果顺序混乱 | 多路结果简单首尾拼接 | 改用排名融合统一排序 |
扩展条数 3 到 5 条性价比最高:再多下去召回覆盖的边际收益很小,延迟与检索成本却线性上升,尤其是检索侧本身有额度的场景。
融合阶段不要对多路分数做加权求和:不同检索器、不同模型的分数尺度不可比,排名融合只使用名次,避免了归一化带来的口径问题。