博客

並非所有 429 都可重試:LLM API 的限流、退避與重試預算

429 可能意味著“稍微放慢一會兒”,也可能意味著“在有人介入之前你已經沒有配額”。在日誌裡,兩者看起來完全一樣,直到你檢查某一個響應頭;這個區別決定了重試是有幫助,還是隻是在燒時間。

並非所有 429 都可重試:LLM API 的限流、退避與重試預算

限流錯誤不是一種情況,而是兩種,它們在日誌裡看起來一模一樣,直到你去檢查某一個響應頭。一種是臨時限流:按指示等待,請求就會通過。另一種是配額或支出上限:重試(包括 SDK 的自動重試)會一直失敗,直到訪問恢復,所以花在退避上的功夫純屬浪費。這正是 LLM API 限流問題中僅憑狀態碼無法告訴你的部分,兩家主要廠商都在各自的限流頁面上記錄了這種區別,並給出了明確的應對指引。本指南會講清楚如何對429進行分類、如何正確使用Retry-After,構建一份不會失控的重試預算,並從架構層面理解限流為什麼會這樣表現。

LLM API 限流的判斷:可重試還是不可重試

首先要讀的是廠商自己的文件:OpenAI 的限流指南和Anthropic 的限流頁面都明確列出了哪些情況值得重試。

對於臨時限流,OpenAI 文件描述了429響應,其錯誤型別為rate_limit_error、錯誤碼為slow_down,意思是你的請求速率上升得太快;指引是:當Retry-After存在時遵循它,降低請求速率,然後再逐步提升。Anthropic 記錄的限流按每週期請求數和 token 數計量;超出後會返回一個429,說明超出的是哪一項限流,並附一個retry-after響應頭,指示需要等待多久。這兩種都屬於“等待後重試”的情況。

配額與支出上限則不同。Anthropic 指出,當用量達到組織的支出上限時,響應是一個429,其錯誤型別為rate_limit_error,與限流是同一型別,但沒有retry-after響應頭;並且重試(包括 SDK 的自動重試)會一直失敗,直到訪問恢復;在 Messages API 上,你可以通過檢查 error details 裡的 code 是否為強制的支出上限來區分這兩種情況。其文件還指出,提升 tier 可以恢復訪問,而這是人的決定,不是退避策略。OpenAI 的文件從另一個方向說得同樣明確:Retry-After可能會出現在429響應上,這類響應由臨時限流引起,並且它“並不意味著配額、計費或其他需要使用者操作的錯誤可以通過重試解決”。其硬性支出上限也會讓受影響的請求返回429,解決辦法是調整上限,而不是等更久。

由此得到本文圍繞的分類規則:一個429帶有Retry-After的響應是限流訊號:照它執行。一個429不帶Retry-After的響應,應當被視為“先檢查配額、計費和 tier”,而不是可以更用力重試的邀請。廠商也可能把同一個狀態碼用於其他錯誤,所以在選擇恢復動作前要先看錯誤正文。

正確解讀 429 的 Retry-After

當Retry-After存在時,要精確理解它的語義:它是一個最小等待時長,而不是目標值。OpenAI 的指引是至少等待這麼久,並在其上再加一點隨機延遲,避免多個客戶端在同一時刻一起重試。後半句很容易被忽略,而忽略的代價很高:一群 worker 都嚴格按響應頭等到秒,視窗一開就會集體衝上去。

當響應頭缺失或無效,或你在處理它覆蓋範圍之外的情況時,退回到帶抖動的指數退避:先短暫等待,然後在每次失敗後增加延遲,並用隨機化把重試分散開。同樣的邏輯也適用於臨時過載響應;OpenAI 文件描述了503響應,帶有service_unavailable_error、錯誤碼server_is_overloaded,指引是:當Retry-After存在時遵循它,然後重試;如果錯誤持續,就加大每次嘗試之間的延遲。

同一份文件裡還有一處細節:一個slow_down即使你的流量處在每分鐘請求數和每分鐘 token 數的限制之內也可能出現,因為它反映的是流量增長得多快,而不是你是否用盡了配額。文件給出的補救辦法是逐步抬升的斜坡,而不是階躍式變化。

帶抖動的指數退避不是可選項

把抖動當作“有則更好”的團隊,往往會重新發現它為什麼是標準做法。確定性的退避會讓客戶端同步:所有在同一時刻失敗的 worker 等待同樣的時長,並在同一時刻再次發出請求,把一個短暫的限流轉成自己製造的流量尖峰,讓限流一直處於觸發狀態。抖動讓重試彼此去相關。配合Retry-After,模式很簡單:等待指示的最小值再加一個隨機比例,只有在沒有任何指示時才使用指數增長。這兩部分都不稀奇,也不特定於某家廠商;它是任何同步 API 整合的最小可行重試策略。

重試預算:限制嘗試次數與總時長

能避免大多數重試風暴的指引既枯燥又具體:同時限制嘗試次數和重試的總時長。只有次數沒有截止時間,一連串等待會堆在一個使用者請求後面;只有截止時間沒有次數,一串快速失敗會反覆衝擊端點。兩者都要,而且都要由所有呼叫方共用的同一段程式碼來執行。

