2026 年 9 月 3 日,Claude、Grok,以及部分 ChatGPT/Codex 功能先後出現服務異常。真正值得工程團隊追問的,不是「到底是哪一家雲端害的」,而是:如果你的產品已經串了三家模型,為什麼仍可能一起失去服務?這篇 LLM Chaos Drill 教學會把答案變成一套可執行的演練:先畫故障域,再限制重試,接著注入 5xx、429、串流中斷與狀態接力,最後用同一張 receipt 驗收恢復時間、成本與資料一致性。
先記住這條式子:多模型備援 ≠ 高可用;高可用=獨立故障域+有限重試+可攜狀態+安全降級。
你不必先是 SRE。只要已有一個會呼叫 LLM 的 API、Agent 或客服流程,就能跟著完成。讀完後,你會得到一張依賴圖、五個故障注入案例、一份控制面偽程式碼,以及可重跑的驗收清單。
TL;DR:三個 provider,不等於三個故障域
- 2026 年 9 月 3 日三家事故的時間窗有交集,但開始時間不同、影響範圍不同,也沒有公開的共同根因。
- 真正會讓 fallback 一起失效的,常是你自己的共用 DNS、登入、帳務、gateway、狀態庫,或多層 SDK 同時重試。
- 一次請求只能有一位 retry owner;要同時設總 deadline、attempt 上限、retry budget、full jitter 與 circuit breaker。
- 狀態接力不能只複製聊天文字;還要帶上 turn ID、已提交工具結果、狀態版本與 operation ID,避免重複扣款或重複下單。
- 演練成功不是「最後有回字」;而是恢復時間、額外呼叫、費用、重複副作用與資料一致性全部過門檻。
先校正事件:是重疊事故,不是已證實的共同故障
依三家公開紀錄,Claude 的主要多模型事故在 13:26 UTC 開始,Grok 的主要產品與 API 表面在 13:30 開始,而 OpenAI 對 ChatGPT/Codex 的事故在 14:43 才開始。若以 Anthropic 明確表示使用者影響結束的 16:16 為界,三個事故窗從 14:43 到 16:16 相交,共 93 分鐘。這些紀錄描述的是部分或廣泛功能異常,不等於每位使用者、每個區域都完全離線。

截至 2026 年 9 月 5 日,公開證據能支持「時間重疊」,卻不能把三件事合併成一個共同 RCA。OpenAI 後來對 WIRED 描述的是 routing error;SpaceXAI 把 Grok 異常指向 Memphis compute center;Anthropic 的狀態頁則說已識別原因,沒有公開命名基礎設施供應商。換句話說,Azure、Cloudflare、流量轉移或協同攻擊等說法,都不能拿來當這篇架構的前提。
這反而是最好的演練題:可靠性設計不該靠猜中昨天的根因,而要證明不同根因組合發生時,系統仍能安全收斂。
LLM Chaos Drill 第一步:畫出六層 dependency map
先別急著寫「A 失敗就切 B」。拿目前的 production path,從使用者請求一路畫到回覆提交。至少拆成以下六層:
- Provider 與模型層:模型端點、區域、配額、context window、tool schema 是否真的互換。
- 網路入口層:DNS、CDN、TLS、egress、proxy。三家 hostname 不同,仍可能共用同一個企業 DNS 或 NAT。
- 身分與帳務層:secret manager、KMS、SSO、組織用量上限與付款。三把 API key 不代表三套獨立控制面。
- Gateway 與 SDK 層:router、rate limiter、快取、observability,以及 SDK 內建 retry。這裡最容易製造重試放大。
- 工作流狀態層:system prompt、對話、檢索結果、工具結果、已送出的 token、狀態版本。
- 副作用層:寄信、扣款、下單、建單、寫資料庫。模型換成功,副作用重做一次仍是事故。

