一个每晚运行的数据留存 worker(retention worker)会删除任何超过 30 天的用户消息。一个从 3 月初开始的长周期企业支持会话,到 5 月底仍然处于活跃状态。在第 41 轮对话请求进来时,你的 Prompt 组装器(prompt assembler)从同一个消息表中读取数据,而那个留存 worker 一直在悄悄地清理这个表。第 1 到 28 轮已经消失了。模型接收到的对话是从第 29 轮开始的,没有任何信号表明之前的对话曾经存在过。用户问道:“我们之前商定的 SLA 是什么?”模型自信地编造了一个数字,因为真正的答案在第 4 轮——而留存 worker 在前一天晚上把它删除了。
这不是模型故障。模型完全按照其应有的方式运行:从交给它的上下文中生成一个看似合理的答案。故障发生在更上游,处于两个团队之间的鸿沟中——每个团队都认为自己拥有消息表。
消息表是两个不同的东西
如果你问隐私或安全团队消息表是什么,他们会告诉你这是一个受留存策略约束的用户数据存储。留存策略规定:任何超过 30 天的数据,删除。Worker 每晚运行,策略得到执行,合规仪表板显示为绿色。
如果你问 AI 平台团队消息表是什么,他们会告诉你它是上下文窗口(context window)。Prompt 组装器在请求时读取它,用目前的对话内容填充模型,并让模型继续对话线索。
两个团队对表的理解在各自看来都是正确的。但没有一个团队真正理解这个表本质上是什么:它是一个多消费者数据集,其生命周期现在受策略最激进的团队管辖。留存团队拥有删除密钥。Prompt 组装只有读取路径。因此,留存团队默认赢得了每一次冲突——这种冲突在请求时表现为幻觉,而不是在删除时表现为错误。
这种不对称性至关重要,因为留存团队永远不会看到这个 bug。他们运行每晚的任务,行被删除,他们的 SLO 得到了满足。只有当有人通过 Prompt 构建路径重新读取现在已被截断的对话时,bug 才会出现,而这种读取发生在另一个系统中,由另一个团队负责,并针对不同的指标进行监测。这两个团队处理的是相同的数据,但他们的遥测(telemetry)数据直到客户投诉前都不会产生交集。
为什么模型无法察觉历史记录缺失
直觉上会将问题推给模型:“模型应该注意到对话是从中间开始的,并要求澄清。”但由于结构性原因,这行不通。模型无法知道第 1 到 28 轮曾经存在过。Prompt 组装器构建了一个上下文数组并交给模型,模型对世界的看法就是该数组中的任何内容。这里没有“轮次已删除”的标记,因为组装器从未放过这种标记。组装器只是按要求做事:从消息表中读取,将返回的任何内容格式化为对话,发送给模型。
从模型的角度来看,一个从第 29 轮开始的会话与一个从第 29 轮开始的会话是无法区分的。编号是你的记账方式,而不是模型的。如果你的组装器将轮次重新编号为从 1 开始,甚至连你的遥测系统都无法标记这种截断。模型对一个它从未见过的轮次中讨论过的 SLA 给出的自信回答,在通俗意义上并不是幻觉——它是模型在你提供的输入以及这些输入所暗示的对话框架下,成功完成了你交给它的任务。
这就是为什么在模型内部修复它是错误的举动。模型不是知道对话被截断的那一层。组装器才是。组装器是观察到“预期第 1 轮”与“实际最旧行是第 29 轮”之间差异的地方。这种观察必须在到达模型之前转化为信号,否则信号将永远丢失。
两个团队的问题
我见过的几乎每一个遇到这个 bug 的团队都有相同的组织架构。隐私和安全团队拥有数据仓库、留存 worker 和删除审计。AI 平台团队拥有 Prompt 组装器、推理路径和评估套件。他们位于不同的子部门,发布节奏也不同。他们有独立的事件频道,有独立的路线图。
从任何合理的责任划分来看,数据留存都属于隐私范畴。如果数据存放时间过长,他们是承担监管风险的人。他们也是知道哪些字段是敏感的、哪些司法管辖区需要更短的期限、哪些合同需要明确的按租户删除窗口的人。将数据留存交给 AI 平台团队将是一个更糟糕的结果。
实际的问题在于,留存被视为存储的一个属性,而不是读取路径的一个属性。留存 worker 对于该行已超过其期限的判断是正确的。但留存 worker 认为删除它是安全的判断是错误的,因为这里的“安全”意味着“不再有消费者依赖该行”,而 Prompt 组装器是一个无人告知留存 worker 的消费者。组装器是在留存策略制定多年后才加入该表的。删除路径从未更新以适应新的消费者。
这种破坏力之所以会叠加,是因为聊天记录留存正是那种设置一次后就会被遗忘的策略。它很可能是在“消息表”意味着“向阅读工单的代理显示的支持 UI 内容”时编写的。显示类消费者可以优雅地容忍截断——代理只是看到一个较短的对话线索。Prompt 消费者则不然,因为他们会将截断通过一个随机过程处理,并用看似合理的虚构内容填补空白。
“会话感知型保留”究竟该怎么做 通常最先被提出的修复方案是某种形式的“为活跃会话暂停删除”。这在精神上是正确的,但定义模糊。你需要定义什么是“活跃”,定义什么是“会话”,并且你需要让这些定义在原始保留策略从未考虑过的情况下依然有效。
第一版的实现可能是:如果一个会话中任何消息是在过去七天内写入的,则该会话是活跃的。只要会话是活跃的,保留策略就会跳过它。这在遇到以下情况前运行良好:一个支持会话因为客户等待工程团队处理而沉默了数周,然后重新开启。根据你的七天规则,该会话已变为非活跃状态,保留程序运行,早期的轮次被清理,模型在没有上下文的情况下进入下一条消息。七天窗口不仅没有减少错误,反而让错误更具间歇性且难以调试。
一种更健壮的模式是将“会话”视为保留的单位,而不是“消息”。当会话创建时,你会记录其保留周期。只要会话处于开启状态,该周期就不会针对其组成的条消息推进。当会话关闭时——通过显式的关闭事件、超时或一个能辩护其“完成”定义的状态机——保留程序将整个会话作为一个单位运行。保留工作器不再处理单条消息,而是处理整个会话;提示词组装器不再担心单条消息间的断层,因为会话要么完全存在,要么完全消失。
对于那些具有严格单条消息保留期限的司法管辖区(某些金融和医疗监管体系有此类要求)的一种变体是:在删除之前,将已删除的消息折叠到存储在免除保留或独立治理的存储库中的每会话摘要中。组装器读取摘要加上剩余的消息。模型看到的是一个连贯的线程,其中标明了压缩发生的位置,而你的保留工作器仍然满足其针对原始消息的策略。这与压缩(compaction)模式相同,也具有相同的风险:摘要必须保留关键的结构状态,而不仅仅是对话散文,否则你只是在更深的一层重建了 bug。
宁可显性失败,也不要隐性幻觉 修复的另一半位于组装器中。即使有了会话感知型保留,你仍然会遇到边缘情况——会话被错误分类、策略追溯应用、回填出错。组装器必须是发现问题的层级。
成本最低的版本是使用“哨兵”。组装器查询会话,获取一组行,并计算它预期看到的行是否确实全部存在。如果第 1 轮丢失了,组装器会执行以下三件事之一:向用户揭示断层(“根据你的保留设置,此会话中的早期轮次已被删除——请重新说明你希望我使用的任何先验上下文”),在提示词中插入显式的系统消息告诉模型第 N 到 M 轮不可用,或者拒绝执行请求并转接人工。
选择哪一个取决于产品界面。消费级聊天机器人可能可以直接向用户展示断层。企业级支持代理可能应该转接人工,因为错误的 SLA 答案成本很高,而人工调解的价值很高。高流量的内部工具可以告诉模型“你无法访问第 1 到 28 轮;不要断言本应出现在那里的事实”。这些都比默认的“隐性幻觉”要好,而且一旦组装器承担了检查的职责,它们都很容易实现。
你希望从组装器获得的信号与你从任何缓存中获得的信号是一样的:一种区分“没有可用信息”和“未找到相关信息”的方法。当这两种情况无法区分时,模型会充满信心地填补空白,而用户会信任这种信心,因为响应中没有任何标记不确定性的内容。
在客户发现之前捕获问题的审计 如果你运行的系统同时具备保留策略和提示词组装功能,你可能可以在发布修复方案之前先发布一个审计。审计很简单:遍历每个当前活跃的会话,针对当前的保留周期模拟提示词构建路径,并报告任何提示词将丢失旧于该周期消息的会话。该报告就是你的事故清单。每一个条目都是一个下次收到消息时会产生幻觉的会话。
即使在发布修复方案后,审计依然很有价值。保留策略会改变。新的司法管辖区、新的合同、新的产品线会带来不同的保留周期。每一次策略变更都是一次以新方式破坏组装器的机会,每天针对活跃会话集运行的审计将在客户发现之前捕获回归问题。审计成为了隐私平台与 AI 平台之间的契约:任何删除路径都必须声明其对活跃提示词组装的影响,而审计则验证该声明。
更深层次的组织转变是将聊天历史保留视为“系统契约”,而非“存储策略”。保留团队在宣布“我们要删除一列”的同一个论坛中宣布“我们要将保留周期从 30 天缩短到 14 天”。AI 平台团队像审查任何破坏性模式变更(schema change)一样对其进行审查。双方团队都接受消息表是具有多个消费者的共享基础设施,删除路径就像任何其他 API 一样具有向下兼容的契约。
没人买单的合规副作用 这里的架构洞见(如果有的话)是,你在执行合规计划时,并没有打算买下“一本正经的幻觉”。你买的是“三十天后删除数据”。幻觉是在应用该策略时,由于没有对 Prompt 构建的依赖关系进行建模而产生的副作用。合规程序的副作用通常对负责该程序的团队是不可见的,因为这些副作用会体现在下游其他团队的指标中。
隐私团队的工作很艰巨。他们的考核标准是删除操作是否发生,而不是下游系统是否能优雅地处理这种删除。AI 平台团队的工作同样不易。他们的考核标准是模型是否给出了优质的回答,而不是消息表(messages table)是否得到了良好的治理。这个交集中的 Bug —— 作为合规副作用而产生的一本正经的幻觉 —— 默认情况下无人负责。最有可能检测到它的团队(最接近用户的团队)往往最不可能知道其根本原因源自另一个子部门中的每日定时任务(nightly cron)。
做得好的团队通常都有一个共同的习惯。他们将任何共享数据集视为系统契约,并要求消费者和生产者都声明各自的预期。负责数据保留的程序(retention worker)声明它将删除什么以及何时删除。Prompt 组装器声明它预期读取的内容,以及在预期被违背时的行为。契约在客户发现问题之前,为两个团队提供了一个可以争论、测试和犯错的空间。如果没有这份契约,消息表就只是一个两个团队碰巧共享的表,而掌握删除密钥的团队则在无声无息中主宰着负责读取路径的团队。
会员专享
余下内容仅对会员开放。 会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
— 完整文章,包含未公开存档的部分 — 可落地的工作框架,附带权衡与决策依据 — 新文章抢先看,先于公开发布 登录以继续阅读→ 随时取消 · 一次订阅,畅读全部