跳到主要内容

你的 AI 功能需要一个无需部署的紧急开关 (Kill Switch)

· 阅读需 14 分钟
Tian Pan
Software Engineer

想象一下这个场景:凌晨 2:14,值班工程师的手机嗡嗡作响,你旗舰产品中的 AI 功能正自信地告诉企业客户,他们的账号是“西红柿汤”。模型供应商推送了一个路由变更,你的提示词被静默升级的分词器截断了,或者是检索索引针对一个损坏的 Parquet 文件重新生成了——原因现在还不重要。重要的是,距离有人截图输出并发布到 LinkedIn 只剩 10 分钟。

如果你唯一的对策是“回滚部署并等待 CI”,那你已经输了。标准的流水线回滚从报警到恢复需要 20 到 40 分钟,而糟糕的输出不会在绿色对勾渲染时礼貌地暂停。等到新容器恢复健康时,截图已经在信息流里传开了,支持信箱里塞满了 50 个工单,而你花了 6 个月建立的信任正被那些从未使用过该产品的人审查。

那些能在 5 分钟而不是 5 小时内控制此类事件的团队并不是靠运气。他们在需要之前就构建了一个紧急开关(Kill Switch)——这是一个允许值班工程师在几秒钟内禁用 AI 路径的原语,无需部署,无需合并,也无需任何人触碰生产环境的二进制文件。这篇文章将探讨这种专门针对 AI 功能的原语是什么样的,为什么确定性软件的版本不足以应对,以及在事故发生前的一天必须具备什么条件,才能让响应在事故发生的当晚奏效。

这种类型的故障,部署路径太慢了

十年经验的 Web 服务工程师通常会对“如何回滚错误的变更”有一个自信的答案:撤销提交、运行 CI、部署上一个制品、监控。这个答案适用于你的生产二进制文件是故障单元,且发布频率大约是每日一次的机制。它之所以有效,是因为部署是可预测的,错误的代码就是新的代码,且回滚目标是昨天在生产环境中运行的已知良好版本。

AI 功能同时在三个方面打破了这些假设。

首先,故障通常不在 你的 代码中。你的容器运行良好。但模型供应商在稳定的模型名称下轮换了权重,上游向量化服务开始返回 0.0 向量,或者安全分类器变得更加激进,现在正拒绝 30% 的合法请求。由于回归是搭载在他人的发布列车上而来的,因此没有可以撤销的提交。

其次,故障表现在 输出质量 上,而不是可用性上。端点返回 200,延迟正常,JSON 解析顺利。根据你现有告警关注的每一项指标,系统都是健康的。出问题的是输出的 含义,而你的流量仪表盘无法感知含义。

第三,回滚目标是模糊的。即使你重新部署了之前的容器,其背后的模型可能已经改变了。“昨天的二进制文件”不再等同于“昨天的行为”,因为行为始终是代码、提示词、模型、检索索引以及半打拥有各自时钟的上游服务的集合体。你可以发布上周的制品,但仍然会遇到这周的事故。

这些因素都使得“部署即回滚”路径比确定性服务更慢、更不可靠且更不精准。紧急开关的存在就是为了完全绕过这条路径。

开关实际需要做什么

AI 功能的紧急开关不是一个简单的布尔值。它是一组预先部署的行为,每个行为都由一个 Flag 控制,值班工程师可以在几秒钟内进行组合。最小可行方案包含四个成员。

第一个是 关闭并回退(off-with-fallback)。当 AI 路径被关闭时,功能不会返回错误或加载动画。它会返回一个确定性的响应——来自 AI 之前的关键词匹配的搜索结果,基于规则的草稿(而不是 LLM 编写的),或者是静态的常见问题解答(而不是对话式回答)。用户会察觉到功能变笨了,而不是功能消失了。核心点在于“关闭”不能意味着“损坏”——如果你唯一的回退方案是 500 错误,那么你的紧急开关只是另一种形式的停机。

第二个是 基于租户的范围(per-tenant scope)。AI 故障的影响范围在客户之间很少是均匀的。破坏某个租户索引的检索 Bug 对其他租户是不可见的。破坏受监管行业客户格式的提示词变更对其他人可能没问题。全局关闭是一把大锤;基于租户的关闭则是手术刀,而大多数实际事故需要的是手术刀。Flag 系统必须支持根据租户 ID、账户等级、区域或流量实际拆分的任何其他维度进行定位。

第三个是 基于操作的范围(per-operation scope)。AI 路径很少是单一路径。它包括流式对话端点、后台摘要任务、自动补全、基于向量的搜索。它们共享基础设施但独立发生故障。因为其中一个表现异常而关闭整个“AI”功能的紧急开关在 75% 的情况下都是过度反应。每个高价值操作都需要自己的 Flag,每个 Flag 都需要自己的回退方案。

第四个是 自动激活(automatic activation)。值班工程师是响应链条中最慢的一环。即使是一个优秀的团队,从检测到通知再到确认并切换 Flag 的过程也很少能压缩到 5 分钟以内。对于故障信号可以自动化的场景——输出分布偏移、金丝雀集合上的评估分数骤降、拒绝率激增、幻觉分类器报警——紧急开关应该在信号超过阈值时自行触发,并在事后(而不是事前)通知人类。这就是 5 分钟和 5 秒钟控制事故的区别。

无人愿解决的检测难题

一个紧急开关的速度取决于触发它的信号。确定性软件工具包(deterministic-software toolkit)会给你 5xx 错误率、p99 延迟和异常计数;但对于 AI 功能,这三项指标可能非常平稳,而输出却已在悄无声息中崩溃。

对 AI 功能真正重要的信号是不同的,且更难进行埋点监测(instrument)。

输出分布偏移(Output-distribution shift)是核心手段。你对模型在近期窗口内的输出进行指纹提取——包括长度分布、拒绝率、top-k token 频率、输出到一小组预设类别的分类——并与基准线进行对比。平均输出长度的突增、拒绝率的翻倍或类别分布的偏移,都是上游发生了变化的强烈信号。检测器不需要知道哪里出了错,它只需要注意到系统的行为与一小时前在统计学上有所不同。

连续金丝雀(continuous canary)上的评估分数回退能捕捉到分布偏移漏掉的故障。你每隔五到十分钟针对生产环境运行一小组固定的评估集(五十到几百个用例),并跟踪分数。当分数低于设定的底线时,你会收到告警,并可选择自动触发紧急开关。金丝雀的规模是成本与灵敏度之间的杠杆;在实践中,“小而频繁”优于“大而稀少”。

分群质量仪表盘(Per-cohort quality dashboards)能发现特定用户细分领域中无声的回退。整体质量可能保持稳定,但某一个群体——如企业级客户、德语用户、负载异常的长尾账户——可能正在悄然崩溃。通过分群切分质量并在单分片出现回退时告警的监控层,可以在用户提交工单之前捕获这些问题。

建立了紧急开关但跳过检测层的团队,拥有快速的人工响应和缓慢的检测时间,这加起来就是缓慢的恢复时间。建立了检测但没有紧急开关的团队,拥有快速的检测和类似于重新部署(deploy-shaped)的响应,这只是从另一个极端面临同样的问题。你两者都需要。

必须在事故发生前测试开关

一个从未在生产环境中行使过的紧急开关,就是一个不起作用的开关。这是 SRE 社区经受惨痛教训学到的,也是 AI 工程社区目前正在重新学习的。

失败模式很直接:Flag 是在一年前添加的;回退路径写好了但从未在真实流量下测试过;Flag 评估客户端默认缓存了十分钟的值;每个租户的定向规则有一个没人发现的拼写错误;审计日志因为配置漂移在生产环境中悄悄禁用了。这一切直到凌晨 2:14 才浮出水面,而现在你正在调试那个本该用来调试问题的工具。

必须落地的纪律是将紧急开关视为具有自身测试计划的一等公民功能。具体包括:一个集成测试,针对预发布租户切换 Flag 并验证回退路径是否提供了预期响应;一个合成的连续探测器,每天在金丝雀租户中开启和关闭一次 Flag,验证响应形式是否发生了相应变化,如果没有则进行告警;每季度的故障演练,由值班轮值人员在办公时间内对低流量生产租户实际操作开关并观察系统行为;审计日志审查,确认每一次 Flag 变更都记录了操作者、原因、范围和时间戳。

文化上的承诺比上述任何一项都难:紧急开关不发布,功能就不发布。开启开关和关闭开关是同一个交付物的一部分。如果一个团队允许功能上线时虽然合并了关闭开关的 Flag 但回退路径未经测试,那他们发布的就是一个没有关闭开关的功能。那个 Flag 只是安慰剂。

延迟、传播以及决定结果的其他枯燥琐事

两个操作细节决定了你设计精美的紧急开关是否能快到足以发挥作用。

