LLM API 錯誤碼是一套很小的詞彙表,而讀懂它們與據此行動之間隔著一道大鴻溝。造成這道鴻溝的有兩件事:同一個狀態碼在不同提供商那裡可能意味著不同的東西;還有一類錯誤,無論什麼策略都不值得重試。可行的做法是按層分診:認證,許可權與賬號,端點與路徑,容量與速率,然後再判斷這個錯誤到底是否可重試。如果你還沒有對端點成功完成過一次最小呼叫,先去做那件事,我們快速上手裡的做法適用,因為每一張分診表都假設你已經有一條可用的基線。
按層分診:API 錯誤 401 與 403、404 及其餘
按層而不是按數字來分類,會讓你保持誠實,因為即便數字重複,修法也隨層而異。下表覆蓋你最早會遇到的這些錯誤碼;每一條都是官方解讀,依據 OpenAI 的錯誤碼指南與 Anthropic 的錯誤參考。
| Code | Layer | First thing to check |
|---|---|---|
| 400 | Request | Malformed request or unsupported parameter value; OpenAI also returns it for a disallowed service tier, Anthropic as invalid_request_error |
| 401 | Authentication | The key itself: OpenAI distinguishes invalid authentication from an incorrect key, and Anthropic's authentication_error covers malformed, revoked, or expired keys |
| 402 | Account | Anthropic only: billing_error means the billing or payment information needs attention |
| 403 | Permission and region | OpenAI's known 403 is a country, region, or territory not supported; Anthropic's permission_error means the key lacks access to the resource, so check organization and workspace settings |
| 404 | Path | The endpoint path or resource identifier in the URL does not exist, per Anthropic's not_found_error |
| 409 | State | Anthropic's conflict_error: the request conflicts with the resource's current state, such as a concurrent modification; resolve, then retry |
有一個模式值得早點注意:在 OpenAI 相容配置中,401 和 404 往往是配置問題而不是程式碼問題——金鑰被截斷,或者 base URL 丟了路徑。這種失效模式在提供商切換指南裡有單獨的實戰講解,包括陳舊取值可能藏身的環境變數與設定介面。
容量層:OpenAI 的 429 限流子型別、503,以及 Anthropic 的 529 overloaded
這裡錯誤詞彙才真正顯出廠商特色,因為同一個 429 狀態承載著好幾種不同情形。OpenAI 的文件把它拆成有名字的幾種:rate limit reached for requests,告訴你要給請求限速,並在存在 Retry-After 頭時遵循它;slow_down,即使你的流量還在名義限額內也可能出現,要求你遵循 Retry-After 頭並更緩慢地爬坡;以及 credit_balance_exhausted,這根本不是限速問題,因為組織沒有預付額度,必須充值。還有一個相關情形 organization_spend_limit_exceeded,在觸及強制消費上限時觸發。
Anthropic 的 429 拆法不同,其中一種變體值得單獨點出:層級消費上限觸發的 429 不帶 retry-after 頭,並且會持續失敗直到訪問恢復,所以一個天真的重試迴圈會對著它無限打轉。該提供商還指出,Claude Code 工作區的消費上限可能以 429 出現,而相關情形則以 400 出現;用量陡增可能觸發加速限制的 429——這本身就說明應當有意識地給流量爬坡。對長時間執行的請求,超時情形也有自己的歸處,即 504 timeout_error。
服務端容量錯誤大多看起來一樣,但要仔細讀:OpenAI 的 503 以 service_unavailable_error 出現,code 為 server_is_overloaded,意味所請求的模型暫時過載,指引是存在 Retry-After 時遵循它,缺失時加大延遲。500 是通用伺服器錯誤。Anthropic 的 500 是 api_error,用指數退避重試,若持續存在,該提供商要求你在聯絡支援時提供 request ID。然後是 529 overloaded_error,一個標準集合之外的狀態碼,表示 API 對全體使用者暫時過載。非標準狀態碼恰恰是那些憑記憶寫出的錯誤對映器會漏掉的;處理器必須顯式點名它,否則它就落進 catch-all。
當容量成為最常失守的那一層時,戰略問題變成重試該落在哪裡:同一端點,還是備選端點。跨端點、跨提供商路由的架構取捨見我們的聚合與直連對比。
重試還是停止:決策表
在 API 錯誤處理中最有用的單一區分,是“稍後可能成功的錯誤”與“除非請求之外有東西改變、否則不會成功的錯誤”之間的差別。按兩家提供商自己的指引,劃分如下。
| Situation | Retry? |
|---|---|
| 429 with a Retry-After header | Yes, honoring the header |
| 503 or 529 (overload) | Yes, with backoff, honoring Retry-After when present |
| 500-class server errors | Yes, bounded, with backoff |
| Connection errors before a response | Yes, standard practice |
| Any 401 or 403 | No. Fix the key or the permission first |
| 404 and 409 | No. Fix the path or resolve the state conflict first |
| Billing and quota conditions, including credit_balance_exhausted and Anthropic's 402 and tier spend-cap 429 | No. The official line is blunt: retrying billing, spend, or quota errors will not restore access, and the spend-cap 429 keeps failing by design until access resumes |
無論哪家廠商,三條原則能讓重試邏輯保持誠實。第一,帶抖動的指數退避,讓爆發不會同步。第二,API 響應帶有 Retry-After 頭時始終遵循它;把缺失該頭當作需要更多耐心的理由,而不是更少。第三,只重試可安全重複的操作;對有副作用的操作,先讓它冪等,再讓它進入重試層。還要了解你的 SDK 已經做了什麼:兩大 SDK 都對一小部分錯誤內建了自動重試,帶指數退避和可配置上限——這很有幫助,直到它悄悄把你並不知道正在發生的重試風暴翻了一倍。請弄清這個設定。
同一個問題,不同廠商給出不同錯誤碼
按層而不是按數字分支,最實際的理由是業務層面的問題在不同提供商那裡對映到不同錯誤碼。賬戶餘額耗盡,在 Anthropic 是 402 billing_error,在 OpenAI 是 credit_balance_exhausted 429。許可權問題在 Anthropic 是 403 permission_error,而 OpenAI 那個獨立的 403 是地理原因。速率壓力在兩家都可能是 429,但子型別不同,而 Anthropic 的一種 429 變體(消費上限)的行為與 OpenAI 限流器產出的任何東西都不一樣,因為它根本不是限速訊號。
這對你的程式碼有具體後果:維護一張自己的對映表,一層一行,寫明落進該層的各廠商錯誤碼,並按層路由處理。新增一家提供商或一個相容端點時,你擴充套件這張表,而不是重寫分支。把錯誤碼當作方言,把層當作語法。
一個最小的錯誤處理骨架
把這件事處理好,不需要精巧的框架。你只需要每層一個處理器,並給出明確的處置方式:
- 認證與許可權(401、403):不重試;快速失敗,丟擲“檢查金鑰或許可權”,並記錄端點與憑據來源,讓修法一目瞭然。
- 賬號與計費(402、餘額與消費上限情形):不重試;告警給運維人員,因為這一類要等人來動手。
- 路徑與狀態(404、409):不重試;記錄確切的 URL 與資源 ID,因為這一層幾乎總是配置或併發 bug,本不該被重試成噪音。
- 容量(帶 Retry-After 的 429、503、529、500 類):按上面三條原則重試;如果重試預算耗盡,降級到備用模型或端點,而不是把請求丟在地上。
- 未知:記錄並告警,絕不靜默吞掉。通用處理器應把無法識別的狀態碼(包括非標準狀態碼)當作獨立一類。
有兩個日誌細節在每次事故復盤中都能回本。記錄錯誤的結構化 type 與 code,而不只是狀態行,因為兩家提供商都在數字之上疊加了含義:OpenAI 要求你針對計費與限流情形檢視 error.code,Anthropic 則指出 error.type 的取值可能隨時間增加,這也是“真理由你的對映表而非記憶承載”的又一個理由。同時記錄提供商返回的 request ID,連同端點與模型,因為它是把“某個東西失敗了”變成具體時間線的唯一抓手。至於相鄰的那一類——請求成功但響應形狀不對,streaming 事件與工具呼叫是常見嫌疑——那一片在本文末尾有自己的清單;端點上下文本身(base URL、bearer 認證、模型列表)寫在平臺自己的文件。
接下來做什麼
- 配置層的一個完整例項。實踐中大多數 401 和 404 來自憑據與端點配置面, Cursor 自定義端點配置端到端走完一個真實案例,包含它自己的分診表。
- 隔壁的形狀層。當狀態碼沒問題而響應行為異常時,相容性檢查清單把這個面拆成可測試的若干塊。
讀錯誤碼、說出它屬於哪一層、判斷重試有沒有可能幫上忙,然後才寫那個分支。