在圖上替每一個節點填四欄:owner、failure signal、fallback、state boundary。若某節點沒有可觀測訊號,這次 drill 先停在這裡補監控;否則故障注入後,你只會知道「很慢」,不知道慢在哪裡。想把 request ID、provider latency 與 cache 命中串起來,可先讀 AI Agent 可觀測性教學。
第二步:先定義「安全完成」,再定義 fallback
對聊天摘要而言,fallback 模型給出較短答案或許仍算完成;對退款、醫療分流或交易工作流而言,缺一個工具結果就不能自動完成。因此每條工作流先寫三個狀態:
- FULL:模型、檢索與工具都正常,回傳完整答案。
- DEGRADED:改用較小上下文、關閉非必要工具或只提供已驗證資料,但仍符合產品承諾。
- HUMAN/QUEUE:保留已提交狀態,告知使用者請求已排隊,交由人工或延後重放;不能偷偷猜完。
這一步會直接決定哪些 provider 能互換。若資料政策規定敏感內容只能留在單一供應商,另一家模型就不是 fallback;它只適合處理去識別後的子任務。若工具呼叫 schema 不同,則要先在 gateway 建一層 canonical tool contract,而不是把 A 的 JSON 原封不動塞給 B。
第三步:只留一位 retry owner,封住重試風暴
最危險的設定是:瀏覽器重試 gateway 三次、gateway 重試 SDK 三次、SDK 又重試 provider 三次。三層各「再試三次」可能把一次操作放大成數十次下游嘗試。Google SRE 對 cascading failure 的做法,是使用隨機化 exponential backoff、每個請求的上限,以及整個服務共用的 retry budget。
實作時,把 gateway 設成唯一 retry owner;若 SDK 內建 retry,明確關閉或納入同一份 attempt 計數。每次工作流都有一個不可延長的 global deadline,單次 timeout 必須小於剩餘時間。遇到 429 時先讀 Retry-After;沒有提示才算 full jitter:隨機等待 0…min(cap, base × 2^attempt)。重試預算耗盡就打開 breaker,不可因為換 provider 而把預算歸零。
關鍵規則:provider switch 是一次新的 attempt,不是一張免費續命券。
第四步:用 circuit breaker、queue 與人工模式收斂
Retry 是「這一次要不要再試」;circuit breaker 是「整個服務現在還要不要繼續打」。依 Circuit Breaker pattern,健康時是 Closed,連續錯誤跨過門檻後進 Open;冷卻期結束只放少量 probe 到 Half-open,成功才逐步恢復,失敗則回 Open。
不要只用全站錯誤率開關。至少以 provider × region × capability 分桶,例如文字生成正常、streaming 壞掉時,非串流請求不必一起停。相反地,若共用 gateway、DNS 或 identity 壞掉,切哪家都沒有用;控制面應直接進 queue 或人工模式,避免把剩餘供應商一起壓垮。
第五步:把對話變成可攜、可提交的狀態
多數簡易 fallback 只把 message history 傳給下一家,卻忘了「剛剛做到哪裡」。建議替每個 turn 保存一份 canonical envelope:
{
"conversation_id": "conv_42",
"turn_id": "turn_18",
"state_version": 7,
"operation_id": "refund_9f2a",
"input": "我要取消剛才的訂單",
"tool_results": [
{"name": "cancel_order", "status": "committed", "receipt": "ord_731"}
],
"assistant_output": {"status": "incomplete", "visible_chars": 2},
"policy": {"sensitive_data": "provider_a_only"}
}
committed 是最重要的界線。若工具已取消訂單、模型串流卻只送出「已為」就斷線,fallback 不能再呼叫一次工具,也不能把兩段文字硬接起來。它應讀取 receipt,產生一個全新的恢復回覆:「訂單已取消,確認碼為 ord_731。」所有有副作用的工具都要吃 operation_id 並具備 idempotency;並發切換則用 state_version 或 compare-and-swap 防止舊寫入蓋掉新狀態。
若你的系統還沒有統一的工具與狀態邊界,先從 AI Agent Harness 與 Agent Harness 實作補齊,再做跨 provider 接力。
第六步:寫一個有總 deadline 的控制面
下面是控制面的最小邏輯。它刻意只展示 deadline、budget、breaker、狀態提交與降級順序;正式環境還要補上各 SDK 型別、錯誤正規化、durable store、分散式鎖、tracing 與 secret 管理。
async function runWorkflow(request, state) {
const deadline = now() + 20_000
const providers = routeByPolicy(request, state)
for (const provider of providers) {
if (now() >= deadline) break
if (breaker.isOpen(provider, request.capability)) continue
if (!retryBudget.take(1)) break
try {
const remaining = deadline - now()
const response = await provider.generate({
request,
state: state.snapshot(),
timeoutMs: Math.min(6_000, remaining),
sdkRetries: 0
})
await state.commitCompletedTurn(response)
breaker.recordSuccess(provider)
return response
} catch (error) {
await state.recordAttempt(error)
breaker.recordFailure(provider, error)
if (!isTransient(error)) break
await wait(retryAfterOrFullJitter(error, deadline))
}
}
return enqueueOrHumanHandoff(state.committedSnapshot())
}
範例中的 20 秒 deadline、6 秒 attempt timeout 只是練習環境起始值。實際數字要由使用者可接受等待時間、你自己的 p95 latency 與 queue SLA 倒推。最容易漏掉的是串流:像 Anthropic 的 API error 說明所示,SSE 可能在先收到 HTTP 200 後才發生錯誤,所以「拿到 200」不能等同「turn 已提交」。
第七步:一次注入五種故障,不只測單家 5xx
在 staging 或完全隔離的演練環境,把故障開關放在你控制的 adapter 前面,不要真的攻擊供應商。每個案例都固定相同 prompt、state seed 與 operation ID,跑完輸出 machine-readable receipt。