首先是 Flag 评估延迟。每一个到达 AI 路径的请求都必须询问“这被关掉了吗?”,答案必须是本地化的——没有远程调用,没有网络跳数,没有第三方 API。如果 Flag 客户端每分钟轮询一次配置服务器并缓存答案,那么无论你切换开关的速度有多快,紧急开关的传播时间都有一分钟的底线。采用流式更新(SSE、WebSocket 或基于发布订阅的推送)的 Flag 客户端可以将该时间缩短到个位数秒级。对于紧急开关来说,这种差异决定了全局。

其次是故障安全默认值(Fail-safe defaults)。当 Flag 服务本身不健康时——它终究会发生——客户端必须选择一个值。紧急开关 Flag 的正确默认值既不是最近获取的值(过时且可能错误),也不是“关闭”(当 Flag 服务抖动时,它会静默禁用你的功能)。正确的默认值应该是创建 Flag 当天硬编码的保守值,快照到构建产物中,且仅随刻意意图更新。如果无法连接 Flag 服务,客户端将回退到快照。在 Flag 服务不可达时的行为应该是深思熟虑的选择,而不是缓存层偶然产生的属性。

这些细节在凌晨 2:14 决定你的事故是持续五分钟还是五小时之前,都是枯燥乏味的。

轮值操作手册(Runbook)是什么样的

当存在紧急停机开关(Kill Switch)时,AI 功能故障的轮值操作手册比人们预期的要短,而且它有一种特定的模式。

检测(Detect)。要么是报警触发,要么是人工注意到了异常。第一个决策是范围:这影响的是所有租户、某个特定群体,还是某一种操作类型?正确的关停操作应该是能缓解症状的最窄范围——全局关停仅用于严重且广泛的损害,而值班人员拥有预授权,可以直接触发窄范围的关停,无需逐级上报。

切换(Toggle)。值班人员切换相应的标志位(Flag)。该操作会写入审计日志,包含操作者、事件 ID、范围和意图。回退路径会在几秒钟内在全球集群中激活,因为 Flag 客户端会流式传输更新。

验证(Verify)。切换后,合成探测(Synthetic Probes)会自动运行,确认回退路径正在提供预期的响应。值班人员观察输出分布和质量信号是否趋于稳定。如果没稳定,说明范围选错了(需要扩大关停范围),或者是关停方向有误,实际问题出在别处。

调查(Investigate)。随着“流血”停止,团队有时间进行真正的诊断,而无需面对倒计时的压力。查看日志、追踪、模型版本差异、重跑评估套件、上游依赖状态。紧急停机开关为冷静处理赢得了时间。

恢复(Restore)。当根本原因解决后,Flag 才会切回。在此之前绝不操作。因为“现在可能正常了”而过早切回的冲动,往往会将单次事故演变成两个事故的夜晚。恢复的准入门槛是成功的金丝雀发布(Canary Run),而不是凭感觉。

在需要之前就构建了紧急停机开关的团队,可以在 5 到 15 分钟内完成这个循环。没构建的团队则至少需要 40 分钟的部署循环,并祈祷部署能解决一个本就不在部署代码中的问题。

深层认知

紧急停机开关所代表的更深层次转变是“接受”——在架构层面接受你的 AI 功能会以你无法预测的方式和时间出现异常,而应对这种不确定性的正确做法是将“关闭开关”设计在功能内部,而不是仅仅写在操作手册里。

内化了这一点的团队会在第一天就编写回退路径,在开启功能前就测试好紧急停机开关,构建自动触发开关的检测信号,并每季度演练操作手册。没有内化这一点的团队则是先编写功能,发布它,然后在第一次事故后才添加紧急停机开关——到那时,他们拥有的只是一个根据“已经坏掉的地方”而设计的开关,而不是为了应对“下一个可能坏掉的地方”而设计的。

确定性软件时代教会了工程文化:可用性(Uptime)是合同,Bug 在代码审查中被发现。而 AI 时代需要一种不同的反射动作:输出(Outputs)才是合同,无论你是否发布模型权重,模型都是你依赖树的一部分。一个你无法快速关闭的功能,就是一个你无法真正控制的功能。紧急停机开关是一个微小的原语(Primitive),它将“我们发布 AI 功能”转变为“我们运维 AI 功能”,而这两句话之间的差距,就是事故发生的温床。

先构建关闭开关。在信任它之前先测试它。连接好无需人工干预即可触发它的检测机制。当你真正需要它的那个夜晚,你将没有时间去补救。

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