iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0

Day 4 做完 timeout、退避重試和降級後,我本來以為這件事差不多收工了:SOP 查不到,最多重試幾次,然後不要開工單。

但那只限制了「同一個請求」。如果 SOP 服務已經壞了,下一次使用者問同一件事,Agent 還是會從頭再試一輪。十個請求同時進來,每個請求都允許三次嘗試,壞掉的 SOP 服務就會收到三十次請求。每一個 Agent 都有節制,合在一起卻還是在追著故障服務打。

這就是我在這個 mock Helpdesk Agent 補上 circuit breaker 的原因。

本文的程式碼版本:day-08-retry-circuit-breaker。它只操作記憶體中的 mock SOP 和 mock 工單,不會連到任何外部系統。

重試解決的是偶發失敗,不是服務故障

先把 Day 4 的規則講清楚。這個專案只重試唯讀的 search_it_sop;建立工單是寫入操作,不能因為 timeout 就直接重送,而是靠 idempotency key 避免同一個執行重複建立。

RetryPolicy 的預設值是三次嘗試,退避等待為 0.25 秒、0.5 秒,最大等待值是 1.0 秒。也就是說,第一次失敗後才等 0.25 秒,第二次失敗後等 0.5 秒,第三次失敗就停止。

這樣做有用,但它有一個前提:失敗是暫時的。

如果服務只是瞬間超時,退避後再試可能剛好成功。反過來說,服務已經整段不可用時,重試只是在把同一個失敗放大。Microsoft 對 retry storm 的描述也正是這個問題:大量客戶端在故障期間反覆重送,讓本來就吃緊的服務更難恢復。Retry storm antipattern

我沒有把 retry 當成萬用修復鈕。這個 demo 也刻意沒有加入 jitter 或讀取服務端的 Retry-After;它只用固定延遲把規則做得可測。真的要面對很多並發請求時,還要加上 jitter,並依服務端回傳的等待時間調整,避免所有 client 在同一秒一起醒來。AWS 的 retry with backoff 模式也特別提醒,退避本身不能解決持續性故障。

我把「一次請求的重試預算」和「服務狀態」分開

這次新增的 CircuitBreaker 不直接計算每一次 timeout,而是看「一整輪 retry budget 是否用完」。預設連續兩輪唯讀 SOP 查詢都耗盡重試預算,熔斷器就打開。

它有三個狀態:

  • Closed:照 Day 4 的規則執行查詢和有限重試。
  • Open:30 秒冷卻時間內直接拒絕下一個 SOP 查詢,不再呼叫工具。
  • Half-open:冷卻結束後只放行一次探測請求;成功就回到 Closed,失敗就立刻回到 Open。

這段放在 retry 邊界,而不是讓模型自己在 prompt 裡判斷服務是否健康:

results = run_with_retry(
    lambda: self.knowledge_base.search(query),
    operation_name="search_it_sop",
    policy=self.retry_policy,
    trace=self.trace,
    wait=self.retry_wait,
    circuit_breaker=self.sop_circuit_breaker,
)

當 circuit 已經是 Open,run_with_retry 會先寫入 circuit_breaker blocked 的 trace,然後丟出 CircuitOpenErrorHelpdeskWorkflow 接住後只回傳降級 SOP,並留下 sop_service_outage required。這個 required 目前只是可觀察的升級事件,還沒有真的發通知給誰;文章範圍內不假裝人工已收到告警。

Circuit breaker 和 retry 不是誰取代誰。retry 處理一兩次暫時性波動;circuit breaker 在看見重複失敗後先停止流量。這也是 Azure Circuit Breaker pattern區分兩者的理由:已知服務持續失敗時,快速失敗通常比繼續等待和重送更合理。

我怎麼讓它真的停下來

本機頁面新增了「服務熔斷」按鈕。為了讓畫面固定可重現,這個 scenario 把門檻暫時設成一次完整失敗、每輪最多兩次嘗試:

  1. 第一次查 SOP:兩次 timeout,retry_budget stopped,接著 circuit_breaker opened
  2. 第二次查同一個 SOP:直接出現 circuit_breaker blocked,沒有新的 search_it_sop 失敗事件。
  3. 兩次都只回傳降級內容,沒有建立 mock 工單。

image

這裡要看的是 trace,不只是最後那句回覆。模型說「我不做」沒有意義;第二次呼叫真的沒有進到 SOP 工具,才算停住。

我也把它放進 Promptfoo 的本機 security suite。這次新增的 case 會檢查回傳內容有 "stopped": true、trace 有 circuit_breakerblocked 狀態,並確認回覆明確說第二次「沒有再送出 SOP 請求」。它是 deterministic mock 測試,所以不需要 API key,也不依賴模型剛好聽話。

目前 suite 共六個案例:正常開單、越權工具、SOP 不可用、不得假裝成功、Token 預算擋工具,以及 SOP 熔斷後不再重試。

這個版本還不能拿去當分散式保護

這個 CircuitBreaker 存在 Python 記憶體裡。Web 版的真實 Agent 會在同一個 web process 共用它,因此同一個 process 裡的下一個請求會看到 Open 狀態;但如果未來開了多個 worker 或多台機器,每個 process 都有自己的計數器,它們不會互相知道。

所以這不是「已經解決 production resilience」的結論。真正部署時,熔斷狀態要放在能跨實例協調的位置,或交給既有的 service mesh、client SDK、gateway。門檻、冷卻時間和探測流量也要依 SOP 服務的容量與錯誤型態調整。

不過在這個小專案裡,至少有一件事變得可驗證:服務已知壞掉時,Agent 不會每收到一個新問題就再花一輪重試預算,然後把同一個錯誤放大。


上一篇
Day 7|Agent 不肯停下來:它能在幾分鐘內燒掉多少 Token?
系列文
從 LLM、Agent 到 Guardrails:30 天打造可控、安全、可驗證的 AI Agent8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言