案例 A:單一 provider 回 5xx
預期:單次 timeout 內結束、同一 retry budget 扣一次、breaker 計分,然後只切一次 provider。檢查 SDK 是否暗中重試;若 trace 顯示一次 gateway attempt 對應多個下游 request ID,先修 retry ownership。
案例 B:兩家同時 429/timeout
預期:尊重各自 Retry-After、full jitter 不形成整點尖峰、總嘗試不超過上限。不要讓第二家有一份全新的 budget;否則 provider 越多,事故期間的放大倍數越高。
案例 C:共用 DNS、identity 或 gateway 失效
預期:健康 probe 能分辨「provider 壞」與「到所有 provider 的共同路徑壞」。後者直接進 queue/human,不做無效切換。這是多模型清單最常掩蓋的單點。
案例 D:串流送出一半後斷線
預期:前端標記該 turn 不完整;狀態庫沒有把半段助理文字當成完成訊息。fallback 以最後 committed state 生成替代回覆,不能接續一段自己沒看過的 token。
案例 E:工具已成功,回覆尚未提交
預期:相同 operation_id 不會再次執行工具;fallback 能引用既有 receipt;兩個 provider 同時晚到時,只有符合最新 state_version 的結果可提交。這一關若失敗,先不要開自動 failover。
LLM Chaos Drill 的 receipt 要記什麼?
每一輪演練至少保存以下欄位,才能比較修正前後,而不是靠截圖說「看起來好了」:
scenario_id、程式版本、設定 hash、固定輸入 seed。- 每次 attempt 的 provider、region、capability、request ID、開始/結束時間與錯誤類別。
- recovery time:從第一次可觀測故障到 FULL、DEGRADED 或 HUMAN 狀態。
- retry amplification:下游實際請求數 ÷ 使用者操作數。
- cost per completed task:包含失敗嘗試、fallback、重播與 judge,不只最後一次成功呼叫。
- state consistency:context probe 是否正確、是否出現重複副作用、版本衝突或半段回覆。
- breaker 的 Closed/Open/Half-open 轉換、queue age 與人工接手時間。
把每個結果同時寫入 trace 與 JSON receipt,再用 CI 比對門檻。可以從「任何重複副作用=0 容忍」、「amplification 不超過設計上限」、「global deadline 內必須進入明確降級狀態」三條開始;品質門檻則用自己的固定題組與 AI Evals 版本化。
一個完整例子:客服取消訂單在串流中斷時怎麼接
假設 provider A 已呼叫 cancel_order,資料庫回傳成功 receipt,接著只串出「已為」就中斷。正確流程如下:
- Adapter 將本次 assistant turn 標為
incomplete,但保留工具的committedreceipt。 - Gateway 扣除同一份 retry budget;若 A breaker 已開,挑選符合資料政策的 B。
- B 收到最後完整使用者輸入、已提交工具結果與狀態版本,不再次取得取消工具權限。
- B 產生完整的新回覆;舊的「已為」在 UI 被替換或明確標示中斷,不和新文字拼接。
- 狀態庫用 compare-and-swap 提交;若 A 的遲到回覆版本較舊,就丟棄並記錄 late result。
這個例子顯示:模型輸出只是工作流的一部分。真正的接力單位是「最後已提交狀態+可重入的下一步」。已有 gateway 的讀者,可搭配 AI Gateway、OpenTelemetry 與 failover 驗證,把同一個 operation ID 貫穿每個 span。
研究提供什麼證據?又缺了什麼?
2026 年的 ContinuityBench 用 150 段合成多輪對話、每套系統跑 5 次,形成 750 個 failover event。論文回報:轉送對話歷史的系統通過 744 次 context probe(99.2%),stateless baseline 則是 0 次。這支持「狀態必須跟著切換」的方向,但不能直接當成你的 production SLO。

