跳到主要内容

当你的 RAG 流读取时发生的 Wiki 中途编辑问题

· 阅读需 13 分钟
Tian Pan
Software Engineer

你平台团队的一名技术文档工程师正在移动一个段落。这并非比喻——她正真实地从入职指引页剪切一个章节,粘贴到运维手册中,删除第三页上的一个草稿占位符,并修改第四页上的一个弃用警告。整个编辑过程大约花费了她 11 分钟。而你的 RAG 摄取任务每 15 分钟运行一次。恰好在第 6 分钟时,任务启动了。

在接下来的 15 分钟里,你的检索索引包含了一个在她的脑海中从未在任何单一时刻存在过的 Wiki 状态。入职指引页仍然保留着那个章节。运维手册里却还没有。那个草稿占位符在被删除到一半时被捕获了,里面包含了一句她从未打算发布的占位语句。旧的弃用警告仍然被索引着。当一名工程师询问智能体“我们如何在这个服务中处理凭证轮换”时,模型从同一个来源检索到了矛盾的分块,并自信地合成出评分较高的那一个。答案呈现出一种任何人都没写过的错误形态。

这是大多数团队在发布时都没有注意到的失效模式:单一事实来源是事务性的,摄取是轮询的,而两者之间的鸿沟就是“脏读”存在的地方。

没人察觉的 Bug 形状

标准的 RAG 架构如此整洁,以至于让你放松了警惕。一边是 Wiki,另一边是向量数据库,中间是一个定时任务,负责遍历语料库、对每个页面进行分块、嵌入分块,并将它们更新插入(upsert)到索引中。每 15 分钟一次。每一小时一次。或者每天晚上一次。根据你的延迟预算来选。发布。

那个架构图里隐藏的谎言是:Wiki 是一个静态文档集。它不是。一家中型公司在 Confluence 或 Notion 上会累积 20 万到 50 万个页面,多名编辑会并发协作。在工作日的几乎任何时刻,每个页面都可能有人正在修改。摄取任务是一个没有一致性契约的读取者,扫描的是一个拥有多个写入者且没有读取快照的数据库。

用事务数据库的术语来说,这就是脏读。摄取任务在事务进行到一半时看到了数据行。在这种情况下,事务不是一条 SQL 语句——它是一个人的心智编辑过程,跨越多个页面,耗时数分钟,且没有摄取任务可以订阅的提交标记。流水线在思维进行到一半时捕捉到了这个世界。

下游的症状是团队会注意到的部分(如果他们注意到了的话)。智能体会给出一个自信的错误答案,而团队中没有任何人写过这样的内容。没有任何提交、没有任何差异对比(diff)、也没有任何审计日志包含模型刚刚生成的句子——因为这个句子是跨越了从未共存过的页面修订版本的综合体。等到用户标记它时,下一次摄取已经覆盖了那个不一致的状态,Bug 变得无法复现。

为什么删除的草稿总会“死而复生”

“飞行途中”的读取还有一个类似的失效模式,它更容易复现,却更难修复,并且在“智能体告诉了我一些甚至已经不在 Wiki 里的东西”这类工单中占了很大比例:复活的草稿。

一名作者开始写一个新页面,写了两个段落,然后去吃午饭,回来后觉得构思不对,删掉了整个页面。总寿命:40 分钟。你的摄取任务恰好在第 25 分钟运行了。这两个段落现在进入了你的向量索引。而它们来源的页面在 Wiki 端已不复存在。

在下一次摄取过程中,该页面消失了——但“消失”是摄取任务必须主动检测并采取行动的信息。如果流水线只是遍历当前 Wiki 中的内容并更新插入它发现的内容,它就收不到任何东西消失了的信号。这些孤儿分块将永远留在向量库中。它们会被检索。它们会被引用。作者废弃的草稿在一种真实且可观察的意义上,变成了知识库的永久组成部分,只要索引还在,就能在智能体的回答中被检索到。

这种失效模式会永久破坏 Wiki 与智能体之间的信任。用户会发现他们删除的东西其实并没有消失。一旦接受了这个教训,Wiki 就不再是人们放置高可信度草稿的地方。写作任何内容的成本都会上升,因为“删除”不再是一个起作用的原语。

轮询是问题所在,调度不是

当你注意到这些 Bug 时,第一直觉是提高摄取频率。每 15 分钟变成每 5 分钟,再变成每分钟。这压缩了脏读窗口,但并没有关闭它。从那个段落恰好落在窗口内的编辑者角度来看,一分钟的窗口仍然是无限大的。你支付了更多的重新嵌入成本——重新嵌入一个没有实质变化的语料库是生产环境下 RAG 中最大的一笔不合理开支——而且你并没有修复架构上的错配。

架构上的修复方案是停止轮询。现代 Wiki 平台都会发出编辑事件。Notion 有 Webhook。Confluence 有事件监听器。两者都可以在编辑落地的亚秒级延迟内,向你的流水线推送变更通知。摄取变成了事件驱动:一个 CDC 订阅者,接收“页面 X 的修订版本 Y 刚刚保存”的消息并处理该单一变更,而不是一个每隔 N 分钟遍历整个世界的任务。

CDC 驱动的摄取是关闭脏读窗口的架构重写方案。延迟从分钟级降至秒级。成本也随之下降——你只重新嵌入发生变化的内容,而不是整个语料库。但更重要的是,系统的模型终于与现实匹配了:作者提交,摄取反应,流水线停止尝试将移动的目标读取为静态快照。

这次重写的成本是实实在在的。CDC 需要消息代理、事件消费者、幂等处理、针对遗漏事件的重放语义以及死信处理。当它第一次出现问题时——比如事件流积压,你发现索引已经陈旧了 6 个小时——你会怀念定时任务的简单。但定时任务一直都在对你撒谎。CDC 流水线会在失败时告诉你,而这才是你真正需要的特性。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates