第 18 章 AI AgentAgentic Patterns韧性

第 18 章 韧性工程:扛得住部分失效

系统总会部分失效,关键是扛得住

前几章让 Agent 输出可靠、评测跟上、还能看得见。但还有个现实:依赖的东西总会失效。外部 API 会挂、模型提供商会抖、一次提示词改动可能让整个 Agent 行为崩盘、定时任务可能悄无声息地死掉。韧性工程回答的是:这些失效发生时,系统怎么扛住、怎么恢复、怎么不把用户坑了。

这一章讲四个模式,从"挡住重复失败"到"盯住静默死亡":

  1. Agent Circuit Breaker(Agent 熔断器):坏掉的工具别反复调,暂时熔断。
  2. Failover-Aware Model Fallback(模型回退):按失败语义智能切换模型。
  3. Canary Rollout & Auto-Rollback(金丝雀发布与自动回滚):策略改动先放小流量,越界自动回滚。
  4. Dead-Man's Switch(Dead-Man's Switch):定时任务靠"成功哨兵"报警,而不是报错。

模式一:Agent 熔断器(Agent Circuit Breaker)

问题

用外部工具的 Agent(API、数据库、网页爬虫、代码执行器)面临一个常见的失败模式:某个工具端点降级或不可用,Agent 却一直调它,在永远不会成功的重试上白烧 token。

这带来三个级联问题:

  • Token 浪费:每次失败的工具调用都花输入/输出 token,Agent 还经常生成大段重试推理。
  • 延迟放大:在一个死端点上顺序重试,几秒变几分钟,毫无进展。
  • 级联失败:一个工具挂了(比如搜索 API),Agent 可能整个卡死,而不是改用其它办法。

模型层的故障转移(一个提供商挂了,在 GPT-4 和 Claude 之间切换)解决不了这个问题。工具层的失败需要不同策略:Agent 要在会话中途学会"这个工具有毛病,别用"。

方案

把分布式系统的经典熔断器模式用到 Agent 工具调用上。 熔断器包住每次工具调用,跟踪失败率,在三个状态间切换:

  • Closed(关闭):调用正常放行,失败被计数。
  • Open(打开):调用立即被阻断,返回缓存的错误或回退,实际请求根本不发。
  • Half-Open(半开):放行一个探测调用。成功就重置回 Closed,失败就回到 Open。
状态机:
Closed → Open: 失败数 >= 阈值
Open → HalfOpen: 冷却时间到了
HalfOpen → Closed: 探测成功
HalfOpen → Open: 探测失败
class AgentCircuitBreaker:
    def __init__(self, failure_threshold=3, cooldown_seconds=60):
        self.state = "closed"
        self.failure_count = 0
        self.threshold = failure_threshold
        self.cooldown = cooldown_seconds
        self.opened_at = None

    def call(self, tool_fn, *args, **kwargs):
        if self.state == "open":
            if time.time() - self.opened_at >= self.cooldown:
                self.state = "half_open"   # 放一个探测
            else:
                raise CircuitOpenError(f"熔断中,工具禁用 {self.cooldown}s")

        try:
            result = tool_fn(*args, **kwargs)
            if self.state == "half_open":
                self.state = "closed"
                self.failure_count = 0
            return result
        except Exception:
            self.failure_count += 1
            if self.failure_count >= self.threshold:
                self.state = "open"
                self.opened_at = time.time()
            raise

Agent 特有的适配(和传统熔断器不同的地方):

  • Token 感知阈值:烧了 N 个 token 就熔断,而不只是 N 次失败。
  • 回退路由:熔断打开时,通知 Agent 的系统提示词,让它选替代工具。
  • 按工具粒度:每个工具(搜索 API、代码执行器、数据库)有自己的熔断器。
  • 会话级状态:电路状态在 Agent 会话之间重置(不像微服务的持久熔断器)。

