RAG 混合检索结果重复时怎么做去重和排序
来源:17golang原创
时间:2026-09-08 01:35:16 244浏览 收藏
RAG 的关键词检索和向量检索各自返回一份候选时,重复结果并不代表召回失败,真正的问题是“重复项按什么身份合并、合并后怎么排”。实用做法是先用稳定的文档 ID 归并两路候选,再保留最有价值的切片;排序上优先使用 RRF(Reciprocal Rank Fusion),因为 BM25 分数和向量相似度通常不在同一尺度上,不能直接相加。
一条可落地的规则是:切片层保留证据,文档层负责去重,排名层使用 RRF,最后再按业务需要做一次轻量重排。对于编号、型号这类精确词,提高关键词通道的候选覆盖比盲目调整向量阈值更有效。
- 先确定去重粒度:同文档不同切片不是同一条结果。
- 两路结果先带上来源排名,再融合,不要直接比较原始分数。
- RRF 负责稳定合并,Top-K 截断前保留可解释的 doc_id、chunk_id 和来源。
为什么混合检索会出现重复结果
关键词检索擅长命中“ORD-2026-0187”“ZX-450”这样的原词,向量检索擅长理解“订单被拒绝的原因”和“采购单审核失败”之间的语义关系。两路都找到同一份文档时,如果结果对象使用的是 chunk_id,看起来可能是多条;如果它们属于同一个 doc_id,对用户来说通常只是同一份文档的重复展示。
因此要先定义身份层级。推荐保留 doc_id 作为展示去重键,chunk_id 作为证据定位键。一个文档命中多个切片时,保留融合分数最高的切片;若回答确实需要上下文,再保留一条相邻且信息互补的切片。不要在召回刚返回时按文本完全相等去重,因为相邻切片文字不同,却可能来自同一篇手册。

两路候选怎样保留有用信息
每条候选至少保存 doc_id、chunk_id、rank、score 和 source。source 可以是 sparse 或 dense,不要在融合前丢掉它,否则出现重复或排序异常时很难判断是哪一路贡献了结果。
两路通常各取比最终 Top-K 更大的候选池,例如最终展示 5 条时先各取 20 条。这个数字不是固定答案,关键是给融合和去重留出空间。编号查询可以让关键词池更充分;自然语言查询则要避免向量池过窄。若过滤条件(租户、权限、文档状态)会影响可见范围,应在两路检索中使用一致的过滤规则。
def merge_candidates(sparse_hits, dense_hits, limit=5):
# 用文档 ID 做展示层去重,用切片 ID 保留证据位置
grouped = {}
for hit in sparse_hits + dense_hits:
doc_id = hit["doc_id"]
old = grouped.get(doc_id)
# 同一文档只留下融合前更有价值的切片,来源和排名仍然保留
if old is None or hit["score"] > old["score"]:
grouped[doc_id] = hit
# 这里先按候选分数排序,正式系统可在此处接入 RRF 分数
return sorted(grouped.values(), key=lambda item: item["score"], reverse=True)[:limit]
上面的示例只说明“按文档归并”的数据边界,不建议把两路原始分数直接放进同一个排序器。BM25 和向量相似度的数值范围、分布和含义不同,直接相加会让某一路因为量纲更大而长期占优。
RRF 和加权融合该怎么选
RRF 不比较原始分数,只看候选在各自列表里的名次。一个文档在关键词列表排名第 2、在向量列表排名第 5,就把两次排名贡献相加:
def rrf_score(ranks, rank_constant=60):
# ranks 是同一文档在各召回列表中的名次,名次从 1 开始
return sum(1.0 / (rank_constant + rank) for rank in ranks)
def fuse_by_rank(sparse_hits, dense_hits, limit=5):
# 先按 doc_id 建立两路排名,重复文档会自然合并
ranks = {}
for source_hits in (sparse_hits, dense_hits):
for rank, hit in enumerate(source_hits, start=1):
ranks.setdefault(hit["doc_id"], []).append(rank)
scored = [
{"doc_id": doc_id, "score": rrf_score(doc_ranks)}
for doc_id, doc_ranks in ranks.items()
]
# 最后再截断,避免某一路先截断导致另一条证据失去机会
return sorted(scored, key=lambda item: item["score"], reverse=True)[:limit]
Elastic 的官方文档把 RRF 定义为合并多个结果集的排名方法,并说明 rank_constant 越大,低排名结果的影响越明显;NVIDIA RAG Blueprint 也把 hybrid 检索和 reranker 作为可配置的检索链路。工程上可以先用 RRF 获得稳定基线,再根据评测数据调整候选池大小和重排策略。
只有当业务明确要求“精确编号必须优先”时,才考虑加权融合。此时先把两路分数分别归一化,再使用例如 0.65 * sparse + 0.35 * dense 的可解释权重;这个比例只是起点,不能当成通用最优值。没有标注查询集时,RRF 通常比凭感觉调权重更容易维护。

去重和排序完成后检查什么
不要只看“最终列表没有重复”就结束。至少准备三类小样本:包含订单号或产品型号的精确查询、完全改写的自然语言查询、同时包含编号和描述的混合查询。分别记录首个正确文档的位置、最终 Top-K 的文档重复率、两路各自覆盖了多少正确文档,以及同一文档保留的是哪个切片。
| 现象 | 优先检查 | 处理方向 |
|---|---|---|
| 列表有多条同文档结果 | doc_id 与 chunk_id 是否混用 | 文档层去重,切片层留证据 |
| 编号命中靠后 | 关键词候选池和过滤条件 | 扩大 sparse 候选,再观察 RRF |
| 语义结果被精确词挤掉 | 融合前是否过早截断 | 两路先取更大池,最后统一 Top-K |
还要把权限过滤、文档状态和索引更新时间纳入回归样本。若同一文档的旧切片与新切片同时出现,应先在索引侧处理版本状态,而不是靠展示层随机删除一条。对 RRF 的 rank_window_size 也要保持稳定,否则分页时可能出现顺序漂移。
RAG 混合检索常见问题
应该按文本内容去重吗?
不建议。文本相同只能说明内容相似,不能可靠识别文档身份。优先使用稳定的 doc_id,再用 chunk_id 定位证据。
RRF 是否还需要向量分数?
RRF 排序本身只使用各列表排名,但可以继续保存原始向量分数用于解释、调试或后续重排,不要把它和 RRF 分数直接混加。
什么时候应该调大候选池?
当正确文档只出现在某一路的较后位置,或去重后剩余文档不足 Top-K 时,先调大该路召回数量,再观察延迟和覆盖率。
同一文档要保留几个切片?
默认保留一个最高贡献切片;回答需要跨段上下文时再增加一个相邻且互补的切片,并设置每个文档的上限,避免上下文被单一文档占满。
混合检索的核心不是把两份列表简单拼接,而是把“身份、排名、证据”三个层次分开:用文档 ID 消除展示重复,用 RRF 处理不同分数尺度,用切片 ID 保留可追溯证据。这样再接入业务权重或 reranker,调参也会有清晰的落点。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
320 收藏
-
355 收藏
-
277 收藏
-
177 收藏
-
261 收藏
-
233 收藏
-
473 收藏
-
197 收藏
-
124 收藏
-
399 收藏
-
科技周边 · 人工智能 | 16小时前 | 性能优化 · 人工智能 · transformers · 批量推理 · Hugging Face Transformers dynamic padding attention_mask297 收藏
-
108 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习