錯誤
每次失敗都會回傳一個 JSON 本文和一個 HTTP 狀態碼。這是沒帶 Key 的請求收到的回應:
{
"error": {
"message": "Missing API key. Send it as: Authorization: Bearer <your OceanToken key>.",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}
請依 code 分支處理。它是一組固定且保持穩定的詞彙;message 是給人看的,措辭可能改變。type 把處理方式相同的錯誤碼歸為一類。HTTP 狀態碼表達的是同一件事,只是更粗略,所以依狀態碼分支的既有用戶端仍可正常運作。
錯誤碼
| HTTP | type | code | 發生了什麼 | 怎麼處理 |
|---|---|---|---|---|
| 400 | invalid_request_error | invalid_request | 請求格式有誤:缺少欄位、模型不接受的值、損壞的圖片。 | 修正請求。不要原樣重試。 |
| 400 | not_found_error | model_not_found | model 名稱不存在。 | 從 GET /v1/models 中選一個名稱。 |
| 400 | insufficient_credits | insufficient_credits | 可用額度已用完。沒有執行,也沒有扣款。 | 儲值後重試。 |
| 400 | insufficient_credits | key_budget_exceeded | 這個 Key 自己的額度上限已用完(帳號可能仍有餘額)。 | 提高這個 Key 的上限、等它重置,或改用其他 Key。 |
| 400 | insufficient_credits | request_exceeds_balance | 餘額大於 $0,但扣掉進行中請求預留的部分後,剩下的不足以支付這個請求最多可能的費用。最壞情況的算法:輸入按完整輸入價,加上 max_tokens 的輸出(不超過模型的輸出上限;沒有 max_tokens 時取該上限,最多 16,384 個 token),乘以 n,再加上任何按次或搜尋費用,並以該模型所有通道中可能計費的最高價格計算,包含長上下文和推理價格。沒有執行,也沒有扣款。 | 降低 max_tokens 或縮短輸入,或儲值。 |
| 400 | content_policy_violation | content_filtered | 模型執行前,安全檢查拒絕了請求。不收費。 | 修改輸入。同樣的輸入重試會得到同樣的結果。 |
| 401 | authentication_error | invalid_api_key | 沒有 Key、未知的 Key 或已刪除的 Key。 | 檢查 Authorization: Bearer sk-... 標頭。 |
| 401 | authentication_error | key_expired | Key 已過期。 | 建立新的 Key。 |
| 403 | permission_error | model_not_allowed | Key 有效,但這個模型不在帳號允許的範圍內。 | 改用 GET /v1/models 中的模型。 |
| 403 | permission_error | forbidden | 這個 Key 不能執行此操作。管理 API Key 就是一例:API Key 不能建立、修改、輪替或刪除 Key。 | 在主控台管理 Key;其他情況請改用權限合適的 Key。 |
| 404 | not_found_error | not_found | 未知的路徑,或不存在的影片 / 任務 id。 | 檢查 URL 和 id。 |
| 409 | idempotency_error | idempotency_key_reused | 這個 Idempotency-Key 已用於另一個不同的請求本文。 | 新請求請用新的 Key。見重試。 |
| 409 | idempotency_error | idempotency_key_in_progress | 使用這個 Key 的第一個請求仍在執行中。 | 等待 Retry-After 秒後,重試同一個請求。 |
| 429 | rate_limit_error | rate_limited | 超過了這個 Key 每分鐘請求數或 token 數的上限。 | 等到 Retry-After 後再試;見限制。 |
| 429 | rate_limit_error | provider_overloaded | 這個模型的所有通道目前都滿載。見重試與故障轉移。 | 退避後重試,或換模型。 |
| 501 | invalid_request_error | not_supported | 不提供此端點:批次(/v1/batches)、微調(/v1/fine_tuning),以及在 /v1/files 下列出、讀取或刪除檔案。 | 不要重試。改把每個請求送到對應的一般端點。 |
| 503 | api_error | temporarily_unavailable | 閘道剛才無法查核接受請求所需的資訊,例如組織的餘額。沒有執行,也沒有扣款。 | 等 Retry-After 秒後重試。 |
| 5xx | api_error | provider_error | 閘道自行重試和故障轉移後,模型仍然失敗。不收費。 | 退避後重試;若持續發生,請換模型。 |
| 408 | api_error | timeout | 串流開始後 120 秒內沒有收到模型的任何內容。 | 退避後重試。 |
| 504 | api_error | timeout | 請求超過了 240 秒的時限。 | 改用串流、縮短請求,或使用非同步端點。 |
不在表中的錯誤都是 api_error。之後可能新增錯誤碼;既有的錯誤碼不會改名或移除。
請求 ID
模型請求的回應帶有標頭 x-oceantoken-request-id。請把它和錯誤一起記錄,聯絡我們時附上。因 Key 被拒(401)的請求沒有到達模型,因此沒有這個 ID。
成功的回應在本文中也有 id,串流的每個區塊都帶有相同的 id。它就是主控台 Logs 中顯示的 Request ID(附請求的狀態、延遲和費用),也是 GET /v1/usage/{request_id} 用來查詢的 id。
重試
錯誤傳到你這裡之前,閘道已經重試過;如果模型還有其他通道,也已把請求轉過去。重試與故障轉移 說明它處理了什麼、留給你處理什麼。
只在以下情況重試:429;除 501 以外的任何 5xx;timeout 錯誤;網路失敗(連線中斷、用戶端逾時)。所有 400 段的錯誤和 501 not_supported,在請求或帳號改變之前都會以同樣方式再次失敗。
使用帶隨機抖動的指數退避,從約一秒開始,上限約三十秒;回應帶有 Retry-After(秒數)時,至少等待那麼久。
Retry-After 只在 409 idempotency_key_in_progress 上保證出現;429 或 503 可能沒有,此時請用你自己的退避策略。
# Retry only what can succeed on a second try: 429, 408 (timeout), every 5xx
# except 501 (an endpoint that is not offered) and network failures,
# with exponential backoff and jitter, honouring Retry-After when it is sent.
import os
import random
import time
from openai import APIConnectionError, APIStatusError, OpenAI
client = OpenAI(
base_url=os.getenv("OCEANTOKEN_BASE_URL", "https://api.oceantoken.ai/v1"),
api_key=os.environ["OCEANTOKEN_API_KEY"],
max_retries=0, # the loop below does the retrying
)
def with_retries(call, attempts=5):
for attempt in range(attempts):
try:
return call()
except APIConnectionError:
if attempt == attempts - 1:
raise
delay = None # network failure: back off and try again
except APIStatusError as e:
retryable = e.status_code in (408, 429) or (e.status_code >= 500 and e.status_code != 501)
if not retryable or attempt == attempts - 1:
raise
retry_after = e.response.headers.get("retry-after")
delay = float(retry_after) if retry_after else None
time.sleep(delay if delay is not None else min(30, 2**attempt) + random.random())
models = with_retries(lambda: client.models.list())
print(len(models.data), "models")
重試會花錢的請求
圖片、影片和語音合成請求在執行時就會扣款。網路逾時後重試,原本會讓它們再執行一次。請在 POST /v1/images/generations、/v1/images/edits、/v1/videos 和 /v1/audio/speech 上傳送 Idempotency-Key 標頭(任何唯一字串,最多 255 個字元,例如 UUID):
- 第一個請求正常執行,其回應保留 24 小時。
- 用相同 Key 且相同本文重試,會拿回那個回應,並帶有標頭
Idempotency-Replayed: true。不會執行,也不會扣款。 - 相同 Key 但本文不同,會被拒絕並回傳
409 idempotency_key_reused。 - 第一個請求仍在執行時重試,會得到帶
Retry-After的409 idempotency_key_in_progress。 5xx回應不會保留,所以供應端失敗後的重試會重新執行。
Key 的範圍限於你的 API Key。其他端點會忽略這個標頭;在上述端點上,不帶它的請求行為和以前完全一樣。超過 8 MB 的回應不會保留;請求照常執行,只是不適用重放。
串流中的錯誤
使用 "stream": true 時,在第一個 token 之前發生的錯誤,是一般的 JSON 錯誤回應,狀態碼如上。串流開始後發生的錯誤,會以最後一個 data: 區塊送達,內容是同樣的 error 物件,然後串流結束。
提前結束的串流(連線中斷、你主動斷線,或被錯誤中斷),按模型的一般價格計費:
- 提示全部計費;
- 到那時為止已串流的輸出。推理模型串流出的推理內容算作輸出,即使還沒收到任何回答文字。
串流停止後模型可能繼續生成的輸出不計費。還沒產生任何輸出就結束的串流完全不計費。
餘額用盡
餘額小於或等於 $0 時,所有付費模型的請求都會被拒絕並回傳 400 insufficient_credits。不會執行,也不會扣款。儲值後重試。模型 ID 以 :free 結尾的模型在餘額為 $0 時仍可呼叫。
餘額大於 $0 但少於某個請求最多可能的費用時,只有那個請求會被拒絕並回傳 400 request_exceeds_balance;較小的請求仍會執行。
錯誤訊息會說明這個請求最多可能花多少,以及你的餘額是多少。
降低 max_tokens 或縮短輸入,讓它在餘額內執行,或者儲值。
從 OpenAI 遷移
錯誤本文的 error 物件與 OpenAI 相同。
有兩種情況的狀態碼或錯誤碼不同:
| 情況 | OpenAI | OceanToken |
|---|---|---|
| 餘額用完 | 429 insufficient_quota | 400 insufficient_credits |
| 未知模型 | 404 model_not_found | 400 model_not_found |
code 的值全部是我們自己的,見錯誤碼。
有幾個狀態碼帶有我們自己的含義:模型滿載是 429 provider_overloaded(OpenAI 回 503),408 timeout 是串流沒了動靜,409 是冪等衝突,501 not_supported 是我們不提供的端點。
如果你的程式把 429 insufficient_quota 當作沒錢了,請改看 400 insufficient_credits;它不是 429,所以重試迴圈不會在它上面空轉。
未知模型是 400,所以等待 404 的處理器看不到它;依 code 分支,兩邊都適用。
內容政策
觸發安全檢查的請求會被拒絕並回傳 400 content_filtered。檢查在呼叫模型前進行,所以不收費。
找到失敗的請求
每次失敗都會記錄在主控台的 Logs 中,附狀態碼和回應本文。依時間和模型找到它,聯絡我們時附上它的 x-oceantoken-request-id。