证据

  • 证据等级:中medium)。
  • 有价值发现:熔断器在微服务架构里是生产验证过的成熟模式(Netflix Hystrix、Resilience4j、Polly),能直接迁移到 Agent 工具调用;生产 Agent 系统报告,熔断器阻止了对降级工具的无效重试循环,省了 40%-60% 的 token;Anthropic 的 Agent 可靠性研究强调"快速失败"优于重试重策略。
  • 未验证:最优阈值和冷却值因工具类型和延迟特征差异很大。

怎么用

什么时候用:

  • Agent 用 3+ 个可能独立失败的外部工具。
  • 工具可靠性参差(有限流的 API、网页爬虫、第三方服务)。
  • Agent 会话长到工具可能在会话中途恢复。

实施步骤:

  1. 每个工具包一个熔断器实例
  2. 按工具特征设阈值:快 API(搜索、天气)threshold=3、cooldown=30s;慢工具(爬虫、编译)threshold=2、cooldown=120s。
  3. 定义熔断后的回退行为:有缓存就返回缓存/过期结果;路由到替代工具(换搜索提供商);告诉 Agent 这个工具不可用,让它调整计划
  4. 记录熔断状态变化,供可观测性用。

取舍

  • 好处:防止在坏工具上无谓重试烧 token;正常降级,Agent 用可用工具继续干活;自愈,半开探测在工具恢复后自动复原;实现简单(核心逻辑约 50 行);会话级状态省掉持久熔断器的状态管理复杂度。
  • 代价:给工具调用加了一层间接;阈值要理解每个工具的失败特征来调;可能掩盖本来单次重试就能自然解决的间歇性错误;要提示 Agent 优雅处理 CircuitOpenError(回退意识);只有 1-2 个高度可靠工具时没用。

模式二:模型回退(Failover-Aware Model Fallback)

问题

AI 模型请求会以各种各样、往往不透明的方式失败。简单重试逻辑分不清:

  • 瞬时失败(超时、限流):加退避重试有效。
  • 语义失败(认证错误、计费问题):重试没用。
  • 用户中止:重试浪费资源还惹恼用户。

用多个模型或提供商的 Agent,需要尊重失败语义的智能回退策略,避免重试循环,并给出清晰的诊断。

方案

语义错误分类 + 智能回退链。 每个失败被归类成具体的原因类型,回退行为按原因定制。多模型回退链按请求配置,带提供商白名单和冷却跟踪。

核心概念:

  • 错误分类:把失败映射成语义原因类型(timeoutrate_limitauthbillingformatcontext_overflow),把提供商特有的错误码映射成统一的语义类型,一致处理。
  • 按原因回退:不同原因触发不同回退行为:
    • timeoutrate_limit:用链里下一个模型重试。
    • authbilling:立即失败,重试没用。
    • formatcontext_overflow:可以用调整后的请求重试。
  • 用户中止检测:区分用户发起的中止和超时引起的中止。用户中止立即重抛;超时才触发回退。
  • 多模型链:有序的 {provider, model} 候选列表。每次尝试用下一个候选,直到成功或用尽。
  • 提供商白名单:按提供商限制模型,防止回退到不兼容的模型。
  • 诊断跟踪:每次失败尝试都记录错误详情、原因、状态码、提供商/模型,供调试。
async function runWithModelFallback({ candidates, run }) {
  const attempts = [];
  for (const candidate of candidates) {
    try {
      const result = await run(candidate.provider, candidate.model);
      return { result, ...candidate, attempts };
    } catch (err) {
      const reason = classifyFailoverReason(err);
      if (reason === "auth" || reason === "billing") throw err;  // 重试没用
      if (isUserAbort(err)) throw err;                            // 用户取消,不回退
      attempts.push({ ...candidate, error: err, reason });
      // 继续下一个候选
    }
  }
  throw new Error(`所有模型都失败了: ...`);
}

// 错误分类示例
function classifyFailoverReason(err) {
  if (status === 402) return "billing";
  if (status === 429) return "rate_limit";
  if (status === 401 || status === 403) return "auth";
  if (status === 408) return "timeout";
  if (message.includes("context window")) return "context_overflow";
  return null;
}

