跳到主要内容

AI 系统中的功能交互故障:当两个正常运行的组件结合时发生崩溃

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的流式传输正常工作。你的重试逻辑正常工作。你的安全过滤器正常工作。你的个性化功能也正常工作。但当你将它们部署在一起时,奇怪的事情发生了:流式传输中途出现的速率限制错误导致用户看到的是一段被截断的响应,而系统却将其记录为成功。重试机制触发了,但流式传输已经结束。个性化层提供了一个定制化的响应,而安全过滤器本应拦截这个响应——除非过滤器看到的是 Prompt 的脱敏版本,而不是个性化层所处理的那个版本。

每一个功能都通过了你编写的各项测试。然而系统还是让用户失望了。

这就是功能交互故障(feature interaction failure),它是当今 AI 系统中最容易被误诊的生产环境 Bug。

为什么单一测试无法发现交互故障

测试 AI 系统的标准思维模型通常是这样的:为每个功能编写测试,确保每个测试都通过,然后发布。当各功能相互独立时,这种模型运作良好。但当功能共享状态、按顺序执行,或对其他功能已经完成的工作做出隐含假设时,这种模型就会彻底失效。

数学规律是无情的。五个可靠性均为 99% 的组件组成的系统,其整体可靠性仅为 95%。再增加五个组件,可靠性就会降至 90%。但可靠性并不是真正的问题,隐性失败(silent failure)才是。在传统的软件系统中,5% 的失败率会出现在你的错误日志中。但在一个组合式 AI 系统中,这 5% 的失败通常表现为看起来正确的输出:连贯的句子、有效的 JSON、没有抛出任何异常。系统认为自己成功了,但用户收到的却是错误的内容。

关于多智能体系统的研究发现,简单的组合产生的错误大约是单智能体系统的 17 倍,而且其中大多数在组件级别是不可见的。在被归类为“规格说明失效”(specification failures)的 42% 故障中(即系统行为偏离了意图,但技术上没有任何组件发生故障),几乎全都是伪装下的交互故障。

让生产团队头疼的三种冲突模式

交互故障的清单很长,但有三种模式经常出现,以至于每一个构建组合式 AI 系统的团队最终都会至少遇到其中一种。

流式传输 + 重试逻辑

流式传输和重试机制对于请求如何完成有着截然不同的假设。重试逻辑假设如果请求失败,你可以重新执行它。流式传输则假设部分输出已经交付给了客户端。

冲突发生在衔接处。在一个 1000 token 的响应中,如果在第 400 个 token 处出现速率限制错误,意味着部分内容已经显示在用户的浏览器中。重试机制看到了 429 错误并重新执行了整个请求——这在技术上是正确的——但客户端现在收到的却是一个重复的开头,并拼接在之前截断的第一个响应之后。或者重试机制根本没有触发,因为在流中出现错误之前,请求在技术上已经返回了 200 状态码。

这种失效模式在单元测试中是不可见的,因为重试逻辑的单元测试通常模拟的是在返回任何 token 之前就失败的请求。流式传输集成测试则验证 token 是否到达。没有任何测试会构建出在实时重试循环中流式传输中途发生错误的情景。

解决办法是架构层面的:将生成过程与客户端连接解耦。LLM 调用存在于一个可以独立重试的服务层;客户端连接则订阅输出队列。这意味着重试机制和流式传输机制永远不会触及相同的状态。

缓存 + 新鲜度要求

缓存可以降低延迟并减少成本。新鲜度要求则意味着某些数据必须反映世界的当前状态。这两个目标之间存在直接的张力,当两个功能都不知道对方存在时,它们会产生糟糕的交互。

一个带有语义缓存的客服机器人可能会根据 47 分钟前有效的缓存条目来回答“订单 #12345 的状态是什么?”。而订单在此期间已经发货了。响应是连贯的、具体的,但却是错误的。系统没有抛出异常。准确率指标也捕捉不到这一点,因为它测量的是响应是否格式正确,而不是底层数据是否是最新的。

在多跳场景中,这种失效更为严重。智能体检索缓存的用户偏好(“寄送到办公室地址”),然后检索缓存的地址记录,最后生成确认信息。每一次缓存查询独立看起来都是正确的。但用户今天早上更改了地址,现在正等着一个会寄送到错误大楼的包裹。

RAG 系统也面临类似的变体:在嵌入(embedding)时准确的向量索引,随着源文档的更改会悄然变得陈旧。近似最近邻检索没有新鲜度的概念——它寻找的是语义相似的文档,而不是当前准确的文档。构建实时应用的团队发现,只有当用户询问昨天发生变化的事情时,他们才会发现每晚重新索引的计划造成了 24 小时的新鲜度差距。

这里的交互审计针对每个缓存层提出了一个简单的问题:当用户的查询需要特定事件发生之前就被缓存的数据时,会发生什么?列出这些事件(记录更新、政策变更、价格变动),然后追踪接触这些记录的每一个功能。

个性化 + 安全过滤

个性化和安全过滤通常被实现为独立的中间件层,且每一层在隔离状态下都能正常工作。个性化根据用户历史记录或明确的偏好来定制响应;安全过滤则拦截违反政策的响应。

当这两层在同一个请求的不同表示形式上运行时,冲突就发生了。个性化层可能会在将请求传递给模型之前对其进行转换(例如 “像海盗一样回答,语气随意”)。安全过滤器可能会评估原始请求、转换后的请求或最终响应——在许多实现中,它会评估任何检查成本最低的表示形式。

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