團隊違反自己預算最常見的方式是巢狀。如果你的應用管理重試,而底層的 SDK 也自動重試,實際嘗試次數是相乘而不是相加,總時長也會不可預測地拉長。OpenAI 的文件直接談到了這一點:如果你在應用裡管理重試,就停用 SDK 重試,或在你的限制裡把它們計算進去,以免巢狀的重試迴圈把請求數量翻倍。它還警告說,每個官方 SDK 都會按各自的設定重試符合條件的429和503響應,並且對Retry-After,尤其是長延遲場景,會因 SDK 版本和配置而異,所以要驗證你安裝版本的行為,而不要假設服務端給出的延遲會被完全尊重。

最後,要決定預算耗盡時會發生什麼,並讓它足夠顯眼:快速讓請求失敗,記錄分類結果(Retry-After是否存在),並在出現不可重試的429時告警,因為這類錯誤意味著有人需要動手。悄悄重試一個配額錯誤直到超時,就是這樣把五分鐘的故障變成五十分鐘。

為什麼令牌桶限流會拒絕平均速率合規的流量

一個常見的意外:你測到的平均速率看起來明顯低於上限,請求卻仍被拒絕。機制本身能解釋這件事。Anthropic 文件指出,其 API 使用令牌桶演算法,容量持續補充到最大值,而不是按固定間隔重置,所以即使當天平均量不大,一次突發也能耗光桶。因此,削平流量比壓低每日總量更有價值。一個在客戶端給請求定速的佇列,加上每個呼叫方的API 併發上限,對穩定性的幫助超過任何重試策略。

突發並不是問題的唯一形態。Anthropic 還記錄了加速限制:用量的急劇上升本身就能產生429錯誤,指引是逐步抬升流量並保持穩定的用量模式。實際理解是,容量規劃應包含你增長的速度,而不只是你傳送的量:把斜坡預熱,避免在部署時出現階躍變化,並讓流量模式足夠穩定,使看板上的尖峰不會變成拒絕率的尖峰。

分層限流:組織、workspace、project

兩套生態裡的限流都在不同層級執行,知道在哪一層會改變你要修的地方。Anthropic 在組織層執行服務配置的限流,並額外支援按 workspace 使用者自定義限流,這可以讓某個 workspace 的上限低於組織,以免單個負載把其他負載餓死。OpenAI 通過響應頭暴露 project 範圍的 token 限制,當 project 範圍的 token 限制生效時這些響應頭可能出現。Anthropic 的 Message Batches API 又加了一層:一份跨模型共享的獨立限流,加上對處理佇列中批處理請求的上限,請求在被成功處理之前一直計入。

架構上的含義是:在你自己的系統裡映象廠商的結構——按負載、按團隊或按產品面拆分 workspace 或 project,這樣失控的批處理任務只會拖累它自己的那一份,而不是共享池。這也是歸因重要的地方:當限流觸發時,第一個問題是“誰消耗了容量”,回答它所需的指標就是用量歸因指南。

按租戶設計這些層級,並把配額與成本歸因接到同一套結構上,是多租戶指南的主題。

把限流響應頭當作控制訊號

響應頭不只是診斷資訊,它們是即時的控制訊號。OpenAI 暴露了一組響應頭,分別針對請求和 token 覆蓋上限、剩餘量和重置時間,再加上前面提到的 project 範圍變體;重置值是時長風格的字串。一個能讀取剩餘數量和重置視窗的客戶端,可以在自己拿到429之前就先給自己限速:當剩餘容量很低或重置即將到來時,預先放慢佇列,而不是一頭撞上去。文件裡有兩點提醒:響應頭是否存在及其含義應比對廠商當前頁面確認,數值也不應硬編碼進客戶端。把它們當作自適應限流器的輸入,而不是寫死的常量。

本文的位置,以及清單

本文是錯誤碼指南所介紹的那片水域的深水區:那篇梳理錯誤碼,這篇處理429本身。要減少遇到它的頻率,快取指南講了如何從源頭削減重複工作,而聚合層討論講了什麼時候多廠商層是容量問題的正確架構答案。你所用服務的端點和協議細節都在它的文件裡。

清單彙總在一處:

  1. 只重試可重試的錯誤:臨時限流和過載,而不是配額、計費或任何需要人處理的錯誤。
  2. 如果Retry-After存在,至少等待這麼久,再加一點隨機延遲。
  3. 如果它缺失或無效,就使用帶抖動的指數退避。
  4. 在共享程式碼裡同時對嘗試次數和總時長設預算。
  5. 停用或把 SDK 的重試計算進去,避免迴圈巢狀。
  6. 在客戶端給流量定速:排隊、削峰,並對每個呼叫方的 API 併發上限設限。
  7. 按 workspace 或 project 映象限流,並做好用量歸因,讓限流決策有明確的負責人。
  8. 自適應地讀取限流響應頭,並對不可重試的429進行告警,而不是重試它們。

這些都不光鮮。它決定了整合在壓力下是優雅降級,還是把每一次容量波動都變成一次事故復盤。