配置示例:

agents:
  defaults:
    model:
      primary: "anthropic/claude-sonnet-4-20250514"
      fallbacks:
        - "openai/gpt-4o"
        - "google/gemini-2.0-flash"

证据

  • 证据等级:生产验证validated-in-production),来自 Clawdbot。

怎么用

  1. 定义回退链:按用例(编码 vs 通用聊天)指定替代模型的有序列表。
  2. 配白名单:把回退限制在支持你请求格式的模型(比如只要图像模型)。
  3. 分类错误:把提供商特有错误码映射成语义原因,一致处理。
  4. 跟踪尝试:每次回退尝试记录提供商、模型、错误、原因,供可观测性。
  5. 处理耗尽:所有候选都失败时,聚合错误消息,给出可行动的反馈。

要避开的坑:

  • 过度回退:回退链太多会在提供商之间级联失败。用指数退避 + 抖动,防止惊群问题。
  • 语义不匹配:回退模型可能能力不同(视觉、工具)。按必需特性过滤。
  • 静默失败:有些错误(format)说明请求不兼容,回退可能同样失败。

取舍

  • 好处:瞬时失败(超时、限流)不阻塞 Agent;成本优化,高级模型不可用时回退到便宜模型;诊断清晰,尝试历史显示哪些模型为何失败;尊重用户中止,区分取消和超时,避免无谓回退。
  • 代价:每次失败尝试都加一轮往返延迟;不同模型响应不同,可能影响下游解析;回退链可能让单个逻辑请求产生多次 API 计费;配置(白名单、链、提供商特有行为)增加运维开销。

模式三:金丝雀发布与自动回滚(Canary Rollout & Auto-Rollback)

问题

Agent 行为频繁变化:提示词更新、工具策略、路由规则、评估器阈值。即使很小的策略改动,也可能在成本、延迟、安全或任务质量上造成大面积回归。 不经过分段曝光就全量发布,回滚慢,用户影响大。

方案

把 Agent 策略改动当生产发布来对待:先放小流量,监控先行指标,越界就自动回滚。

核心组件:

  • 流量切分器:把固定百分比的路由到新策略。
  • 策略版本注册表:不可变的标识符。
  • 实时监控:质量、延迟、失败率、安全标志、花费、目标达成率、无限循环检测。
  • 回滚自动化:无需人工干预恢复到上一个稳定策略。
  • 可选的影子模式:在用户曝光前先验证技术稳定性。

推荐阶段:

  1. 1% 流量金丝雀:快速发现异常。
  2. 5-10% 验证阶段:更严格的阈值。
  3. 25-50% 浸泡期:在混合负载下验证稳定性。
  4. 100% 全量:只在所有 SLO 和安全条件都满足时。
policy = registry.current_candidate()
traffic = splitter.assign(request, canary_percent=5)

response = run_agent(policy if traffic.canary else registry.stable())
metrics.ingest(response, policy.version)

if monitors.breach(policy.version):
    registry.rollback_to_stable()
    alert("自动回滚已执行", policy.version)

证据

  • 证据等级:成熟established),来自 Codex(OpenAI)。
  • 金丝雀发布是 SRE 的成熟实践,直接套用到 Agent 策略;MI9 Runtime Governance(2025)、AGENTSAFE Safety Evaluation(2025)提供了运行时治理和安全评估的参考。

怎么用

  • 任何可能改变外部行为的改动都用:提示词、工具、评估器逻辑、记忆策略、路由。
  • 发布开始前定义回滚触发器。设观察窗口(比如 2+ 分钟),避免误报。
  • 回滚要确定性:总是恢复到最后一个已知良好的版本。
  • 策略产物带版本化元数据存储,让事故可复现。

取舍

  • 好处:限制爆炸半径,缩短恢复时间。
  • 代价:需要发布编排、更丰富的遥测、清晰的版本卫生。

模式四:Dead-Man's Switch(Dead-Man's Switch for Scheduled Agent Jobs)

问题

一个 Agent 工作区会累积定时任务:晨间摘要、后台任务管理器、每周自审、记忆合并。这些任务静默失败。调度器配置错误、OAuth token 过期、权限回归,都不会在人类看的地方抛出任何错误,任务日志只是不再出现。没有东西盯"缺失",所以一个坏掉的每日任务可能坏了几个星期,才有人注意到输出没了。

方案

把监控方向反过来:对"缺失的成功"报警,而不是对"错误"报警。

  • 每个定时任务在每次运行的日志末尾,写一个显式的成功哨兵(比如 MORNING_BRIEF_OK)作为最后一步。
  • 一个新鲜度检查器知道每个被跟踪任务、它的节奏、它的过期窗口。它扫每个任务的最新日志:哨兵在且新鲜 = FRESH;缺失、过期、没哨兵 = 记一条发现。
  • 发现落进 Agent 自己的任务队列,也就是运维本来就在看的地方,而不是一个没人打开的单独 dashboard。
  • 一个按任务的 manual 标志,豁免按需任务不被判过期,避免检查器对本来就该不定期跑的任务乱报警。
  • 检查器用和它盯的任务不同的节奏跑(每周审计 + 按需),在不引入新基础设施的情况下回答"谁来盯检查器"。
for task in tracked_tasks:
    log = latest_log(task)
    if task.manual and log is None:        status = MANUAL_OK
    elif log is None or age(log) > task.window: status = STALE
    elif task.sentinel not in log.text:    status = NO_SENTINEL
    else:                                  status = FRESH
    if status not in (FRESH, MANUAL_OK):
        file_finding(task, status)   # 落进 Agent 的任务队列

证据

  • 证据等级:中medium),来自 James Ross 的生产工作区。
  • 有价值的发现:在生产里抓住了一个多日静默失败(权限回归让每日任务以只读模式运行;配置看起来正确,缺失的哨兵证明了问题)。底层反转对人类 cron 任务早就是成熟做法(Healthchecks.io)。
  • 未验证:Agent 特定变体只在一个生产工作区验证过;它抓不住"跑了但产出是错的"的任务(那类要配合合成金丝雀)。

怎么用

  • 工作区有两个以上定时 Agent 任务时就值得用。要求:持久的按次运行日志、写进每个任务指令的成功哨兵约定、用你本来信任的节奏调用的检查器(循环自审是天然宿主)。
  • 按任务调过期窗口:两小时跑一次的任务和每周跑一次的任务,"迟到"的定义完全不同。

取舍

  • 好处:抓住配置检查抓不住的失败类别("配置了"不等于"在跑");不需要外部服务;哨兵约定每个任务就一行。
  • 代价:任务来来去去,按任务配置要持续维护;跑了但产出是垃圾的任务照样过哨兵;检查器自己需要一个宿主节奏。

四个模式怎么选

场景 推荐模式
外部工具反复失败,白烧 token 熔断器
模型提供商抖动,要智能切换 模型回退
改提示词/策略,怕大面积回归 金丝雀发布与自动回滚
定时任务静默失败,没人发现 Dead-Man's Switch

四个模式是韧性工程的四道防线熔断器挡住"工具层"的重复失败,模型回退扛住"模型层"的提供商抖动,金丝雀守住"发布层"的改动风险,Dead-Man's Switch盯住"任务层"的静默死亡。从最细的工具调用,到最大的策略发布,四道防线把 Agent 系统撑起来。

实践清单

  • 每个外部工具配熔断器:坏工具立即熔断,半开探测自愈
  • 熔断打开时通知 Agent 改用替代工具,记录状态变化
  • 模型请求按语义分类错误(timeout / rate_limit / auth / billing / format)
  • 回退链按原因触发:瞬时失败换模型,auth/billing 直接失败,用户中止不回退
  • 回退加指数退避 + 抖动,防惊群;按必需能力过滤回退模型
  • 策略改动走金丝雀:1% → 5-10% → 25-50% → 100%,越界自动回滚到已知良好版本
  • 回滚触发器发布前定义好,观察窗口 2+ 分钟防误报,策略版本化存储
  • 定时任务写成功哨兵,新鲜度检查器对"缺失的成功"报警
  • 检查器落进 Agent 自己的任务队列,用不同节奏跑(谁盯检查器)
  • 按任务调过期窗口,manual 任务豁免,防乱报警

本章小结

  • 熔断器:坏工具立即熔断,不再白烧 token,省 40%-60%。
  • 模型回退:按失败语义智能切模型,瞬时失败重试、语义失败直抛、用户中止不回退。
  • 金丝雀发布:策略改动先放小流量,越界自动回滚,限制爆炸半径。
  • Dead-Man's Switch:成功哨兵 + 新鲜度检查器,盯"缺失的成功",抓静默死亡。
  • 韧性工程是最后一道关:评测告诉你对不对,可观测告诉你为什么坏,韧性让系统坏的时候不崩。

到这里,第四部分"让它可靠"就讲完了。你现在的链路是:结构化输出(14)→ 验证循环(15)→ 评测基建(16)→ 可观测性(17)→ 韧性工程(18),从"输出稳"到"系统扛得住"。下一部分,进入"让它安全":信任边界。先从第 19 章威胁模型开始。

📑 Agent 模式实战:生产级 AI Agent 的工程模式

1 第 1 章 什么是 Agent 模式 2 第 2 章 规划-执行-观察:先想清楚,再动手 3 第 3 章 反思闭环:让 Agent 学会检查自己的作业 4 第 4 章 委派:让主 Agent 学会把活分出去 5 第 5 章 上下文预算治理:把 token 当成钱来管 6 第 6 章 上下文压缩与精选:装不下怎么办 7 第 7 章 上下文最小化:别让脏东西留在脑子里 8 第 8 章 记忆体系:让 Agent 记得住过去 9 第 9 章 学习沉淀:让 Agent 和团队一起变聪明 10 第 10 章 工具接口哲学:让 Agent 能用、好用、用得起 11 第 11 章 工具发现:让 Agent 在几百个工具里找到对的 12 第 12 章 执行环境:Agent 在哪动手、怎么动手 13 第 13 章 代码执行与沙箱:先写码,再跑码 14 第 14 章 结构化输出与契约:让 Agent 的输出能接住 15 第 15 章 验证循环:Agent 怎么检查自己的作业 16 第 16 章 评测基建:怎么系统地检验 Agent 17 第 17 章 可观测性:看见 Agent 在想什么、在干嘛 18 第 18 章 韧性工程:扛得住部分失效 19 第 19 章 威胁模型:先看风险长什么样,再谈防御 20 第 20 章 控制流隔离:把"谁做决定"和"谁执行"分开 21 第 21 章 权限与审批:谁有权干什么、谁点头 22 第 22 章 凭据与出口:Agent 手里的钥匙和门 23 第 23 章 多智能体信任:多个 Agent 之间怎么互信、怎么审计 24 第 24 章 反馈信号设计:给 Agent 的是信号,不是更大的提示词 25 第 25 章 评测驱动的改进:让 Agent 在真实使用和对抗测试里变强 26 第 26 章 强化学习:把反馈变成训练信号 27 第 27 章 复合式进化:让 Agent 系统越用越值钱 28 第 28 章 多智能体协调:让一群 Agent 一起干活不掉链子 29 第 29 章 模型路由:谁用哪个模型,怎么用得起 30 第 30 章 推理搜索结构:让 Agent 多想想,而不是一条道走到黑 31 第 31 章 控制谱系:从自动补全到完全自主的滑动条 32 第 32 章 团队与产品:把 Agent 变成团队资产,而不是个人玩具
← 返回本书大纲