
1. 事件复盘那天下午我的 Agent 工作流是怎么“全线变红”的1.1 三个服务接连翻车的现场还原前一阵的某个工作日下午我正坐在电脑前准备让 Claude Code 跑一批代码迁移任务。本地队列里已经堆了一百多个待处理的 Agent 任务里面既有跨文件的重构也有测试用例的批量生成。我当时的计划是让这三套服务各司其职Claude Code 负责核心代码改动Codex 处理结构化的批量重构Grok 负责文本摘要和文案润色。结果下午三点出头队列里的任务开始一个接一个变红。先是 Claude Code 的 stderr 里持续刷出 529 状态码后面跟着 overloaded 字样偶尔还能看到 500。我一开始以为是自己的提示词太长或者上下文窗口超了还专门检查了最近的 workspace 配置。紧接着 Codex 也开始出问题请求打到 /responses 端点时先是高延迟后来直接超时重试两次之后反而报了配额相关的错误连组织设置都加载不出来。最后轮到 Grok那边跑着的文本摘要任务开始返回 5xx部分请求甚至返回了空内容一眼就能看出是服务端的问题。到 15:40 左右三套服务的对外接口几乎全部不可用或者处于严重降级状态。我刷了一圈官方状态页才发现这不是我本地网络或者代码的问题而是三家模型服务在同一天集体翻车。状态页从 investigating 一路变成 degraded直到第二天凌晨才陆续恢复 stable。说实话我当时的第一反应不是焦虑而是有点荒诞三家完全不同的公司、三套不同的技术栈竟然在差不多同一个时间段集体倒下了。1.2 我用五分钟做的第一轮应急排查遇到这种局面最关键的不是急着改代码而是先分清责任边界。我的排查次序是固定的先看本地网络再查服务状态最后才动重试和切换逻辑。第一步我先确认自己的网络连通性。简单 ping 一下目标域名再看 DNS 解析是否正常顺便用一个最小请求测试各家 API 的基础连通性。这里要注意ping 通了不代表接口就正常域名解析正常也不代表 TLS 握手和鉴权链路没问题所以我会再发一个最简单的鉴权请求来验证。这个环节大概花了两分钟确认本地网络没有任何异常。第二步打开各家状态页和第三方状态聚合页面发现 Claude、Codex、Grok 在同一个时间段都标记了异常基本可以断定是上游服务故障而不是我的配置问题。这一步很重要因为一旦确认是上游故障我就可以省掉排查本地配置的时间直接进入应急模式。第三步也是最容易做错的一步停掉所有自动重试把队列挂起防止雪崩式重试把配额耗尽。很多人遇到服务不可用时第一反应是加大重试力度这其实是最糟糕的做法。在全局故障期间你的每一次重试都在加剧上游负载而且自己的限流配额会在高峰期被快速耗光等别人恢复正常了你反而因为没额度而继续失败。当时我花五分钟做完这三件事后工作流从“拼命重试”的状态切换到了“原地等待”的状态剩下的就是持续观察状态页同时准备降级方案。现在回头看这几分钟的决定挽救了后续的大量配额和费用也让复盘时有了干净的时间线数据可查。1.3 这次宕机让我看清的三个隐患故障恢复之后我没有急着把队列跑满而是坐下来把这次事件暴露出的问题列了一遍。表面上看是“上游故障导致工作流暂停”但深挖下去我发现自己的 Agent 架构里有三个非常危险的隐患。第一个隐患所有关键任务都依赖单一厂商的 API。我的架构里虽然用了三家服务但每个特定任务都只绑定一个供应商。代码任务绑定 Claude Code 就是 Claude Code重构任务绑定 Codex 就是 Codex没有设计任何跨供应商的备份路径。这意味着任何一个供应商故障对应的那类任务就会全停。多供应商实际上只是“多入口”并没有形成真正的“多路径”。第二个隐患没有设计降级路径。平时能用的小模型完全没有接入本地推理也没有启用。其实我的电脑上装了 Ollama 和 LM Studio但从来没有把本地模型接进 Agent 工作流里总觉得云端 API 效果更好、速度更快没必要折腾本地。这次故障让我意识到降级链路不是“用不上”而是“必须有最好永远用不上”。第三个隐患队列没有持久化。我的任务队列跑在内存里进程重启就会丢任务更糟糕的是执行到一半的任务没有任何断点记录。后来恢复时才意识到如果当时需要重启服务我连“哪些任务跑到哪一步”都说不清楚。这个隐患平时不显眼但在故障恢复阶段会被无限放大因为你不仅要处理新任务还得人工核对旧任务的状态。这三个隐患叠加起来就是一次“集体翻车”就能让我全线瘫痪的根本原因。也正因为这次复盘我后面才下决心对 Agent 工作流做了一次比较大的容灾改造。2. 集体宕机背后为什么 Claude、Codex、Grok 会“手拉手”倒下2.1 三个服务的技术架构与依赖差异想要理解为什么三家公司会在同一天集体出问题得先看清这三家服务背后的架构差异。Claude 走的是 Anthropic 托管 API模型推理基本在自建推理集群里完成长上下文窗口是它的强项但也正因为长上下文推理负载很容易在高峰期被打满。Codex 作为 OpenAI 的工程化产品同样走云端 API重度依赖算力调度、身份鉴权、配额管理这几个核心系统。Grok 的生态入口则更特殊一些它主要通过 X 生态的接口和专属 API 对外提供服务访问路径和基础设施跟前两家差异很大。三者的故障表象也完全不一样。Claude 挂的时候报 529强调的是服务过载Codex 挂的时候报超时和配额异常说明是链路某处排队长Grok 挂的时候返回 5xx 和空内容更像是生成服务本身的稳定性问题。如果你只是用“模型不好用”来概括会错过很多诊断线索。服务典型入口主要任务类型故障常见表象Claude Code命令行工具 托管 API代码编写、长上下文推理529 overloaded、500Codex云端 API 工程化工作台批量重构、结构化任务请求超时、配额错误GrokX 生态接口 专属 API文本生成、摘要、润色5xx、空内容、高延迟2.2 共享依赖底层算力、API 网关与全球网络链路的连锁反应表面上看是三家独立公司在各自故障但从基础设施层面看它们之间的共享依赖比大多数人以为的要多得多。大型云厂商就那么多AI 公司再大也不可能完全自建所有的数据中心、CDN 和骨干网络。某个云区域出现网络抖动或者某家 CDN 供应商的节点出问题影响范围往往覆盖多家 AI 服务。更常见的连锁反应是某家头部 AI 服务在高峰期打满了某一区域的算力导致同区域的其他服务排队时间变长。而大模型推理服务有一个很统一的压力模式——高峰时段、长提示词、批量任务三路并发。这三路只要同时顶上来任何一个共享的网关层、鉴权层或者数据库层都可能成为共同瓶颈。这就是“集体翻车”的第一个机制不是三家约好了而是它们站在同一片地基上地基晃了一下三栋楼一起晃。2.3 鉴权与配额系统的常见故障模式除了底层基础设施还有一层高频故障点鉴权与配额系统。很多团队和我一样实际业务并不是直接连官方控制台而是通过某个统一接入网关或者服务商聚合通道来调用模型。做统一接入的好处是可以屏蔽不同商家的接口差异但坏处也很明显这层网关一旦出问题故障会被无差别放大。我见过几种典型的故障模式。第一种是鉴权服务抽风Token 校验超时所有请求都卡在“身份验证”这一步业务日志上看不出任何异常但实际没有一个请求真正到达模型推理层。第二种是配额计数器错乱明明账户余额充足却报 quota exceeded这种问题通常不是你的用量真的超了而是配额系统自身状态不一致。第三种是请求在网关层排队堵死网关线程池被打满新增的请求全部进入等待最后超时。复盘时不要只盯着服务状态页看也要看自己的调用链路日志分清到底是在哪一层出的问题。状态页只能告诉你“服务挂没挂”链路日志才能告诉你“你的请求到底卡在哪个环节”。这两份数据配合起来才能判断是该等着上游恢复还是该切换备用通道。2.4 对 Agent 工作流的启示单点依赖的放大效应这次集体宕机给我最大的启示是 Agent 工作流和普通脚本在故障面前的表现完全不同。普通脚本失败就是“这次没跑”顶多重跑一次就行了。Agent 不一样它是有状态的。代码改到一半、文本生成到一半、上下文被污染这些问题不是重跑就能解决的。举个例子我的 Claude Code 任务里有一条是“重构某个模块并更新所有引用”。如果任务执行到一半上游服务断了Agent 失败退出。等恢复之后我把它重新丢进队列它可能从头开始跑也可能带着上次未完成的上下文继续执行。如果它带的上下文是残缺的就可能生成出错误的代码改动而我作为人类在批量任务里很难及时发现这种“专业但错误”的输出。所以我把 Agent 工作流的默认假设从“模型服务是可用的”改成了“任何模型服务都会意外不可用”。在这个前提下调度、重试、降级、持久化就不再是锦上添花而是必须品。单点依赖的放大效应就是这么来的一个模型服务的故障通过 Agent 的状态传播最终污染了整条业务链路的产物。3. 我的 Agent 工作流是怎么搭的以及它在宕机中暴露出的问题3.1 工作流整体拓扑与分工说完了宏观机制回到我自己的架构上来。我的 Agent 工作流并不是一个复杂的分布式系统本质上就是一台开发机加一个本地任务队列人工或者定时器往队列里丢任务调度器再按任务类型分发给不同的模型服务。Claude Code 在我的工作流里承担的是重活写代码、跑测试、修 bug这些任务提示词长、执行时间长、依赖上下文窗口的深度。Codex 负责批量化的结构性任务跨文件重构、字段映射、重复代码清理这类任务的特点是格式要求严格、结果需要结构化返回。Grok 的工作则集中在文本侧日报摘要、社交文案生成、文章润色对代码的依赖少但对语言风格的要求高。三个服务的分工看起来非常清晰我甚至觉得自己已经做到了“不要把鸡蛋放在同一个篮子里”。但后来仔细一想这个认知是错的我只是把不同类型的任务分散到了不同供应商身上但每一类任务本身仍然只有一个执行路径。篮子分开了但每个篮子里还是只有一枚鸡蛋。3.2 关键选型逻辑为什么选这三个而不是只用一家很多朋友问我既然想要稳定为什么不干脆只用一家效果最好的模型把精力全部放在提示词优化上这个问题我在选型阶段也纠结过而且我确实见过只用一家模型跑通全部工作流的团队效率并不低。但我的结论是多模型协作的价值不在于“多一家就多一份备份”而在于不同模型在不同任务上的表现差异确实明显。我实测下来的感受是Claude Code 在长上下文代码推理上更稳Codex 在结构化输出和批量任务上更规整Grok 在文本改写和风格迁移上更自然。把任务分给最擅长它的模型整体效率是高于“用同一个模型硬扛所有任务”的。选三个而不是只选一个还有一个次要原因不同服务商的限流策略和计费模式不同分散使用可以让每个账号的配额都不至于紧张。但我要强调这种“分散”只能解决配额问题解决不了高可用问题。真正的高可用必须要求在同一个任务上至少存在两个可执行路径。这是我这次故障之后才想清楚的逻辑之前是把“多供应商”当成了“多路径”方向就错了。3.3 故障期间的直接损失队列堆积、上下文污染、费用异常故障当天和后续恢复期我实实在在承受了三类损失每一类都值得展开说。第一类是队列堆积。一百多个任务在本地排队等上游恢复后如果直接放开队列短时间内会有大量任务同时涌入 API。因为每个 Agent 任务拆开来看其实是多个 API 请求一个代码任务可能连续调用十几次模型这些请求会在同一秒全部打到服务商的网关直接把刚刚恢复的 API 再次打爆。这就是所谓的恢复性雪崩。我当天就亲眼看到了这个现象下午五点多状态页变绿我放开队列五分钟后 API 又开始报 529。第二类是上下文污染。Agent 执行到一半中断恢复后带着未完成的上下文继续跑结果生成了错误内容。这个问题在代码任务里尤其致命。比如 Agent 已经删了某个函数的所有调用点但还没来得及更新函数定义中断后它可能会把错误状态当成已知事实继续生成新的改动。我在人工审核时发现了好几处逻辑错误的代码全都是这种“半截上下文”导致的。第三类是费用异常。重试会消耗配额部分通道按请求计费空跑也产生费用。故障期间我为了尽快恢复服务启用了重试机制结果每一轮重试都在真实计费。后来对账时发现故障当天的模型调用费用比平时高出了一大截而且大部分费用都花在了失败请求和超时请求上完全没有产出。这个教训直接引出了后面“重试必须带熔断切换必须带降级”的改造原则。4. 如何让 Agent 工作流扛得住“集体翻车”高可用与多模型容灾方案4.1 模型网关层用统一抽象层屏蔽厂商差异经历了这次集体翻车我做的第一件改造就是在业务代码和各个模型 SDK 之间加了一层统一抽象也就是模型网关。这层抽象的核心目的只有一个把所有模型供应商的调用方式统一成一个接口让“切换供应商”变成改配置而不是改代码。在改造之前我的代码里直接散落着各家 SDK 的调用Claude Code 的任务调 Claude SDKCodex 的任务调 OpenAI SDKGrok 的任务又走另一套接口。一旦想切换供应商就得改一堆业务代码根本没有操作性。引入网关层之后业务侧只依赖一个统一的 complete 接口传入提示词和任务类型网关层内部负责路由到具体供应商处理重试、超时、熔断这些横切逻辑。# 简化版模型网关示意 class ModelGateway: def __init__(self, providers): self.providers providers # 按优先级排序的供应商配置 def complete(self, prompt, task_typedefault, timeout60): last_error None for provider in self.providers: try: response provider.complete(prompt, timeouttimeout) if response.is_ok(): return response except TimeoutError as error: last_error error continue except ServiceUnavailableError as error: last_error error continue raise last_error这段代码看起来很简单但它改变了整个工作流的容错逻辑几乎所有的重试都应该是“换一个供应商重试”而不是“同一个供应商无限重试”。网关层的配置可以存放在本地配置文件或者环境变量里按照任务类型配置不同的供应商优先级。比如代码任务优先 Claude备用 Codex文本任务优先 Grok备用 Claude批量重构优先 Codex备用 Claude。这样即使某一家完全不可用任务也只是换了一条路继续跑。4.2 路由与故障转移策略优先级、熔断、降级有了网关层还需要一套完整的路由与故障转移策略否则网关层就只是一个花架子。我现在的配置分三层优先级路由、熔断保护、按需降级。优先级路由解决的是“正常情况下走谁”的问题。每个任务类型都有一份供应商优先级列表网关层按顺序尝试。第一顺位供应商正常时流量全部走它保证输出质量第一顺位失败时自动切换第二顺位保证任务不中断。熔断保护解决的是“别在故障期间反复撞墙”的问题。我设置了一个简单的熔断规则同一个供应商连续失败 3 次就进入 60 秒的冷却期冷却期内不再接收任何新请求。这比单纯依赖超时重试要高效得多因为超时重试只是被动等待而熔断是主动绕开不可用的供应商。按需降级解决的是“高峰期别硬扛”的问题。有些任务并不要求顶级模型比如日志归纳、简单分类、消息总结这类任务在高峰期可以自动降级到便宜模型甚至本地模型。降级的核心不是“效果变差”而是“在可控的质量损失下保证任务不断流”。我通常会把所有任务分成核心任务、重要任务、普通任务三级核心任务永远走最强模型普通任务在检测到服务压力时可以自动降级。4.3 本地模型兜底LM Studio 与 Ollama 作为降级方案在所有云端供应商都不可用的极端场景下本地模型是最后的兜底方案。我现在的做法是在开发机上常年跑着一个本地推理服务用 Ollama 或 LM Studio 加载一个中等参数的模型作为整个工作流的离线降级选项。接入本地模型并不复杂因为本地推理服务一般都会暴露一个 OpenAI 兼容的 HTTP 接口网关层只需要把它当成另一个供应商配置进去即可。你甚至可以在不修改业务代码的情况下把供应商列表配置成Claude - Codex - 本地模型。这样当两家云端服务都不可用时任务会自动滑落到本地模型虽然没有云端那么强的推理能力但至少任务不丢、流程不断。我记得热词里正好有“Claude Code 调用 LM Studio 的本地模型”这个用法确实是真实的。Claude Code 这类工具本身也支持配置额外的模型端点你可以在它的配置文件里加一个自定义供应商指向本地服务的地址。但我的建议是本地模型更适合作为整个网关层的兜底而不是作为某个单一工具的替代。因为 Agent 工作流里的故障转移需要的是全局调度能力而不是某个工具能连上本地模型就行。4.4 任务不丢失队列持久化与断点续跑设计模型网关解决了“走哪条路”的问题队列持久化则解决了“任务会不会丢”的问题。现在我所有 Agent 任务在执行前都会先写入本地数据库SQLite 就够用了不需要引入额外的消息中间件。任务状态机也很简单pending 代表等待执行running 代表正在跑done 代表完成failed 代表失败retry 代表等待重试。关键点在于每个任务在执行前会把完整的提示词、模型配置、参数、当前进度快照都写入数据库Agent 每完成一个阶段就更新一次状态。如果进程重启或者任务中断调度器启动时会扫描数据库里的 pending 和 running 状态从最后完成的阶段继续执行而不是从头再来。# 任务状态机示意 stages { pending: [running], running: [done, failed, retry], retry: [running, failed], done: [], failed: [retry] }这个设计在故障恢复期尤其有价值。上次宕机恢复后我的调度器从数据库里捞出了所有未完成任务逐个检查状态能续跑的续跑需要人工判断的挑出来单独处理。对比之前“内存队列一崩全丢”的情况这份持久化帮我至少省了三个小时的人工核对时间。4.5 Agent 安全与权限隔离最小化风险说到 Agent 高可用很多人忽略了一个和故障相伴而生的安全问题服务故障期间Agent 的容错逻辑会处于压力测试状态此时如果输出质量下降或者上下文被污染Agent 可能执行错误操作。甚至更危险的是Agent 在自动执行命令时如果上下文里混入了误导信息它可能主动去执行一些高风险操作。所以我在故障转移之外同步加强了 Agent 的安全隔离。所有 Agent 任务都在独立的容器或者子进程中执行不能再宿主机上直接跑命令。文件系统限制在固定的工作目录内Agent 能读写的路径都是预先声明的。所有外部操作比如发消息、改配置、执行高权限命令都必须写入审计日志并且对高危命令设置了人工确认机制。这套隔离机制在正常时期看起来有点繁琐但在故障转移期间非常必要。因为当你把任务切换到备用模型甚至本地模型时输出质量本身就不如主模型稳定此时安全边界的价值就体现出来了就算模型输出错了错误也只会在隔离环境里产生不会扩散到真实系统。5. 宕机期间的真实排障过程与经验记录5.1 定位环节从现象到根因的排查思路讲完架构改造我想再记录一下故障当天的排障过程因为很多朋友问过我“你是怎么在半小时内确认是三家公司集体故障的”。说白了是靠一套固定的排查思路不是靠直觉。那天 Codex 那边先报了一个异常错误信息里带着 endpoint 相关字样我第一反应是本地配置或者网络链路的问题。这时候我没有直接去翻配置而是按照固定的顺序排查先检查客户端配置文件的基本参数是否正确再验证网络连通性然后看服务状态页最后拉调用链日志。结果发现配置没有任何变化网络也正常状态页确实标红这才把矛头指向上游服务。这个排查顺序看起来平淡无奇但它最大的价值是帮助你尽快排除“自己这层”的问题。我在实际工作中见过太多人一看到接口报错就开始改代码结果改了半小时发现是服务商挂了。排查的首要目标不是找到根因而是确认问题不在你这一侧。一旦确认是上游故障你就可以把精力从“修”转移到“绕”上了。5.2 临时恢复方案切换备份 Key、调整超时与重试确认是上游故障之后临时恢复方案的核心就三个词切换、限速、保核心。先说切换。我在平时就把每个服务商的 API Key 分成主 Key 和备用 Key存到独立的密钥管理工具里。正常情况下主 Key 承担全部流量故障时手动切换到备用 Key。为什么这样做因为有时候服务商并不是所有鉴权通道都故障主 Key 对应的通道可能正好堵在故障链路上备用 Key 走另一条通道反而能正常工作。虽然这种情况不多但切换一次的成本很低值得尝试。再说限速。故障期间我强烈建议不要再按平时频率发请求即使服务恢复了一部分也应该逐步放量。具体做法是把并发数降到平时的二分之一甚至四分之一并且把超时时间从平时的 60 秒缩短到 10 到 15 秒。快速失败比无限等待更好因为快速失败能让请求尽快结束把机会让给真正可能成功的请求。最后是保核心。把所有非核心任务全部暂停把配额留给最重要的任务。我在故障期间直接把所有文本摘要任务和文案生成任务停掉了只保留代码验证类和依赖升级类的核心任务。虽然最后还是因为上游恢复后的流量洪峰导致部分任务失败但至少核心业务没有完全中断。5.3 恢复后的 24 小时写复盘报告、改造架构故障恢复后的 24 小时我没有急着把积压任务跑完而是先做了两件事写复盘报告开始改造架构。复盘报告不需要写成长篇大论但必须包含六个要素现象、时间线、根因、影响范围、改进项、验证清单。尤其重要的是时间线最好精确到分钟。我是通过状态页截图和本地任务日志还原出完整时间线的这也是为什么我在 1.2 里强调“先截图、先保存状态页 URL”因为事后没有时间线数据复盘就是空谈。架构改造则是按照第四节的方案一步一步落地。先把模型网关层写出来再把任务持久化加上然后配好熔断与降级策略。整个过程花了大概三天因为要保证改造过程中原有的 Agent 任务不受影响所以我先在小流量场景下试运行网关层验证切换逻辑稳定之后才逐步把所有任务切换到新架构上。改造之后我还加了一个简单的监控看板各家服务的健康状态、最近一小时的失败率、平均延迟、配额消耗速度。这些数据不复杂但足够让我在下一次服务异常时更快做出判断而不是靠肉眼盯队列。6. 常见问题速查与避坑清单6.1 服务不可用时的五级响应速查表下面这份速查表是我现在工作流里实际用的响应规则。大家可以根据自己的业务体量调整参数但响应层级这个概念可以参考。响应级别触发条件响应动作一级单个请求超时自动重试一次不切换供应商二级同一供应商连续 3 次失败熔断 60 秒切换备用供应商三级所有云端供应商不可用切本地模型暂停非核心任务四级部分业务受损、核心链路不稳进入保守模式只跑只读任务五级完全瘫痪停队列保留现场启动人工备份流程使用这份速查表时最重要的是让团队成员都熟悉规则而不是只有一个人知道。因为我身边就有过那种情况核心负责人不在工位上其他人遇到故障都不知道该怎么响应只能等一等就是半小时故障影响面就被放大了。6.2 我自己踩过的坑与经验教训最后分享几个我在这次改造过程中踩过的坑或者说是从故障中学到的教训。第一个坑是无限重试。故障期间我一度开了“失败自动重试 5 次”结果恢复后所有任务的重试请求瞬间涌进 API 网关再次触发限流。现在我的重试策略是“最多重试 2 次 指数退避 熔断”宁可让任务进入待重试队列也不要在同一时间点集中重试。第二个坑是任务没有持久化。这件事我已经在前面讲过了这里再强调一次内存队列真的不靠谱Agent 任务比普通定时任务更需要持久化因为它的执行是有状态的丢失一个任务可能意味着丢失一整个阶段的工作。用 SQLite 做任务持久化成本几乎为零收益却非常大。第三个坑是只做多供应商不做多路径。多供应商只是入口多多路径才是真的容灾。同一个任务必须有两个以上可以执行它的模型哪怕第二个模型效果差一些也好过没有。我现在给所有核心任务都配置了至少两个供应商普通任务至少一个主选加一个兜底。第四个坑是忽略上下文污染。这次故障之后我给长任务增加了一个检查点机制Agent 每完成一个阶段就把当前上下文的关键信息压缩成摘要连同状态一起存到数据库。恢复执行时优先使用摘要恢复而不是把断掉的完整上下文原样灌回去。这样能显著降低“带着残缺上下文继续跑”导致的错误输出。整体来说经历过这次集体翻车我最大的变化是把“稳定”当成了 Agent 工作流的一等公民来对待。模型能力固然重要但没有工程手段托底再强的模型也扛不住上游的一阵风。我现在的工作流已经默认“降级优先于完美”任务可以慢但不能丢模型可以不是最强的但必须有兜底。这个原则应该能让我在下次面对类似情况时从容很多。