iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Claude AI

Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記系列 第 21

# Day 21|AI 掛掉怎麼辦:容錯設計與退回基本版

  • 分享至 

  • xImage
  •  

一個不方便的事實

LLM API 會掛。逾時、限流、5xx、回傳格式跑掉——全部都會發生,而且往往挑在你睡覺的時候。如果 Day 20 那個每日摘要功能直接依賴 AI 呼叫成功,那麼 AI 供應商打個噴嚏,你的訂戶那天就收不到內容。

對免費功能,這也許能忍。對付費訂閱的核心功能,「今天沒有內容」等於直接違約——訂戶付錢買的就是每天那份摘要。

設計原則:AI 是增強,不是依賴

解法的核心觀念:把功能拆成「基本版」和「AI 增強版」兩層,AI 掛掉時退回基本版,而不是整個開天窗。

以每日摘要為例:AI 版是把資料寫成流暢的敘述;基本版則是純程式產生的結構化數據呈現——表格、數字、固定格式的條列。沒有 AI 的文采,但該有的資訊一項不少。訂戶收到基本版的感受是「今天的摘要比較乾」,收到空白的感受是「這服務壞了」——這兩者對信任的傷害完全不是一個量級。

實作的關鍵形狀:回傳值,不拋例外

容錯的實作有一個講究的細節。AI 呼叫的包裝函式,失敗時回傳的是可用的字串,而不是拋出例外

def _call_llm_once(prompt: str, fallback: str) -> str:
    """呼叫 LLM 生成內容。任何失敗(逾時/限流/格式錯誤)回傳 fallback,
    絕不拋例外——呼叫端永遠拿到可直接使用的字串。"""
    try:
        resp = llm_client.generate(prompt, timeout=TIMEOUT)
        if not _looks_valid(resp):        # 格式檢查:AI 回了但回得不對
            return fallback
        return resp.text
    except Exception:
        log.exception("llm call failed, using fallback")
        return fallback

為什麼堅持「回字串不拋例外」?因為這個函式的呼叫端是排程流程——一條要服務全體訂戶的生產線。例外會讓整條線停下來,而「拿到一個保底字串」讓生產線無感地繼續走。把失敗處理封裝在最靠近失敗源的地方,讓上游永遠面對一個「不會失敗」的介面,整條流程的複雜度會斷崖式下降:呼叫端不需要 try/except、不需要判斷降級、不需要知道 AI 今天心情如何。

注意 _looks_valid 那一行:「AI 回了,但回的東西不對」(格式跑掉、內容截斷、答非所問)跟「AI 沒回」同樣是失敗。這是實戰裡學到的——LLM 的失敗模式裡,「回傳垃圾」比「回傳錯誤」更常見也更危險,因為後者你的 except 接得住,前者會直接送到使用者眼前。

測試怎麼寫

容錯路徑的測試原則:fallback 路徑要當成正式功能來測,不是當成邊緣案例。

  • mock AI 呼叫拋逾時 → 驗證訂戶依然收到內容、內容是合法的基本版格式
  • mock AI 回傳格式錯誤的垃圾 → 驗證垃圾沒有流到使用者面前
  • 驗證 fallback 內容本身的正確性——基本版不是「隨便給點東西」,它的數據必須跟 AI 版同源同值

最後一點常被忽略:降級版本也是版本,它的品質決定了事故當天使用者對你的評價。

通則

依賴任何外部智慧服務的功能,設計時先回答三題:它掛了,使用者看到什麼?它回了垃圾,攔得住嗎?降級版本自己有沒有被測試? 三題都有答案,才算把 AI 當成「增強」;答不出來,你做的是「依賴」——而依賴的東西,終將在最壞的時刻辜負你。


上一篇
# Day 20|AI 內容生成:一次呼叫服務全體訂戶的鐵律
下一篇
# Day 22|用 Claude Code 實作容錯模式的實際過程
系列文
Claude Code陪跑:一人開發者的訂閱制SaaS架構與十大地雷實戰記30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言