FDE Times 数据在招 FDE 职位 826▼35 近 7 天在招职位数的变化(同口径比较):不计入本周新开始追踪的公司的职位近 7 天新增 77在招公司 154支持远程 25%美国薪资中位数 $202k招聘最多 Databricks 104
ENVI

前线部署工程师(FDE)的专业媒体

技能指南 · 指南

为什么 RAG 会信心十足地引用已作废的政策

嵌入向量没有时间概念,解法是给每个分块加有效期,每次查询带 as-of 日期,再按日期测试。

分享微博

要点速览

  • 修订版与被替换的旧版在嵌入空间中几乎重叠,只按相似度排序,无法分辨哪一版正在生效。
  • 每个分块都要带生效日期,每次查询都要带 as-of 日期,effective_to 应存为远未来的哨兵值。
  • 在 VersionQA 基准上,VersionRAG 得分 90%,朴素 RAG 只有 58%:不知道哪个版本有效的 RAG 会答错很多。
退款政策的时间线:v1 版本(30 天)的有效期到 2026年7月1日为止,此后由 v2 版本(14 天)接替,一直延续到哨兵值 9999-12-31。查询时点为 6月15日时引用 v1,查询时点为 7月15日时引用 v2。
有效期区间左闭右开,所以每个查询时点只有一个版本能通过过滤;在 7月1日 00:00 整,只有 v2 匹配。来源:本文示例与校验,参照 Particula Tech 的建议。

设想客户修订了退款政策:退款期限从 30 天缩短为 14 天,2026 年 7 月 1 日起生效。两个版本都存在向量库里。一名员工问聊天机器人“客户有多长时间可以申请退款?”,机器人信心十足地回答 30 天,还附上了引用。

问题不在大模型。Particula Tech 指出,修订版与它替换的旧版在嵌入空间中几乎是重复内容,过期版本很容易排在第一位。语义相似度没有时间概念。

在企业 RAG 项目中,前线部署工程师(FDE)几乎一定会遇到这个问题,因为每份政策、合同和流程迟早都会出新版本。本指南从 payload 讲到过滤条件,再到测试集,目标是让系统只依据所问时点正在生效的版本作答。

要做出什么,需要准备什么

最终成果是一条由三部分组成的数据管道:为每个分块附加有效期区间的摄取环节,按 as-of 日期过滤的检索环节,以及按日期设计的测试集。需要准备一个向量库(主示例用 Qdrant,另有章节介绍如何迁移到 Pinecone 和 pgvector)、一份至少有两个版本的文档,以及用于编写摄取代码的 Python。

为什么要费这番功夫?VersionRAG 是一种已发表的方法,与本文介绍的元数据过滤管道不同。其作者在 VersionQA 基准上测得 VersionRAG 准确率为 90%,朴素 RAG 为 58%,GraphRAG 为 64%。这一差距说明,面对会变化的文档,忽略版本的 RAG 会答错多少。

第 1 步:生效日期必须写在分块上

最常见的设计错误,是只在文档登记表里记录生效日期。Particula Tech 强调,生效日期必须附加到每个分块上,因为检索过滤的是分块,而不是文档。所以每个分块都要带上生成它的那个版本的完整元数据。

# 简化示例:单个分块的 payload
SENTINEL = "9999-12-31T23:59:59Z"

def make_payload(doc_id, version, text, effective_from, effective_to=None):
    return {
        "doc_id": doc_id,
        "version": version,
        "status": "effective",
        "effective_from": effective_from,           # RFC 3339 格式
        "effective_to": effective_to or SENTINEL,   # 绝不为 null
        "text": text,
    }

关键在 effective_to or SENTINEL 这一行。当前版本还没有失效日期,直觉上会把这个字段留空。Particula Tech 建议改为写入一个明确的远未来哨兵值,因为在大多数向量库中,缺失的键不会匹配范围条件。换句话说,被过滤掉的恰恰是最新的分块。

**检查:**统计 effective_to 为空或缺失的分块数量,正确结果是 0。

第 2 步:新版本到来时,关闭旧版本

2026 年 7 月 1 日,v2 以 effective_from = "2026-07-01T00:00:00Z" 摄取入库。与此同时,所有 v1 分块的 effective_to 都要更新为这个时间戳,status 改为 superseded。此后 v1 的区间是封闭的,v2 则一直延续到哨兵值。

元数据正确还不足以高枕无忧。Particula Tech 提醒,如果所有版本共用一个集合,只要有一次忘了加过滤条件,系统就会返回一条已被取代、却看似正确的条款。

他们更推荐的做法是把旧版本移到单独的归档集合。主集合只保存正在生效的版本,归档集合保留过期版本,供涉及过去的问题使用。

**检查:**不加任何过滤条件查询主集合。如果仍有分块带着 status = superseded,说明迁移到归档集合的环节出了问题。

第 3 步:每次查询都带 as-of 日期

按照 Particula Tech 的建议,每次查询都应带一个 as-of 时间戳,默认取当前时间,并只保留有效期区间包含该时点的分块。这样,“5 月份的时候,客户有多长时间可以退款?”这类问题,区别只在于传入的 as-of 日期不同。

