向量检索召回很多但答案变差:按来源分组保留上下文多样性
来源:17golang原创
时间:2026-08-29 13:03:07 261浏览 收藏
知识库问答里有一种很隐蔽的退化:召回数量从 5 调到 20,检索指标看起来更宽了,回答却开始围着同一份长文打转。常见原因不是向量模型突然失效,而是相似片段集中来自同一个 source_id,有限的上下文窗口被重复证据占满。
先按来源分组,再在每组中保留最高分片段,最后用一个明确的
top_k配额合并候选,通常比盲目增大召回数更容易保住上下文多样性。
要点速览
top_k只是最终送入模型的数量,不等于有效证据的来源数量。- 用
source_id分组能先识别“同文档霸榜”,再决定每组最多保留几段。 similarity_score仍负责组内排序,分组不会把低相关片段硬塞进上下文。- 小实验应同时比较来源数、分数分布和最终回答引用,不能只看召回条数。
先复现同一来源占满 top_k 的问题
假设检索器返回 8 个候选片段,分数已经按降序排列。前 5 段来自一份部署手册,后 3 段来自两个故障复盘。若直接取前 5 段,模型看到的是同一来源的连续解释,另两份材料即使包含反例,也进不了上下文。
candidates = [
{"chunk_id": "a-01", "source_id": "deploy-guide", "similarity_score": 0.92},
{"chunk_id": "a-02", "source_id": "deploy-guide", "similarity_score": 0.91},
{"chunk_id": "a-03", "source_id": "deploy-guide", "similarity_score": 0.90},
{"chunk_id": "a-04", "source_id": "deploy-guide", "similarity_score": 0.89},
{"chunk_id": "b-01", "source_id": "incident-2024-11", "similarity_score": 0.88},
{"chunk_id": "c-01", "source_id": "runbook-cache", "similarity_score": 0.87},
]
top_k = 5
selected = candidates[:top_k]
print(len(selected), {item["source_id"] for item in selected})
这段代码会打印 5 和只有一个来源。它没有违反相似度排序规则,却把“证据覆盖面”交给了文档长度和切块方式。这里先别急着调大 top_k,因为上下文窗口、重复内容和模型注意力都会一起增加。
用 source_id 分组,再做组内保留
一个可控的后处理步骤是先执行 group_by_source。它不重新计算向量,只把候选片段按真实来源归档;每一组内部仍按 similarity_score 从高到低排列。
from collections import defaultdict
def group_by_source(candidates):
groups = defaultdict(list)
for item in candidates:
groups[item["source_id"]].append(item)
for items in groups.values():
items.sort(key=lambda item: item["similarity_score"], reverse=True)
return groups
groups = group_by_source(candidates)
selected = []
per_source = 2
for source_id, items in groups.items():
selected.extend(items[:per_source])
selected.sort(key=lambda item: item["similarity_score"], reverse=True)
selected = selected[:top_k]
print([item["source_id"] for item in selected])
这里的关键节点是 source_id、group_by_source 和 selected:候选先进入来源组,组内截断后再汇总,最终仍受 top_k 限制。示例输出会包含 deploy-guide、incident-2024-11 和 runbook-cache,但并不保证三个来源各占相同数量。

图中只画这段实验真正存在的关系:候选片段进入 group_by_source,按 source_id 分组,再汇入 selected。如果某来源只有一个高相关片段,它不会为了凑数被复制。
如何决定每个来源的配额
固定的 per_source = 2 适合做第一版实验,但线上通常需要把候选数量、来源数和上下文预算一起看。可以先设一个较宽的召回池,再用每组上限控制重复度:
- 知识库来源很多时,每组保留 1 段,优先观察覆盖面;
- 来源较少但每份文档内部有互补章节时,每组保留 2 段或 3 段;
- 某些来源是权威规范时,可以给它额外配额,但要在配置中显式记录,不要让长文天然获得特权。
别把“来源多”直接当成“答案可靠”。如果一组的最高 similarity_score 都明显低于另一组,仍应先检查查询改写、切块粒度和元数据过滤。分组是控制证据结构的手段,不是相关性校验的替代品。

运行检查:别只打印最终片段
小实验至少输出三个结果:最终片段数、来源集合、每个来源的分数范围。这样才能分辨是配额生效,还是候选本身就只有一个有效来源。
source_stats = {}
for item in selected:
source_stats.setdefault(item["source_id"], []).append(item["similarity_score"])
print("selected=", len(selected))
print("sources=", sorted(source_stats))
for source_id, scores in sorted(source_stats.items()):
print(source_id, min(scores), max(scores))
检查时重点看两种异常:第一,selected 数量不足,说明召回池或每组上限过小;第二,来源集合仍只有一个,说明候选池里没有其他来源,继续调后处理没有意义。只有把来源集合扩展开,才值得比较最终回答是否减少了重复解释。
常见问题:来源多样性和相关性怎么取舍
按来源分组会不会把最高分片段丢掉?
会有这个可能,所以应保留组内最高分,并用离线问答集比较答案正确率,而不是追求来源数量最大化。
top_k 应该先调大还是先做分组?
建议先固定一个能放进上下文的 top_k,完成分组实验后再调大;否则重复片段和窗口压力会同时变化。
只有一个 source_id 时怎么办?
先查元数据过滤、切块和召回池是否只覆盖一份文档。后处理无法凭空制造第二个来源。
如何判断多样性真的改善了回答?
把来源集合、引用覆盖和答案评测放在同一次实验里记录;仅看去重后的条数,不能证明回答更准确。
把实验收敛成一条可观测规则
在 RAG 链路中,向量召回负责找到候选,来源分组负责控制证据结构,selected 再把有限候选交给生成模型。上线前给这三个阶段分别打日志,至少记录 top_k、来源数量、每组保留数和最终引用来源。这样答案变差时,能判断问题发生在召回不足、来源过度集中,还是生成阶段没有使用已提供的证据。
-
346 收藏
-
235 收藏
-
387 收藏
-
447 收藏
-
360 收藏
-
170 收藏
-
221 收藏
-
501 收藏
-
495 收藏
-
319 收藏
-
208 收藏
-
348 收藏
-
397 收藏
-
430 收藏
-
322 收藏
-
202 收藏
-
科技周边 · 人工智能 | 6小时前 | 异步任务 · 人工智能 · openai · 工程实践 · Batch API · OpenAI Batch API 部分结果 cancelling cancelled output_file_id error_file_id250 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习