跳到主要内容

那些悄悄共享长期记忆的智能体 A/B 测试变体

· 阅读需 12 分钟
Tian Pan
Software Engineer

你运行了职业生涯中最干净的一次 A/B 测试。流量分配是 50/50,哈希函数看起来没问题,指标流水线没有造假,留存样本(holdout)得到了保留,经过三周的分析,得出了一个明确的胜者:变体 B 将任务完成率提升了 4 个点,统计团队对 p 值也没有异议。你将其 100% 发布了。发布两周后,你启动时所依据的核心指标却漂回了基准线,而且没人能解释原因。

这就是那个花了一些时间才看清的部分。两个变体都在向同一个长期记忆库写入并从中读取。变体 A 中的用户写入了一条记忆,比如 “该客户更喜欢直截了当的总结”,第二天,当同一个用户恰好处于变体 B 时,变体 B 的 Agent 加载了该记忆并将其读入其提示词(prompt)中。反向的情况也在另一个方向发生。实验并不是在比较提示词 A 与提示词 B。它是在比较 “读取提示词 B 形状记忆的提示词 A” 与 “读取提示词 A 形状记忆的提示词 B”。结果是受污染的联合分布的平均值,而发布则是回归到了同一曲面上的另一个点。

这种失效模式并不罕见。每一个在实验框架已经就绪后才将长期记忆接入 Agent 产品上的团队都面临这种风险,而且大多数这类团队尚未察觉。实验框架是为一个独立单位是用户、分区原语是用户 ID 哈希、且变体可以写入的唯一状态是其自身日志行的世界而设计的。Agent 记忆悄无声息地打破了这三个假设。

分区原语不再匹配独立单位

经典的 A/B 测试依赖于个体处理效应稳定性假设(Stable Unit Treatment Value Assumption —— SUTVA)—— 即一个用户的表现不受任何其他用户处理分配的影响。构建你实验系统的平台团队将这一假设编码到了分区原语中:对用户 ID 进行哈希处理,路由到变体,记录结果,进行比较。数学上之所以行得通,是因为用户是独立单位且分区是干净的。

添加了长期 Agent 记忆后,独立单位就不再是用户了。它是 Agent 在变体调用期间读取或写入的每一个持久化表面的闭包。该闭包包括过去对话的向量库、Agent 维护的每个用户的个人资料摘要、返回先前原样答案的语义缓存、用户上传文档时更新的每个用户的 RAG 索引,以及记录 “该 Agent 尝试了 X 但失败了” 的工作流记忆。当平台团队编写分区原语时,这些表面都没有出现在他们的心理模型中。

更糟糕的是,这种污染是双向且不对称的。变体 A 写入具有变体 A 提示词特征的记忆。变体 B 随后通过变体 B 的提示词模板读取这些记忆。这种偏差不仅仅是泄露;它是处理方式(treatment)与另一个处理方式所看到的输入分布之间的协方差。推荐系统的统计文献对此有一个称呼 —— 共生偏差(symbiosis bias)—— 这里的结论同样成立:每个变体测得的性能在一定程度上是其竞争对手生成的训练数据的函数。

eBay 的发现令人警醒。他们的团队在比较竞争同一批买家的双边市场算法时,测得处理效应被高估了 100%。Airbnb 在访客费用实验中测得 32.6% 的虚高。这些数字源于网络干扰,而非记忆干扰,但结构性机制是一样的:共享状态耦合了实验框架视为独立的变体。Agent 记忆只是同一耦合方式的一个更快、更密集的版本,因为每次变体调用都会在同一轮次中同时读取和写入共享状态。

没人声明过的泄露表面

这种现象难以检测的原因是,团队中没有一个人能掌握变体读取和写入内容的完整图景。提示词工程师了解提示词。记忆工程师了解记忆库。基础设施工程师了解实验框架。污染存在于它们之间的接缝处,而这个接缝没有人负责。

常见的泄露表面包括:

  • 对话记忆库(Conversational memory stores):由过去的轮次填充的向量索引,通常按用户 ID 划分命名空间,但不按变体划分。变体 A 写入一个嵌入(embedding);变体 B 在下一轮检索它。
  • 用户资料摘要(User-profile summaries):Agent 作为系统上下文加载的定期更新的 “我们对该用户的了解” 速记。提示词鼓励激进个性化的变体会比对照组变体更新这份具有更强主张的速记,而对照组变体在下个会话中会继承这些主张。
  • 语义答案缓存(Semantic answer caches):当新查询在语义上足够接近时返回先前答案的查找表。生成缓存答案的变体没有编码在缓存键(cache key)中,因此任何未来的变体都可以命中它。
  • 每用户 RAG 索引(Per-user RAG indices):用户上传的文档、Agent 对其计算的摘要、派生的元数据字段。如果变体影响了索引内容或摘要的编写方式,这种影响将比变体本身存在得更久。
  • 工作流状态与技能(Workflow state and skills):“Agent 了解了用户的仓库布局” 之类的记忆。一个进行激进探索的变体填充这些内容的速度更快,而对照组变体随后读取的状态比它自己构建的状态更丰富。
  • 折叠回记忆的反馈信号(Feedback signals folded back into memory):与响应一起存储的点赞和点踩事件,用于未来的检索重排序。一个诱发更多正面反馈的变体现在也会偏置另一个变体的检索。

所有这些情况的共同模式是,记忆层的设计早于实验需求,其隔离原语以用户或租户为键,而不是以实验单元(experiment cell)为键。框架的评分标准是 “用户看到的是变体 A 还是变体 B?”,而记忆库的评级标准是 “这个用户的 ID 是什么?”。这两个原语没有结合在一起,而且也没人规定它们必须结合。

