AI 功能需要多维开关:模型切换、降级路径、按用户隔离,而非简单的开/关布尔值,否则凌晨三点只能干等。
每个团队都知道如何把功能放在开关后面。这里的不同之处在于,最可能在凌晨三点需要改动的东西不是功能是否开启——而是它调用了哪个模型、使用了什么 prompt,以及它在多大程度上可以不经询问就自行操作。
一个传统的功能开关用一个布尔值回答一个问题,而且这个形状是正确的,因为传统功能只有一种失败模式:它坏了。而 AI 功能有多种失败模式,且需要不同的响应。
提供商降级了——你需要换用不同的模型,而不是关掉功能。Prompt 变更导致质量回退——你需要恢复到之前的 prompt,但这不是一次代码部署。功能本身没问题,但某个特定客户的数据产生了糟糕的输出——你只想针对这个客户关掉,对其他人保持开启。支出超出预期——你想要用便宜的模型或降级路径,而不是让服务中断。一个布尔值无法回答以上任何一种情况,因此对每种情况的响应都变成了一次部署,而部署是你最需要速度时最慢的工具。
还有第二个原因是这个依赖关系特有的。你在开关后面管控的行为可能在没有任何部署的情况下发生变化,因为模型是别人的,可以在你不知情的情况下被更新。开关通常是一种控制你自身变化的机制;在这里它也是对你没做出的变化做出反应的机制——这就是为什么检测到提供商一方的行为变化和拥有一个可以应对的开关,是同一个控制机制的两半。
把这些事项保持独立,因为它们被不同的人出于不同的原因拉动。工程师在提供商事故期间切换模型;领域负责人回滚 prompt;支持负责人禁用某个租户;经理在一个糟糕的周后降低自主程度。如果这四件事是一个开关,上述每一个动作都会把功能对所有人关掉,而开关会因为成本太高而停止被使用。
一个值得付出的实现注意事项:所有四个开关的解析值要和模型 ID 以及 prompt 版本一起记录在每条请求的日志行中。否则你既让行为变得可配置,又同时让它变得无法重建某次请求得到了什么行为。
在第一个用户看到功能之前就写好逐步下线的方案;四个节点,每个都有退出条件。
仅限内部,无时间限制。 当团队用它处理了真实工作而非测试输入时退出。目的是发现只有在真实数据上才会出现的失败模式,而且这是免费的。
一个小的命名队列。 不是随机百分比——而是你能联系到的人。当定性反馈加上你所担心的失败类别不再出现时退出。随机百分比用于后续阶段,当你需要统计数据而非解释的时候。
带对照组的百分比爬坡。 现在统计数据变得重要了。保留一个永远得不到该功能的对照组,因为这是将业务指标的变化归因于这个功能而非季节性因素的唯一方法。当你提前命名的质量、成本和延迟指标都满足时退出——三个维度一起考量,因为在某一维度上以牺牲其他维度为代价取得进步,是 AI 功能看起来成功但实际并非如此的常见方式。
默认开启,保留开关。 退出——也就是删除开关——只在经历了一段没有回滚的时期之后,且在替代控制到位的情况下。而对于模型和 prompt 这两个维度,可能永远不会删除;这两个是配置而非临时发布机制。
最容易被跳过、最难恢复的标准是对照组。没有它,半年后没人能说这个功能是否有帮助,而关于是否继续维护它的争论会被最自信的人所左右。
把终止开关和开关混为一谈是终止开关失效的原因。终止开关由四个需求定义,全部都是关于它不需要什么的。
不需要部署。 如果拉下它需要一次构建,那就不是终止开关。这是其他所有需求的前提。
不需要工程师。 凌晨两点注意到问题的人是支持人员。如果只有作者能拉下它,响应时间取决于唤醒他们需要多久。
不依赖被终止的东西。 一个评估逻辑调用了提供商的开关,或者配置是通过失败路径获取的,恰好在最需要它的时候失效。故障安全默认值、本地缓存、不依赖网络调用的评估。
没有错误状态。 拉下它应该产生降级后的功能,而不是堆栈跟踪。开关关闭了模型,但必须有其他东西来响应——这意味着降级路径必须在开关值得拥有之前就存在。
而且它必须被演练过。一个从未在生产环境拉过的终止开关只是一个假设。在一个安静的时段故意拉下它,观察功能的做法,并确认降级路径是你设计的那条而不是一个加载 spinner。这样做一次往往会至少发现一个问题,最常见的是降级路径在某个调用点从未被接入。
开关会累积,而一个过时的开关比没有开关更糟糕:它是一条无人测试的代码路径,最终会有人启用它。两个习惯保持这个集合的健康。
首先,给每个发布开关在创建时就设定一个过期日期——一个截止日期,到期后要么删除要么显式转换为永久配置。这个区分是有用的部分。发布开关是临时脚手架,应该消亡;模型选择或自主程度开关是永久控制,应该被文档化、被拥有、被演练。把两类混合在一起会导致代码库最终有四十个没人敢删除的开关。
其次,当开关被移除时删除死分支。一个永远为真但假分支仍然存在的开关是一条不再被测试、不再为真的路径——而在 AI 功能中,假分支通常是确定性回退,恰恰是下次事故中你需要的代码。要么保持它被演练,要么承认它已消失;让它执行一年然后在紧急情况下信任它,是三个选项中最糟糕的。