容错是智能体从演示走向生产的分水岭

传统后端工程的容错面向「确定性故障」:依赖会挂、网络会断、磁盘会满,但代码路径本身是确定的。智能体系统把这个前提推翻了——每一次模型调用都是概率性输出,每一个工具调用都在触碰真实的外部世界,故障面比传统服务大了一个数量级。可以先算一笔账:一个典型的生产级智能体任务平均发起 15 到 30 次模型调用、5 到 10 次工具调用,假设单次调用成功率 99%,20 次调用的任务级成功率只剩约 82%;哪怕把单次成功率提到 99.5%,任务级也只有 90% 上下。这意味着每十个任务里就有一个会中途失败——容错不是锦上添花的可选项,而是系统的主干逻辑。工程实践里判断一个团队「有没有真正上过生产」,看的不是演示成功率,而是失败的那 10% 到 20% 去了哪里:被吞掉了、被降级了、还是被记录并补偿了。本文按故障面分析、超时、重试、降级、可观测性五层展开,给出一套可以直接落地的容错设计参考。
智能体的故障面:三类失败源的叠加
设计容错之前先要认清对手。智能体的失败源可以归成三类。模型侧:限流拒绝(429)、服务过载(部分厂商甚至专门定义了 529 过载状态码)、上下文超长报错、内容安全拦截、生成到一半断流——这类失败的特点是无法彻底消除,因为头部模型 API 在流量高峰出现秒级到分钟级的过载是常态,2025 年以来多家厂商的重大故障通报里,「过载导致的不可用」都排在第一位。工具侧:外部 API 超时与限流、接口字段悄悄变化、认证凭据过期、第三方服务的区域故障——工具生态越长,这层越脆弱,一个挂了十个子页面爬取任务的浏览器工具就能拖死整个任务。编排侧:进程崩溃、网络分区、状态存储故障、版本发布打断执行中的任务。更麻烦的是,智能体的失败大多是「部分成功」:任务执行到第 8 步挂了,前 7 步的产物是否可复用,直接决定重试成本是「重跑一步」还是「重跑全部」。容错设计的第一个动作,就是给自己的系统画出这张故障清单,并为每一类失败标注发生率量级和可恢复性——没有这张清单,后面所有机制都是盲拍的。
超时:分层预算与截止时间的传递
超时是所有容错的地基,没有超时的系统会在第一个故障依赖上永久挂起。智能体的超时必须分层设定,而不是一刀切。经验参考值:连接层 1 到 3 秒;模型首 token 超时 10 到 30 秒(流式输出下更实用的判定是「间隔超时」——超过 30 秒没有新 token 就判死并断开,而不是等总时长);工具调用按类别分级,纯计算类 5 秒、常规网络 API 10 到 30 秒、浏览器操作与文件批处理这类重工具放宽到 60 到 120 秒;整个任务设总预算,常见做法是 P95 完成时长的 3 到 5 倍。两个容易被忽略的要点。其一,截止时间要传递:父任务把剩余预算随请求传给子智能体和工具,避免「每个子任务各自限时 5 分钟、父任务却被拖了 40 分钟」的预算失控——deadline 传播在 gRPC 等框架里是现成能力,自研编排时不要丢。其二,超时不等于失败:长文档分析、深度检索这类天然慢的环节,正确姿势是转异步——立刻返回任务 ID,让调用方轮询或订阅完成事件,把「等」和「失败」在语义上分开。超时参数还要进配置而不是硬编码,因为模型和工具的耗时分布会随供应商、模型版本、提示词长度漂移,上线后按监控数据回调是例行工作。
重试:指数退避、幂等性与重试预算
重试是最容易被滥用的机制。第一原则是指数退避加抖动:基础间隔 500 毫秒、倍率 2、单次上限 30 秒,再加 0 到 30% 的随机抖动——没有抖动的重试会让故障恢复瞬间被同步的重试洪峰再次打垮,这在限流场景尤其致命:限流说明服务已经过载,无退避的重试只会让过载时间拉长,2025 年几家厂商故障期间「客户端疯狂重试导致全局恢复变慢」就是标准反面教材。第二原则是只重试幂等操作:读操作可以放心重试;写操作必须带幂等键(请求侧生成唯一键,服务端按键去重,Stripe 等支付 API 的成熟惯例),否则一次网络抖动可能造成重复下单、重复发信。第三原则是区分可重试与不可重试错误:429、5xx、网络中断可以重试;400 参数错误、401 认证失败重试毫无意义——正确的动作是先修复前置条件(刷新凭据、修正参数)再发起,把「重试前钩子」做成机制而不是散落的 if。第四原则是重试预算:给单个任务设重试次数上限(如 3 到 5 次)和预算占比(重试请求不超过总请求的 10%),预算耗尽就转入降级或失败路径。还有一类智能体特有的问题:温度大于零时,同一个提示词重试会得到不同输出——对需要确定性的环节,要么把温度设为 0,要么把首次成功的结果缓存复用,不能靠重试「抽卡」。
优雅降级:降级阶梯与最小可用服务
当重试预算耗尽,系统需要一个预设的降级阶梯,而不是当场崩溃。成熟的降级设计按「质量换可用」排好序。模型层:主模型不可用时切备用供应商的同档模型(提示词按供应商方言预适配),再不行切小模型顶上并显式声明能力降级。上下文层:上下文超长报错时,自动触发压缩或截断重试——保留系统提示、近期对话与关键工具输出的骨架,丢掉中间过程详情。工具层:实时搜索失败时退回模型参数内知识并声明时效;数据库只读副本故障时降级到缓存快照;某个非关键工具挂掉时跳过该步骤并在结果里标注「部分完成」。整体策略上,宁可返回一个「降级后的答案 + 置信度声明」,也不要返回硬错误。与降级配套的是熔断:滑动窗口内错误率超过阈值(如 5 分钟内 50%)就对该依赖熔断,一段时间内直接快速失败不再发起真实调用,之后半开探测恢复——熔断保护的是系统自己,避免把资源耗死在一个已经病了的依赖上。最后一级兜底是结构化失败回复:所有路径都失败时,返回已完成步骤、失败步骤、原因分类和建议动作,让用户能手动接管。一个给出「卡在哪、为什么、你可以怎么办」的失败回复,体验上比沉默或超时好一个量级。
流式与长调用的特殊容错:断流、续传与部分结果
模型调用的长时化与流式化带来一类特殊故障,值得单独设计。断流恢复:长推理(深度思考类模型的单次响应可达数分钟)流式输出中途断开是高频故障,成熟做法是客户端记录已收到的 token 位置,重连时判断供应商是否支持续传——支持则从断点续拉,不支持则把「已生成的部分」作为上下文重新发起(让模型接着写而不是从头写),成本从全量重跑降为增量续写。部分结果语义:工具批量执行(抓 20 个页面、处理 100 条数据)时,部分成功是常态——接口要把「成功集合、失败集合、失败原因」结构化返回,而不是整体报错,上层据此决定重试失败子集还是接受部分结果继续。幂等缓存层:在模型调用与工具调用之间加一层请求级缓存(按请求内容哈希命中),故障重试时直接复用刚成功过的调用结果——既省钱又消除重试的随机性漂移。这三件事的共同思想是把「一次大调用」拆成「可增量恢复的小步骤」,让故障的影响半径从整个任务缩小到单个步骤。
可观测性与补偿:让每一次失败都有去处
容错的最后一层是「失败之后的账要算得清」。结构化失败日志是底线:每次失败记录错误分类码、所属故障层、重试次数、最终走的降级路径、各阶段耗时分解——复盘时能回答「上周任务失败率多少、失败集中在哪一类、降级被触发了几次」,这组数字本身就是下一轮容错投入的依据。死信队列处理重试耗尽的任务:不丢弃,而是连同完整上下文落库,由人工或定时任务择机重放——很多「失败」只是失败在了当时的故障窗口里。补偿事务面向多步写操作:已创建的资源要登记逆操作(取消订单、删除草稿),而不可逆动作(发邮件、对外支付)则前移到「执行前人工确认」,这本质上是把补偿不了的失败拦在发生之前。混沌演练是验收手段:定期在生产预发环境注入模型 429、工具超时、状态库故障,验证降级路径真实可用——没有演练过的降级路径等于不存在,真故障时它大概率会因为配置漂移、依赖过期而失灵。容错代码和普通代码一样会腐烂,只有持续演练能保鲜。
flowchart TD
A["单步调用发起"] --> B{"错误分类"}
B -->|"可重试 429 5xx 网络错误"| C["指数退避加抖动重试"]
B -->|"不可重试 400 401"| D["修复前置条件后重发"]
C --> E{"重试预算耗尽?"}
D --> F["继续正常流程"]
E -->|"否"| F
E -->|"是"| G{"依赖级熔断判断"}
G -->|"错误率超阈值"| H["熔断 快速失败 半开探测"]
G -->|"未超阈"| I["进入降级阶梯"]
I --> J["备用模型 缓存值 截断上下文"]
J --> K{"降级成功?"}
K -->|"是"| L["带降级标记与置信度返回"]
K -->|"否"| M["写入死信队列 结构化失败回复 人工接管"]
小结
容错设计是把智能体从「能跑的 demo」推向「敢托付的生产系统」的那道门槛。核心方法论可以压成三句话:第一,失败是常态,按常态设计——任务级的 10% 到 20% 失败率不是耻辱,吞掉失败才是;第二,每一层都要有预算——超时预算分层设限并向下传递,重试预算封顶防止风暴,成本预算约束重试与降级的开销;第三,降级路径必须演练——降级阶梯、熔断、死信队列、补偿事务组成的安全网,只有经过混沌演练才算真实存在。这套机制没有任何一项是智能体独有的新发明,它们来自分布式系统几十年的积累;智能体工程的新意只在于把「概率性输出」纳入故障面,把「部分成功」作为恢复的起点。把老功夫做扎实,智能体系统的可靠性上限就出来了。
读者留言
COMMENTS 暂无还没有留言,来说第一句?