为什么全量发布的结果与实验结果存在偏差

当实验以 50/50 的比例运行时,污染大致是对称的——变体 A 读取被变体 B 污染的记忆的频率,与变体 B 读取被变体 A 污染的记忆的频率基本相同。测得的 delta 仍然反映了变体之间的真实差异,只是比在纯净实验中更小、噪声更多。经历过这种情况的团队将实验阶段描述为具有欺骗性的“表现良好”。

出问题的是全量发布阶段。随着变体 B 从 50% 增加到 100%,记忆库开始只包含变体 B 形式的记忆。变体 B 智能体现在读取的是它在实验期间从未见过的记忆分布。行为发生了偏移,指标发生了偏移,上线后的数据与实验数据不符。团队的第一直觉是怀疑分析方法、对照组(holdout)或季节性因素——任何事情除了记忆层,因为在实验设计时从未提及记忆层。

在某些产品中,这种偏移是有利的:变体 B 读取了与其 prompt 更契合的记忆,从而变得更强。在另一些产品中,则是不利的:变体 B 之前秘密地受益于变体 A 形式的记忆,这些记忆教会了它如何从自身的脆弱性中恢复,而失去它们后,它的表现就会下降。方向取决于具体产品,但教训是通用的。在被污染的状态上测得的 delta 无法外推到全量发布后产品实际运行的纯净状态分布。

按实验单元而非仅按用户划分记忆

AI 领域之外最接近的类比是推荐系统的 A/B 测试,平台团队最终不得不承认,数据管道耦合了变体,标准的划分原语(partition primitive)已不再适用。在那里奏效的解决方案——为每个变体物理隔离状态——在这里也是正确的起点。

缩小差距的实践模式:

  • 实验作用域的记忆命名空间。 将记忆键构造为 (user_id, experiment_id, variant_id) 而不仅仅是 user_id。变体 A 写入一个逻辑命名空间;变体 B 写入另一个;两者互不读取。成本体现在两处:每个用户的状态现在对每个变体而言更小且噪声更多(可借鉴的历史记录更少),并且实验后的迁移必须决定在胜出变体发布时保留哪个变体的记忆。
  • 评估时的污染检测环节。 对于变体调用期间的每次记忆读取,记录写入该记忆的变体。分析时,计算跨越变体边界的读取比例。如果该比例不容忽视,则说明变体边界未守住,测得的 delta 已被污染。将跨变体读取率视为一等公民的实验健康指标,就像经典 A/B 测试中对待 SRM(样本比率偏差)那样。
  • 实验设计时的状态声明。 使实验规范包含一个“该变体读取或修改哪些状态”字段,列举每个持久化存储。框架随后可以验证划分原语是否覆盖了每个声明的存储。这听起来很官僚,事实确实如此——但代价是团队在全量发布导致核心指标变动后才发现泄漏。
  • 对照组纯度指标。 保留一个小的对照组(holdout),在实验期间运行生产环境变体,完全不接触另一个变体的记忆。发布后,将全量发布的变体行为与该对照组在相同输入分布下的行为进行比较。实验内行为与上线后行为的背离,就是划分泄漏的信号。即使实验设计预先遗漏了这一点,该指标也能事后捕捉到这种失效模式。
  • 预发布合成污染测试。 构建一个小型的离线测试环境,故意用变体 B 的记忆种子填充变体 A 的存储,反之亦然,然后运行两个变体并测量行为 delta。如果少量跨变体记忆能显著改变量测指标,则实验设计在上线前需要更强的隔离。将其视为实验发布就绪评审(launch readiness review)的一部分,而非研究产物。

这里的评估纪律与生产纪律同样重要。一套能够检测合成泄漏的评估组件,是随着记忆层演进而保持污染问题透明的唯一方法。如果没有它,每一个新的记忆表面——一个新的缓存、一个新的派生索引、一个新的反馈渠道——都是一个新的泄漏表面,实验团队直到发布出问题才会察觉。

架构层面的认知

思考智能体 A/B 测试的正确方式是:统计独立性的单位不是 user-id。它是智能体在变体调用期间读取或写入的每个持久化表面的闭包(closure)。用户是该闭包的一个组成部分;记忆层是另一个;缓存是另一个;派生索引又是另一个。仅以用户命名的框架交付了一个与系统不匹配的原语。

这种重新定义改变了实验设计时的对话。问题不再是“我们分流对了吗?”,而是“我们是否划分了变体接触的每一块状态?”这个问题由平台团队和智能体团队共同回答,而这正是以前失效发生的缝隙所在。命名闭包迫使人们进行以前无人提及的对话。

这一切背后还有一个更深层次的点。任何实验中测得的 delta 都是变体所见输入分布的函数。当部分输入分布是由变体自身通过持久化状态生成的,实验测量的是耦合系统的一个不动点(fixed point),而不是两个独立处理方案的比较。不动点可以提供信息——它告诉你一些关于联合行为的事情——但它不是团队以为正在测量的量,也不是全量发布后的产品将表现出的量。

那些不命名该闭包的团队,其交付的框架会在团队最需要信号的时刻将噪声误认为信号。解决方法并不光鲜:命名状态、在实验单元处进行划分、监控划分情况,并将泄漏视为一等公民的实验健康故障。在实验框架了解记忆层之前,每一次智能体 A/B 测试都面临着一个风险:全量发布时团队才会发现,其胜出的变体从未真正独立胜出过。

References:Let's stay in touch and Follow me for more thoughts and updates