該研究另顯示平均切換額外延遲很小,P95 尾端卻可增加超過 13 秒;這提醒我們不能只看平均數。更重要的限制是:模型與 API 組合固定、評測題是合成的、每段對話只注入一次故障,LLM judge 校準樣本也很小。你的 receipt 應把 streaming、工具 idempotency、真實資料政策與多重相關故障補進去。
常見失敗:看似有 fallback,其實只把事故延長
- 只測 happy-path 切換:A 立刻 500、B 立刻成功,完全沒碰到慢 timeout、429 或串流半斷。
- 每層都自己 retry:呼叫量呈乘法放大,恢復中的 provider 再次被打倒。
- 拿 HTTP 200 當完成:stream 開始後失敗,半段文字卻被保存成完整 turn。
- 只搬 messages:遺失工具 receipt、資料版本與已提交邊界。
- 所有供應商共用一個 breaker:單一 capability 異常拖垮健康路徑;或相反,所有 provider 每次都被輪詢。
- 只看 uptime:忽略重複扣款、額外 token、queue age 與錯誤但流暢的回答。
- 把備援當無限容量:平常只吃少量流量的 B,事故時未必承受得住 A 的全部工作。
若你的主要問題仍是一般路由與 provider pinning,可先看 OpenRouter provider pinning 教學;本篇的 LLM Chaos Drill 則是在路由完成後,驗證它遇到相關性故障會不會安全收斂。
上線前的最小驗收門檻
- 五個案例都能從同一份設定重跑,receipt 含版本與 seed。
- 任何一次使用者操作,都能追到確切下游 attempts;SDK 沒有隱藏重試。
- global deadline 到期後必定進入 FULL、DEGRADED、QUEUE 或 HUMAN,沒有無限 pending。
- provider 切換不重設 retry budget,也不繞過資料政策。
- 串流中斷不提交半段 turn;工具副作用有 operation ID 與 receipt。
- breaker 有分桶、有 Half-open probe,且共用依賴故障不會盲目輪詢供應商。
- dashboard 同時呈現 recovery time、amplification、每個完成任務成本、queue age 與一致性錯誤。
- 人工模式真的有人接、拿得到 committed state,而且演練過恢復後如何排空 queue。
FAQ:LLM Chaos Drill 常見問題
1. 串兩家 API 就算高可用了嗎?
不算。你還要證明兩條路徑沒有共用的 DNS、identity、gateway、state store 與容量瓶頸,並且切換後資料政策、上下文與副作用仍正確。
2. Retry 和 fallback 的順序怎麼排?
由唯一 retry owner 依錯誤類別、剩餘 deadline、breaker 與 budget 決定。短暫網路抖動可在同一路徑重試;已開 breaker、持續 overload 或區域故障才切路徑。兩者都消耗同一份預算。
3. 429 應該立刻換 provider 嗎?
不一定。先解析 Retry-After 與限流範圍,再看 deadline、配額與備援容量。立刻把全部流量搬走,可能把另一家也推進限流。
4. Circuit breaker 的門檻要設多少?
沒有通用常數。用正常時段每個 capability 的 error rate、最小樣本量、使用者 deadline 與 provider 恢復特性倒推;先在 shadow mode 看它會不會誤開,再逐步讓 Open 狀態真的擋流量。
5. 對話 history 全傳過去就夠了嗎?
不夠。還要傳已提交的 tool results、operation ID、state version、資料政策與 turn 完成狀態。否則語意看似連續,業務狀態仍可能重複或倒退。
6. 串流中斷後可以把兩家輸出接起來嗎?
通常不應直接拼接。兩個模型的 hidden state 不相同,第二家也不知道第一家尚未顯示的內容。把舊 turn 標成 incomplete,從最後 committed state 產生一段完整的新回覆較安全。
7. 沒有第二家付費帳號也能演練嗎?
可以先用本地 stub 模擬狀態碼、延遲、串流斷點與工具 receipt,驗證控制面。這能抓出重試、狀態與副作用 bug;正式上線前,仍要以真實 provider 做低流量 canary,才能量到實際配額與尾端延遲。
8. 演練多久跑一次?
先把五個案例放進每次 gateway、SDK、模型或 state schema 變更的 CI;另外排固定 staging drill。若 provider error mapping、配額或資料政策改變,也應重跑,而不是只等年度災難演練。
新手帶走這 5 件事
- 先畫故障域,再談多模型;provider 名稱不是可靠性證明。
- 一次請求只有一位 retry owner,且 retry 與 provider switch 共用 budget。
- 用 breaker 讓失敗收斂,用 queue/human mode 保住業務,而不是永遠硬撐。
- 狀態接力以 committed boundary、operation ID 與 state version 為核心。
- 用 receipt 驗收 recovery、amplification、成本與一致性;「最後有字」遠遠不夠。
想從更完整的學習路徑建立 Agent、評測與部署能力,可以從 AlphaLab AI 專區開始;要系統化練習,也可查看 AlphaLab 線上課程。
接著閱讀
左右滑動查看更多推薦
結論:先讓故障收斂,再追求無感切換
多模型架構最有價值的成果,不是儀表板上多了三個綠燈,而是當兩個燈一起變紅時,系統仍知道何時停止、保存了什麼、能安全做到哪一步。今天先挑一條有真實副作用的工作流,畫完六層依賴圖,關掉隱藏 retry,注入一次「工具成功+串流中斷」。只要 receipt 能證明沒有重複動作、沒有無限等待,而且在 deadline 內進入明確狀態,你才真正跨出了高可用的第一步。






