繁中 ▾
取得 API 金鑰

無審查 API 錯誤碼與除錯手冊

報錯不可怕,可怕的是不知道該重試還是改程式碼。這份手冊依狀態碼逐一拆解:觸發條件、判斷依據、修正方法。錯誤回應體一律為 JSON,格式為 error 物件內含 code 與 message,因此除錯第一步永遠是讀取 body,而非只看狀態碼。後方附上帶有指數退避的重試程式碼、客戶端速率限制程式碼,以及一份可逐項勾選的清單。

更新於

重點

  • 只有 429 與 503 值得自動重試,其餘狀態碼重試只會白白浪費請求。
  • 402 no_credit 是餘額用完或試用過期,403 content_blocked 是內容被過濾,二者都不是網路問題。
  • 重試要帶指數退避加隨機抖動,並設定最大次數。
  • 每把 key 每分鐘 300 次請求,批量任務要在客戶端先進行速率限制。

錯誤回應體格式與讀取順序

所有失敗回應都是同一個結構:

{"error":{"code":"...","message":"..."}}

讀取順序固定三步:

  1. HTTP 狀態碼,決定大類;
  2. error.code,決定具體原因,程式中用它做分支;
  3. error.message,給人看,寫進日誌,不要用它做字串比對。

先想清楚一個分界:能靠重試解決的(429、503,以及網路超時)和必須修改才能解決的(其餘)。把兩類混在一起,是線上事故常見的根源,例如對 402 不停重試,結果每秒送出數十個無效請求。

日誌中建議固定記錄四個欄位:時間、狀態碼、error.code、本次請求的 prompt_tokens 估值。出問題時,一眼就能看出是某一類錯誤突然增多,還是整體失敗。再配一條告警規則:402 只要出現一次就立刻通知,因為它意味著服務已經對使用者不可用;429 則看比例,偶發無需處理,持續出現表示並行設計有問題。

狀態碼速查

狀態碼error.code含義重試?
400—請求不合法,如 prompt 加 max_tokens 超過 100k否
401—key 無效或缺失否
402no_credit餘額用盡或試用過期否
403content_blocked內容被攔截否
404—端點不存在否
429—觸發限流是,退避
503upstream_busy服務暫時繁忙是,幾秒後

表中「—」表示沒有需要你特別處理的固定 code 字串,按狀態碼分支即可。

4xx:重送請求,別重試

400 請求不合法

  • 成因一:JSON 格式錯誤,常見於手拼字串時引號沒跳脫。
  • 成因二:prompt 加 max_tokens 超過 100,000。長對話最容易觸發。
  • 成因三:請求體超過 8 MB。
  • 修正方式:用 JSON 庫序列化;發送前估算 token,超限時就裁剪歷史或調小 max_tokens;見長上下文實戰。

401 金鑰問題

  • Header 缺失,或缺了 Bearer 前綴。
  • 重新產生過 key,舊 key 立刻失效,而某台機器還在用舊值。
  • 環境變數沒帶進容器或定時任務。

402 no_credit

餘額已用完,或 7 天試用已過期。前往帳戶頁面儲值預付額度即可恢復。建議你自行監控服務餘額,別等使用者報錯才發現。

403 content_blocked

內容被攔截。合法的成人向內容、虛構與爭議話題不會被拒絕,但涉及未成年人的性內容一律攔截,包含小說與角色扮演。遇到 403,應檢查輸入與歷史紀錄中是否有此類內容,而非換個說法重試。

404 端點不存在

只有兩個端點:POST /v1/chat/completions 與 GET /v1/models。路徑漏了 /v1、多了斜線、拼錯,或是請求了不支援的 embeddings、圖片等介面,都會 404。

400 裡還有一類容易誤判:長對話的 token 隨輪次線性增長,白天測試一切正常,跑到幾十輪後突然開始 400。這不是介面不穩定,而是上下文視窗到頂了。解決辦法是給歷史設上限,超過閾值就丟棄最舊的輪次,或是把舊內容壓縮成摘要。別等報錯再處理,在發請求前就估算好。

排查 401 錯誤有個小技巧:將金鑰的前後各四位列印到日誌中,不要列印完整內容,再與帳戶頁面顯示的內容進行比對。這樣可以確認程式碼讀取到的究竟是不是你預期的那把金鑰,特別是在容器與定時任務環境中,環境變數遺失或讀取到舊值是最常見的原因。

429 與 503:該重試的兩類

429 限流

每把 key 每分鐘 300 次請求。批量任務、多執行個體共用一把 key、重試風暴,都會觸發。修法有兩層:客戶端先限速(見後面的程式碼),再對 429 做退避重試。

503 upstream_busy

服務暫時繁忙,幾秒後重試即可。別立刻連發,別在一秒內重試十次,那只會讓情況更糟。

網路超時

長文本生成耗時較長,timeout 不要設得太短,範例用 120 秒。超時後重試要注意:上一次請求可能已經在服务端執行,重複呼叫可能重複消耗額度,因此對生成類請求,超時重試次數要少,最好配合串流輸出縮短單次等待。

import os
import random
import time
import requests

