RAG 文档更新与删除:怎样验收到片段和引用
更新与删除改变的是片段的可见性,而不只是向量行。更新先写齐新版本,再切换可读版本,以额外存储和清理成本换取构建失败时旧版仍可用;删除先阻断读取,再清理全部版本的片段。是否生效要同时查存储、检索上下文和引用,不能只看回答是否提到旧内容。
本文以 Spring AI 2.0.x、PostgreSQL/pgvector 为例;本次 API 核对依据为 Spring AI 2.0.2 官方参考文档,依赖仍应由项目 BOM 锁定,文档版本不代表本地已安装版本。下面的清单和状态机是应用设计建议,验收步骤尚未在你的服务运行。先按 RAG 评估指南 固定题集,再增加变更回放。
1. 为什么重新上传还会搜到旧规则?
原文从“年假五天”改为“年假十天”,重新切分后只剩一个片段。如果每次随机生成片段 ID,新片段入库不会自动带走旧的三个片段;即使复用部分 ID,多余片段仍可能残留。Spring AI ETL 负责读取、转换和写入,业务版本关系需要自己保存。
业务表以租户和文档 ID 为主键,记录当前版本、删除状态、权限版本;另建版本清单,保存内容哈希、切分配置、预期片段数和全部片段 ID。每片元数据带 tenantId/docId/version。片段 ID 按租户、文档、版本、切分配置和序号确定性生成 UUID,重试复用清单,避免重复入库。
2. 更新按什么顺序实现?
- 保存变更身份。 服务端校验文档归属,保存原文快照和任务代次;同一内容、同一配置的重复事件复用任务。
- 构建待发布版本。 解析、切分、生成向量并写入,按清单逐 ID 核对数量与内容哈希。部分批次失败保持旧版可读,失败批次可重试。
- 切换发布。 校验任务仍是最新、文档未删除,以旧版本为条件更新当前版本;条件不符就停止发布并清理孤立片段,避免迟到任务覆盖新版。
- 收尾对账。 在线检索限定当前版本;旧版片段异步删除,单文档缓存键包含查询、租户、权限范围、权限版本、文档 ID 和文档版本;跨文档答案还需绑定所依赖的文档版本集合,并在命中时复查可见性。定时对比清单与实际记录,补偿清理失败。
读取先查权威状态,再构造版本过滤;送入模型前再核对候选片段是否仍有效。查不到当前版就返回无证据,不回退旧版。公开文档改为 HR 限定时也要遵守这个顺序。过滤条件由服务端构造,PGvector 接入文档 说明了元数据过滤的转换方式。
“当前版本”是每篇文档自己的指针。单文档查询要同时限制 tenantId/docId/version;跨文档查询应在租户和权限范围内,按 (docId = A AND version = A当前版) OR (docId = B AND version = B当前版) 组合,不能用一个 version = v2 代表整个知识库。文档很多时,长过滤表达式会增加维护成本,可改用数据库中与权威状态关联的查询路径;这属于应用设计,不是 Spring AI 自动提供的版本管理。
| 阶段 | 权威指针/状态 | 存储中可能存在 | 读取允许使用 |
|---|---|---|---|
| v2 构建中 | 当前 v1 | 完整 v1、部分 v2 | v1 |
| v2 发布后、清理前 | 当前 v2 | 完整 v1、完整 v2 | v2 |
| 删除标记提交后 | 已删除 | 任意版本及迟到片段 | 无 |
这个对照说明为何不能把“行仍存在”直接等同于“仍可读取”:发布与删除的可见性由权威状态决定,物理回收可以稍后完成。
还有一种失败表现是当前版已写齐,却只召回少量片段。pgvector 官方过滤说明 指出,近似索引先扫描候选再应用过滤,严格的租户、版本条件可能让结果不足 Top-K。先按清单确认数据完整,再用执行计划与精确检索对照排查召回;pgvector 0.8.0 起可启用迭代扫描,但扫描仍有上限,扩大扫描也会增加延迟。不要为凑满 Top-K 放宽版本或权限过滤。
3. 删除返回成功就算完成吗?
要区分“已停止服务”和“片段清理完成”。先提交删除标记并增加任务代次,阻止旧任务再次发布;失效相关检索与答案缓存,再删除该租户该文档的所有版本。删除任务失败保留状态与重试次数,不能把超时记为成功。
下面是接入后的调用片段,两个变量分别来自认证上下文与已授权的文档记录,禁止直接拼接客户端过滤字符串:
FilterExpressionBuilder b = new FilterExpressionBuilder();
vectorStore.delete(b.and(
b.eq("tenantId", trustedTenant),
b.eq("docId", authorizedDocId)
).build());VectorStore 删除接口 支持按 ID 或过滤条件删除。业务仍应按清单复查;清理期间若迟到写入产生孤立片段,要再次回收,不能移除删除标记后放任文档复活。
4. 怎样执行更新与删除验收?
在隔离测试库,经同一入库服务准备 A 租户的 policy/v1 三片、v2 一片,以及 B 租户的同名文档。固定查询“年假几天”,保存每次任务号、状态、片段 ID、上下文和引用;以下都是待验证断言。
| 操作与故障注入 | 必须核对的结果 |
|---|---|
| 写 v2 时让一批失败 | 当前版本仍为 v1;上下文没有半成品 v2 |
| 恢复重试并发布 v2 | 只引用 A/v2;再次投递任务,片段数不增加 |
| v3 发布后才恢复 v2 任务 | 当前版本保持 v3,迟到片段进入清理队列 |
| 删除 A/policy,并重放旧入库事件 | 新请求没有该文档上下文;B 的片段数和引用不变 |
| 将当前版改为 HR 限定,以普通员工查询 | 不返回旧公开版;缓存和引用接口同样拒绝 |
删除清理结束后,对 Spring AI 默认 vector_store 表执行;自定义表名时替换表名,元数据键按上文保存:
SELECT metadata->>'tenantId' AS tenant,
metadata->>'version' AS version, count(*) AS chunks
FROM vector_store
WHERE metadata->>'docId' = 'policy'
GROUP BY 1, 2;预期 A 无记录、B 与删除前一致。更新场景则在旧版清理后核对 A 只剩当前版及预期片段数。pgvector 官方存储说明 展示了普通 SQL 更新、删除方式。Top-K 搜不到旧片段无法替代计数核对。
5. 验收边界在哪里?
约定删除标记提交后开始的新请求不得使用该文档;已在生成中的请求需要取消或输出前复查。会话历史、原始文件、备份和日志另列保留与清理策略,向量行删除不代表这些副本已擦除。
对照 小型检索实验 可以理解版本、删除标记的检查顺序;它采用词项匹配与原文摘录,没有向量库、模型生成和真实身份认证,不是完整 RAG。若允许模型发起删除,还需接入 工具调用护栏 的授权与人工确认。
主资料与记录入口
主资料为上文链接的 Spring AI ETL、VectorStore、PGvector 接入文档及 pgvector 官方仓库,均于 2026-11-06 查阅。近似检索可能漏召回,单次问答通过不证明存储已清空,也不证明所有回答正确。
回放时可用资料入口的验收表记录片段、上下文与引用,区分预期断言和实际观察。
