生产级 Agent 与 Demo 的核心差距在于容错设计:单提供商无兜底导致全线崩溃、无限循环导致资源耗尽,需在失败处理上投入与防御同等资源。
一个在演示中能跑的 AI Agent 和一个能在生产环境中跑的 AI Agent 是完全不同的两类软件。演示跑在干净的输入和健康的 API 之上。生产环境则是凌晨三点、周六、供应商返回 503、连接池耗尽、Agent 已经在 47 步工作流的第 37 步、已经花掉了真金白银。
大多数 Agent 工程化的努力都花在如何让故障更难发生上。而那些系统能存活下来的团队,把努力花在了别处——花在故障注定会发生之后如何应对。
传统软件的失败方式大家都很熟悉:服务器宕机、查询超时、磁盘写满。Agent 继承了所有这些,又在上面叠加了自己独有的一系列问题。
Model API 故障是最常见的。速率限制、容量不足、模型被弃用、网络中断,都会切断 Agent 与其推理引擎之间的链接。一个只绑定了单一提供商且没有备选方案的 Agent,会把提供商的一次故障变成整体故障。
无限循环是最贵的。Agent 调用一个工具、得到错误、用相同参数重试、得到同样错误、然后不断重复直到额度耗尽。一个格式错误的任务可能在几个小时内烧掉几千美元,而且没有任何东西能阻止它。
上下文窗口耗尽是最安静的。一旦达到 token 上限,Agent 要么崩溃、要么静默截断它需要的内容、要么开始产生不连贯的输出。长期运行的 Agent 风险最大,因为每一步都在增加负担。
状态损坏是最棘手的。如果 Agent 发送了邮件但在记录"已发送"之前崩溃了,重启后会再次发送。如果它扣了款但在更新订单之前崩溃了,客户会被扣两次。部分状态很难检测、更难修复,而这恰恰是 Agent 以难以复现的方式崩溃的原因。
这些修复方案没有一个是新东西,也没有一个来自 AI。它们来自分布式系统——所有主流云平台和支付处理商多年来一直在使用它们。
断路器(Circuit breakers)监控下游依赖的错误率,一旦超过阈值就停止发送请求。当 LLM API 开始失败时,断路器跳闸,Agent 停止在注定失败的调用上消耗资源。经过一段冷却时间后,它会让几个测试请求通过,要么恢复正常流量,要么保持断开。
重试策略是一个设计决策,而不是默认配置。立即重试处理网络抖动。指数退避处理限速 API。加了抖动的退避阻止一群 Agent 步调一致地重试、猛击已经不堪重负的服务。关键的判断是知道什么时候该重试、什么时候该快速失败,因为重试一个永久性错误比不重试更糟糕。
状态 checkpoint 在定义的检查点保存进度,这样崩溃的代价只是一步而不是五十步。工程上的工作在于是确定什么算关键状态——也就是任务进度、中间结果和资源锁——什么是非持久的、重新计算比存储更便宜的。
优雅降级让一个简化版的服务保持运行。如果主模型宕了,就回退到一个更小的模型,接受质量更低的输出。如果向量数据库连不上,就回退到关键词搜索。更差的答案比没有答案强。
舱壁隔离(Bulkhead isolation)阻止故障横向扩散。邮件组件崩溃永远不该拖垮数据分析组件——这取决于系统如何构建,是通过进程隔离、独立的线程池还是独立的服务。
这一组模式中最有用的是最古老的一个。在监督树中,每个进程都有一个父进程监控它、在它死亡时重启它。根监督器管理子系统,每个子系统监督器管理工作进程,故障只向上传播到需要的层级,而恢复则向下层叠。
重启策略才是真正设计所在的地方。一对一重启只重启失败的子进程,让它的兄弟姐妹继续运行。一对全重启重启整个组——这是当子进程共享现在已经不一致的状态时你想要的行为。在任一方向上选错了都是 bug:太窄了会在损坏的状态下恢复,太宽了会让一次不稳定的工具调用重置四十步已完成的工作。
这一概念来自 Erlang 及其 OTP 框架,由爱立信在 1980 年代为电信交换机构建,目标是实现九个九的可用性。他们的见解是,防止每一个 bug 是不可能的,所以系统应该被构建成能够分块崩溃和恢复,而不是整体崩溃和恢复。Agent 编排比电信交换机的故障点更多,这让这个论点更有说服力,而不是更弱。
如果要给现有的 Agent 做改造,见效最快的顺序大致如下。
在每个外部调用上加断路器,因为那是阻止失控循环变成账单的东西。在任何超过几步的工作流上加 checkpoint,因为那是把崩溃从灾难变成小麻烦的东西。让每个副作用幂等,这样重启不会导致重复发送或重复扣款。然后用监督树包裹一切,认真地选择重启策略而不是用默认配置。
完整的一组模式,包括监控端和真正需要告警的内容,写在了 fault-tolerant AI agents。
这些都不能让 Agent 更聪明。它带来的是能够演示好的系统和下个月还在运行的系统之间的差别。