Qdrant 支持对 RFC 3339 格式的 datetime payload 设置范围条件。must 子句只有在其中所有条件都成立时才为真,正适合把状态和有效期区间组合在一起。下面是简化后的过滤条件:

{
  "must": [
    { "key": "status", "match": { "value": "effective" } },
    { "key": "effective_from", "range": { "lte": "2026-10-26T00:00:00Z" } },
    { "key": "effective_to",   "range": { "gt":  "2026-10-26T00:00:00Z" } }
  ]
}

注意 lte 与 gt 的搭配。区间左闭右开,所以 7 月 1 日 00:00 整只有 v2 匹配,两个版本永远不会同时通过。

对于涉及过去的问题,去掉 status 条件,保留两个区间条件并换上新的 as-of 日期,同时检索主集合和归档集合。如果只查归档集合,问到当前版本已经生效的日期时会一无所获,因为该版本仍在主集合里。

**检查:**先把 as-of 设为 2026 年 6 月 15 日,再设为 2026 年 7 月 15 日,在两个集合中问同一个问题。第一次必须引用 v1(30 天),第二次必须引用 v2(14 天)。

换成 Pinecone 或 pgvector 会出什么问题?

每种向量库都有自己的陷阱,最好在给客户演示之前就搞清楚。

Pinecone 为元数据提供了 $gte/$lte 运算符,但只对数值有效。因此生效日期必须存成数值,比如 epoch 秒数,哨兵值也必须是数值。下面是简化示例:

{
  "status": { "$eq": "effective" },
  "effective_from": { "$lte": 1792972800 },
  "effective_to":   { "$gt":  1792972800 }
}

其中 1792972800 是 2026-10-26T00:00:00Z 对应的 epoch,相应的哨兵值是 253402300799(9999-12-31T23:59:59Z)。如果不小心把日期存成字符串,范围过滤就不会按预期工作。

pgvector 的陷阱更隐蔽。使用近似索引时,WHERE 子句要等索引扫描结束后才生效。结果是:请求前 10 条,索引返回最相近的 10 个分块,其中大多是旧版本,过滤后剩下的不足 10 条。

pgvector 的 README 建议使用迭代扫描(iterative scan);如果过滤字段只有少数几个不同取值,可以使用部分索引,status = 'effective' 正是这种情况。下面的语句仅作示意,请对照所用版本的 pgvector 文档核实:

CREATE INDEX ON policy_chunks
USING hnsw (embedding vector_cosine_ops)
WHERE status = 'effective';

**检查:**请求前 k 条,统计返回的行数。如果经常少于 k,就开启迭代扫描或使用部分索引。

第 4 步:按日期设计的测试集,是给客户的证明

过滤条件只解决了一半问题。同一项 VersionRAG 研究显示,在识别版本间隐性变化的问题上,各基线方法几乎全军覆没,准确率只有 0-10%,而 VersionRAG 达到 60%。

由此可见,即便元数据干净,“退款政策改了什么?”这类问题依然很难。与其向客户许诺,不如把它们放进测试集。

最小的测试集由三元组构成:问题、as-of 日期、预期版本。要覆盖修订日期之前、之后、恰好在切换时点的问题,再加几道询问改了什么的题。

每次重新摄取或更换嵌入模型时,都在 CI 中运行这套测试,检查引用的版本,而不只是答案文本。

哪些错误最常让管道失效?

把系统交给客户之前,再过一遍上面各步骤提到的五个错误。每个都有快速检查方法。

错误 症状 修复
生效日期只记录在文档登记表中 检索过滤没有可用字段 在每个分块上写入 effective_from、effective_to 和 status
当前版本的 effective_to 留空 最新版本从结果中消失 用远未来哨兵值回填
旧版本与新版本共用一个集合 忘加一次过滤就返回旧条款 把旧版本移到归档集合
在 Pinecone 中把日期存成字符串 范围过滤没有按预期生效 存 epoch 数值,哨兵值也用数值
pgvector 中 WHERE 在近似索引之后才生效 请求前 k 条,返回不足 k 条 开启迭代扫描或使用部分索引

如果时间只够修一处,先从第二行做起:统计缺少 effective_to 的分块只需几分钟,却能堵住最难察觉的故障。

在客户现场,这项技能是什么样子?

到客户那里,第一件事不是写代码,而是去问文档的负责人:版本怎么标注,生效日期存在哪里,由谁来判定某个版本已被取代。

答案往往藏在文件名里、某张电子表格里,或者只在法务部某个人的脑子里。拿不到这些信息,第 1 步就无从做起。

如果在申请应用 AI 方向的 FDE 岗位,可以留意那些提到基于内部文档、合规文档或法律文档做 RAG 的职位描述。

简历上不要只写“搭建了 RAG”,而要写清楚具体解决了什么问题:杜绝引用过期版本,并用带 as-of 日期的测试集加以证明。招聘方会看出你理解真实的客户数据。

向量库里只有一个版本时,任何演示都能跑通。客户发布第一次修订的那一天,系统才真正接受考验。

这篇文章对你有帮助吗?

5 个信源
路线图上的下一篇 · 阶段 3: AI 应用一晚搭好 ElevenLabs 订单查询语音 Agent最容易出错的不是声音和提示词:查不到订单时 API 若换了 JSON 结构,Agent 可能把别人的订单状态读给来电者。