URL = "https://api.wushenchaapi.com/v1/chat/completions"
HEADERS = {
    "Authorization": "Bearer " + os.environ["API_KEY"],
    "Content-Type": "application/json",
}
RETRYABLE = {429, 503}          # 只重试这两类
FATAL = {400, 401, 402, 403, 404}

class ApiError(Exception):
    def __init__(self, status, code, message):
        super().__init__(f"{status} {code}: {message}")
        self.status, self.code = status, code

def call(payload, max_retries=5, base=1.0, cap=30.0):
    for attempt in range(max_retries + 1):
        try:
            r = requests.post(URL, headers=HEADERS, json=payload, timeout=120)
        except (requests.ConnectionError, requests.Timeout):
            r = None                      # 网络层失败,按可重试处理
        if r is not None and r.ok:
            return r.json()
        if r is not None:
            try:
                err = r.json().get("error", {})
            except ValueError:
                err = {}
            if r.status_code in FATAL or r.status_code not in RETRYABLE:
                raise ApiError(r.status_code, err.get("code"), err.get("message"))
        if attempt == max_retries:
            raise ApiError(r.status_code if r is not None else 0, "retry_exhausted", "重试次数用尽")
        delay = min(cap, base * (2 ** attempt)) * random.uniform(0.5, 1.0)
        time.sleep(delay)

if __name__ == "__main__":
    out = call({"model": "uncensored", "max_tokens": 100,
                "messages": [{"role": "user", "content": "回复一个字:好"}]})
    print(out["choices"][0]["message"]["content"])

要點:退避公式是 min(cap, base × 2^n) × 隨機系數,隨機抖動避免多個客戶端同時重試撞在一起;FATAL 集合裡的狀態碼一次都不重試。

客戶端限速與診斷請求

與其等 429 再退避,不如事先限速。下面的小工具保證一分鐘內請求數低於設定值,多執行緒安全:

import threading
import time

class RateGate:
    """简单的客户端限速:保证一分钟内请求数不超过 limit。"""
    def __init__(self, limit=240):          # 留出余量,低于 300/分钟
        self.limit, self.stamps, self.lock = limit, [], threading.Lock()

    def wait(self):
        while True:
            with self.lock:
                now = time.time()
                self.stamps = [t for t in self.stamps if now - t < 60]
                if len(self.stamps) < self.limit:
                    self.stamps.append(now)
                    return
                sleep_for = 60 - (now - self.stamps[0])
            time.sleep(max(sleep_for, 0.05))

排查時,最乾淨的辦法是脫離業務程式碼,用 curl 發一個最小請求,-i 能同時看到狀態行與回應標頭:

curl -i https://api.wushenchaapi.com/v1/chat/completions \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"uncensored","max_tokens":20,"messages":[{"role":"user","content":"ping"}]}'

curl 能通而業務程式碼不通,問題在你的程式碼或環境(代理、環境變數、編碼);curl 也不通,看 key、餘額與網路。

為什麼速率限制值設為 240 而不是 300:在多實例部署時,各實例各自進行速率限制,加總後仍可能超過總額;再加上重試會額外佔用配額。預留兩成餘量是比較穩妥的做法。如果你有多個實例,可將每個實例的額度按總數平分,或使用共享計數器進行統一調度。

排障清單

  1. 讀 body 裡的 error.code,不要只看狀態碼。
  2. 401:key 前綴、環境變數、是否重新產生過。
  3. 400:JSON 是否合法,prompt + max_tokens 是否超 100,000,請求體是否超 8 MB。
  4. 402:餘額與試用有效期。
  5. 403:輸入與歷史紀錄裡是否含被禁內容。
  6. 404:請確認路徑是否為 /v1/chat/completions 或 /v1/models。
  7. 429:是否多執行個體共用一把 key,是否做了客戶端限速。
  8. 503:是否做了退避重試,間隔是否至少幾秒。
  9. 超時:timeout 是否足夠,是否可以改串流。
  10. 以上都排除:用 curl 最小請求復現。

剛接入的話,先看接入教程。更多參數在文件裡。

清單用法:出問題時從上往下逐項排除,不要跳著看。大多數「詭異」故障,最終都落在前四項裡。

補充一條經驗:把每次排障結論回寫到團隊文件,下次同樣的報錯就能直接對號入座。

常見問題

返回 503 要等多久再重試?

幾秒即可。建議第一次等一到兩秒,之後按指數增加,並加隨機抖動,設最大重試次數。

為什麼我的請求一直 402?

餘額已用完,或 7 天試用已過期。儲值預付額度後即可恢復,餘額不會過期。

403 content_blocked 能透過改寫繞過嗎?

不要嘗試。涉及未成年人的性內容在任何情況下都會被攔截,包含小說與角色扮演。正常的成人向內容不會觸發這個錯誤。

429 是按帳戶還是按 key 限制?

限制是每把 key 每分鐘 300 次請求。每個帳戶只有一把 key,所以多個服務共用時要共同限速。

只需填寫表單即可取得金鑰

建立帳戶,複製金鑰,修改 Base URL。設定就是這麼簡單。