RAG 怎样按文档版本过滤检索候选
来源:17golang原创
时间:2026-10-24 20:29:40 346浏览 收藏
RAG 同时保留 v1、v2、v3 时,不能指望向量距离自动理解“只回答 v2”。更稳的做法是:入库时把版本写进每个 chunk 的元数据,请求进入检索链路后先解析允许版本,再把版本条件和租户、文档条件一起放进向量库过滤器。这样候选集从源头就不会混入不应使用的旧内容。
- 版本字段是检索约束,不是提示词里的软要求。
- 优先在向量检索阶段预过滤,避免 top-k 被旧版本占满后再丢弃。
- 用有效候选率、版本命中率、p95 延迟和空结果率同时评估,别只看向量相似度。
先把版本语义从向量相似度里拿出来
建议每个切片至少保存 tenant_id、doc_id、version、version_rank、effective_from 和 superseded_at。其中 version 用于精确选择,时间字段用于“回答某个历史时点”的查询,version_rank 只适合排序或回退,不应单独充当访问边界。
| 字段 | 检索用途 | 常见边界 |
|---|---|---|
| doc_id | 限定同一份文档 | 文档拆分后所有 chunk 必须一致 |
| version | 精确匹配 v2 | 不要混用字符串和数字格式 |
| effective_from | 按生效时间筛选 | 时区和闭区间必须固定 |
| superseded_at | 排除已被替代的内容 | 回滚时不能直接物理删除旧版本 |
例如知识库允许用户指定“产品手册 v2”,检索请求应先得到 allowed_version = "v2"。如果请求没有版本,才按产品的默认发布策略解析出一个明确版本;不要把“最新”留给生成模型猜。

把版本条件放进检索过滤器
以 Qdrant 为例,payload 过滤器的 must 表示条件全部满足。官方资料也建议为经常过滤的 payload 字段建立索引。下面的代码只演示“先锁定版本,再做相似检索”的关键关系,向量生成和客户端初始化省略。
官方地址:https://qdrant.tech/documentation/search/filtering/
from qdrant_client import models
def build_version_filter(tenant_id: str, doc_id: str, allowed_version: str):
# 租户、文档和版本必须同时满足,防止旧版本或其他租户混入候选。
return models.Filter(must=[
models.FieldCondition(
key="tenant_id",
match=models.MatchValue(value=tenant_id), # 先隔离租户
),
models.FieldCondition(
key="doc_id",
match=models.MatchValue(value=doc_id), # 再限定文档
),
models.FieldCondition(
key="version",
match=models.MatchValue(value=allowed_version), # 精确版本约束
),
])
query_filter = build_version_filter("tenant-a", "pricing", "v2")
result = client.query_points(
collection_name="knowledge_chunks",
query=query_vector,
query_filter=query_filter, # 过滤发生在候选检索阶段
limit=8,
with_payload=True,
)
如果版本不是离散标签,而是按时间回放,可以把 version 换成 effective_from 与 superseded_at > query_time 的范围条件。两种语义不要混在一个字段里,否则回滚和审计时很难解释“为什么这段内容被选中”。
多版本系统还要处理索引和写入的一致性:先写入带完整元数据的 chunk,再把该版本标记为可检索;删除或替代旧版本时保留可追溯标记。Qdrant 的分支检索示例也采用 payload 条件和覆盖标记来解析某个分支可见的版本。

用基线和压测指标判断是否真的改善
不要只比较平均耗时。先记录“混合版本召回后再过滤”的基线,再记录“带版本条件的预过滤”结果。下面是一个用于设计压测报表的示例,数字是说明格式的原创假设,不是某个向量库的固定承诺。
| 方案 | 有效候选率 | p95 延迟 | 空结果率 |
|---|---|---|---|
| 先召回 20 个再丢旧版本 | 84% | 310 ms | 8.6% |
| 版本字段预过滤后召回 8 个 | 96% | 190 ms | 3.1% |
这里的有效候选率指 top-k 中真正满足版本约束的比例,版本命中率则要单独核对最终上下文。生产压测至少固定:同一批问题、同一 embedding 模型、同一 top-k、版本分布、索引状态和冷/热缓存。否则一次换模型或换缓存状态,就无法判断收益来自过滤还是来自别的变化。
如果版本过滤后空结果率反而升高,不要立刻放宽到所有版本。先确认请求解析出的版本是否存在、过滤字段类型是否一致、索引是否覆盖该字段,以及当前版本是否完成了“可检索”发布。只有业务明确允许回退时,才按 v2 → v1 的顺序做第二次查询,并在答案中记录实际命中的版本。
版本过滤失效时按这张清单排查
- 结果混入旧版本:检查过滤器是否真的传给 query 接口,而不是只写进提示词;再抽样打印返回 payload 的
doc_id和version。 - 过滤后没有结果:检查版本值的大小写、类型、租户字段和索引构建状态;不要用“版本不存在”掩盖写入延迟。
- 回滚难以解释:保留版本和替代关系,用明确的发布状态控制可检索性,不要覆盖原 chunk。
- 性能忽高忽低:同时记录过滤命中数量、索引命中情况、top-k 和 p95,避免只看平均耗时。
常见问题
只在生成提示词里写“使用最新版本”可以吗?
不建议。提示词约束是软规则,无法阻止旧版本进入上下文;版本字段应在检索阶段过滤,提示词只负责解释已选上下文。
文档版本和 chunk 版本必须都保存吗?
至少要让每个 chunk 能追溯到文档版本。通常保存 doc_id 与 version 最清楚,必要时再加生效时间和替代关系。
过滤后候选太少怎么办?
先提高该版本的切片覆盖率或调整切片策略,再考虑有审计记录的版本回退;不要直接取消版本过滤来换取表面上的召回数量。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
119 收藏
-
101 收藏
-
246 收藏
-
118 收藏
-
489 收藏
-
469 收藏
-
439 收藏
-
168 收藏
-
239 收藏
-
491 收藏
-
114 收藏
-
319 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习