一个知识库问答系统给"差旅住宿标准"答出了 2021 版制度里的 400 元/晚,而现行标准是 600 元。检索同时命中了新旧两份文档,而"旧版"的片段因为"历史点击多"分数更高——于是系统自信地给出了过期答案。
RAG 系统里最危险的不是"没有答案",而是"用过期答案回答了现行问题"。这个判断很关键:模型确实能"看出这是 2021 版",但它不一定会"因此不用它"——因为它的任务是"回答问题",而不是"判断资料时效"。所以"时效"必须变成"检索条件"。
而"过期"这件事必须在"检索"这一层被处理,而不是靠模型"看出来这是旧的"。
1. 三种"过期"的形态
| 形态 | 例子 |
|---|---|
| 被新版本替代 | 制度 2021 → 2024 |
| 条件失效 | 某政策暂停执行 |
| 完全废弃 | 部门已撤销 |
第二项最隐蔽:**文档本身没被替代,只是"暂时不适用"**第二项的"变异"是"局部失效":文档大部分仍有效,只有某一章被新政策覆盖。此时"整篇标记过期"会误删有用内容,而"不标记"又会让那一章继续答错。
——于是它仍在索引里、仍能命中,而"暂停"这件事只写在某份通知里。
第三项最容易被忽略:结构变更后,整类文档都不该再出现,而"删除"这个动作通常没人做。
2. 四种退场机制
| 机制 | 效果 |
|---|---|
检索时过滤(status/effective_to) |
最干净 |
| 排序降权(旧版降权) | 缓解,仍可能出现 |
| 索引外移(过期文档不入索引) | 彻底,但需重建 |
| 标注版本让用户判断 | 兜底 |
第一项的"优势"在"候选集":过期文档根本不进入 Top-K,于是"模型看到的就是有效资料"——这比"看到后再判断"可靠得多。
第一项是"首选":在检索查询里加 WHERE status='active',于是过期文档根本不进入候选集。
第三项适合"归档类"语料:过期文档移到单独的"历史"索引,默认不查,需要时才查。
第四项是最低要求:**结果里标注"2021 版"**第四项的"兜底"价值在于"用户可判断":即使过滤漏了,用户看到"2021 版"也会自己去核对——而"无声的过期答案"才是最危险的。
,让用户知道自己在看旧版——它不能替代过滤,但能兜住"过滤漏了"的情况。
// 文档元数据(退场所需的最小字段)
doc:
id: travel-2021
status: superseded # active | superseded | suspended | archived
effective_from: 2021-01-01
effective_to: 2024-03-31
superseded_by: travel-2024 # 指向新版本(可用于"已更新"提示)
3. 检索层怎么用这些字段
| 做法 | 说明 |
|---|---|
过滤 status=active |
默认行为 |
按 effective_to 过滤 |
需"当前日期" |
| 结果去重(同主题只留最新) | 需要"主题聚合"键 |
| 显式"历史查询"模式 | 用户主动选择 |
第一项与第二项的组合是"标准做法":status=active 且 effective_to 为空或晚于今天。
第三项的"前提"是"入库时打了主题键":同一制度的新旧两版必须共享 topic_key,否则"分组"无从进行——而这件事通常没人管。
第三项处理"同一主题多版本并存"的情况:按 topic_key 分组,只留 effective_from 最新的。而它需要"文档标了同一个主题键"——这是入库时的责任。
"时效权重"可以作为"过滤之后"的补充,但要清楚它治不了根:
// 三种做法的可靠性(从高到低)
1 检索过滤:过期文档不进候选集 → 不会错
2 主题去重:同主题只留最新 → 不会错(前提:topic_key 正确)
3 时效加权:新版分数 +x,旧版 -x → 仍可能错(分数被点击量主导)
// 结论:1、2 是"保证",3 只是"倾向"
4. "暂停执行"怎么表达
| 做法 | 问题 |
|---|---|
改 status=suspended |
需要人记得改 |
| 加一条"覆盖文档" | 容易被漏看 |
| 让模型判断 | 不可靠 |
第一项最直接,但它依赖"有人去改"——而"暂停"往往是"发个通知"就结束了。
所以实践中用"有效期管理":给文档设 effective_to,到期自动退出检索。第四项的"自动"很关键:"到期"是时间驱动的,"暂停"是事件驱动的——前者可以被定时任务兜住,后者只能靠人。所以"能写成到期日的,就别用状态标记"。
于是"暂停"= "把到期日改成昨天",动作简单且可审计。
5. 怎么发现"该退场但没退场"
| 手段 | 得到 |
|---|---|
按 effective_to 扫描 |
到期未处理清单 |
| 同主题多版本检测 | 需要去重 |
| 点击/引用分布 | 旧版是否仍被引用 |
| 抽样人工核对 | 兜底 |
第三项的诊断方向很重要:它把"该退场"的问题转成"新版可检索性"的问题。而修法完全不同——不是"删旧版",而是"让新版能被搜到"(补标题、补同义词)。
第三项很实用:如果"旧版"的点击量仍然很高,说明"新版没有被找到"——于是问题不在"旧版该退",而在"新版的可检索性"。
-- 到期未退场的文档(每周跑一次)
SELECT id, title, effective_to, status FROM doc_meta
WHERE effective_to < CURDATE() AND status = 'active'
ORDER BY effective_to;
-- 输出应当为空;非空即"待处理清单"
"入库即标注"是最省力的落地方式:在文档入库的流程里强制要求 effective_from 与 status,缺失则拒绝入库。于是"退场"从"事后清理"变成"数据质量约束"。
// 入库校验(三项,缺一拒绝)
1 status ∈ {active, superseded, suspended, archived}
2 effective_from 必填(未知时填"1990-01-01"并标"待确认")
3 superseded_by 在 status=superseded 时必填(指向新版本)
// 收益:从源头保证"能按状态过滤",而不是事后补数据
6. 排错:五种"旧制度仍在答案里"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 答出过期数字 | 未按状态过滤 | 加 status 过滤 |
| 新旧都返回 | 无主题去重 | 按主题留最新 |
| 已暂停的政策仍生效 | 无有效期管理 | 设 effective_to |
| 旧版片段分数更高 | 历史点击多 | 加时效权重 |
| 用户不知道是旧版 | 无版本标注 | 结果里标版本 |
第四项是"排序"问题,而它不该用"调权重"解决:"过滤"比"降权"可靠第四项的"排序"陷阱在于"分数高不等于该被选":历史点击多只说明"它曾经有用",而"现在是否有效"是另一件事——两件事混在同一个分数里,就无法分开调。
,因为降权仍有概率被排上来。
最后一个"用户视角"的设计:当检索到"被替代"的文档时,不要直接丢弃,而是"用新版本替换"——回答里写"该制度已于 2024 年更新,现行标准见《差旅制度 2024》第 3 条"。于是"用户问的是旧概念"也能被安放到正确的资料上。
// 两种处理"命中旧版"的方式
差:直接过滤掉 → 用户问"2021 年标准"时答不出
好:替换为新版 + 说明 → 既回答又纠正
// 实现:superseded 文档保留"重定向"关系(superseded_by)
7. 边界:什么情况不必做退场机制
| 场景 | 为什么 | 做法 |
|---|---|---|
| 语料只增不改(如日志) | 无"版本"概念 | 不做 |
| 单一年份(无历史) | 无并存 | 不做 |
| 已有强权限/审批流 | 由流程保证 | 跟随 |
| 用户自己维护的小库 | 出错可见 | 不做 |
最后一条与"成本"有关的边界:退场机制的成本主要在"数据维护"(谁标注有效期、谁复核),而不在"代码"。所以它应当被设计成"入库流程的一部分",而不是"事后清理任务"。
判断口径一句话:"同一个问题"会不会有"多个版本"的答案。会(制度、规范、价格表),就必须有退场机制;不会(技术文档一次性写入),只需"过时提醒"。
8. 自测
- 三种"过期"形态里,哪一种最隐蔽?为什么它"仍在索引里仍能命中"?
- 四种退场机制里,哪一种最干净?它与"排序降权"的可靠性差别在哪?
- "按
effective_to过滤"需要什么前提?为什么它比"改 status"更可靠? - "同主题多版本"该怎么处理?它需要入库时的什么责任?
- "旧版点击量仍很高"说明了什么?问题该往哪修?
- 判断"是否需要退场机制"的那个问题是什么?