エラー
失敗はすべて、JSON の本文と HTTP ステータスで返ります。キーなしで送ったリクエストへの応答はこうなります:
{
"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 | このキー自体のクレジット上限に達しました(アカウントにはまだクレジットがある可能性があります)。 | キーの上限を上げる、リセットを待つ、または別のキーを使ってください。 |
| 400 | insufficient_credits | request_exceeds_balance | 残高は $0 を超えていますが、実行中のリクエストが確保した分を除いた残りが、このリクエストの最大費用に足りません。最悪ケースは、入力を通常の入力料金で、max_tokens 分の出力(モデルの出力上限まで。max_tokens がない場合はその上限、最大 16,384 トークン)を加え、n を掛け、リクエスト単位や検索の料金を足したもので、モデルのどのチャネルでも請求されうる最も高い料金(長いコンテキストと推論の料金を含む)で計算します。何も実行されず、請求もありません。 | max_tokens を下げるか入力を短くする、またはクレジットをチャージしてください。 |
| 400 | content_policy_violation | content_filtered | モデルの実行前に安全チェックがリクエストを拒否しました。請求はありません。 | 入力を変えてください。同じ入力で再試行しても同じ結果になります。 |
| 401 | authentication_error | invalid_api_key | キーがない、不明なキー、または削除されたキーです。 | Authorization: Bearer sk-... ヘッダーを確認してください。 |
| 401 | authentication_error | key_expired | キーの有効期限が過ぎています。 | 新しいキーを作成してください。 |
| 403 | permission_error | model_not_allowed | キーは有効ですが、このモデルはアカウントで許可された範囲外です。 | GET /v1/models にあるモデルを使ってください。 |
| 403 | permission_error | forbidden | このキーではこの操作を実行できません。API キーの管理がその一例で、API キーではキーの作成・変更・ローテーション・削除はできません。 | キーはコンソールで管理してください。それ以外は適切な権限のキーを使ってください。 |
| 404 | not_found_error | not_found | 不明なパス、または存在しない動画 / ジョブ id です。 | URL と id を確認してください。 |
| 409 | idempotency_error | idempotency_key_reused | この Idempotency-Key は別の本文ですでに使われています。 | 新しいリクエストには新しいキーを使ってください。再試行を参照。 |
| 409 | idempotency_error | idempotency_key_in_progress | このキーを使った最初のリクエストがまだ実行中です。 | Retry-After 秒待ってから、同じリクエストを再試行してください。 |
| 429 | rate_limit_error | rate_limited | キーの 1 分あたりのリクエスト数またはトークン数の上限を超えました。 | 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 ヘッダーが付きます。エラーと一緒にログに残し、お問い合わせの際に記載してください。キーで拒否されたリクエスト(401)はモデルに届いていないため、この ID はありません。
成功したレスポンスには本文にも id があり、ストリームのすべてのチャンクが同じ id を持ちます。これがコンソールの Logs に、リクエストのステータス・レイテンシー・料金とともに表示される Request ID であり、GET /v1/usage/{request_id} で照会する id です。
再試行
エラーが届く前に、ゲートウェイはすでに再試行しており、モデルに別のチャネルがあればそちらへ切り替えています。再試行とフェイルオーバー で、ゲートウェイが処理することと利用者に任せることを説明しています。
再試行するのは次の場合だけです:429、501 を除くすべての 5xx、timeout エラー、ネットワーク障害(接続の切断、クライアント側のタイムアウト)。400 番台のすべてと 501 not_supported は、リクエストかアカウントが変わるまで同じように失敗します。
ジッター付きの指数バックオフを使い、約 1 秒から始めて上限を約 30 秒にします。レスポンスに 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")
料金がかかるリクエストの再試行
画像、動画、音声合成のリクエストは実行時に請求されます。ネットワークのタイムアウト後に再試行すると、本来は 2 回実行されてしまいます。POST /v1/images/generations、/v1/images/edits、/v1/videos、/v1/audio/speech では Idempotency-Key ヘッダー(255 文字以内の一意の文字列。例:UUID)を送ってください:
- 最初のリクエストは通常どおり実行され、そのレスポンスは 24 時間保持されます。
- 同じキーかつ同じ本文で再試行すると、そのレスポンスが
Idempotency-Replayed: trueヘッダー付きで返ります。何も実行されず、請求もありません。 - 同じキーで本文が異なる場合は
409 idempotency_key_reusedで拒否されます。 - 最初のリクエストがまだ実行中に再試行すると、
Retry-After付きの409 idempotency_key_in_progressが返ります。 5xxの応答は保持されないので、プロバイダー障害の後の再試行は再実行されます。
キーの範囲は API キーごとです。他のエンドポイントではこのヘッダーは無視され、上記のエンドポイントでもヘッダーのないリクエストはこれまでとまったく同じ動作です。8 MB を超えるレスポンスは保持されません。リクエストは実行されますが、リプレイが効かないだけです。
ストリーミング中のエラー
"stream": true のとき、最初のトークンより前に起きたエラーは、上記のステータスを持つ通常の JSON エラーレスポンスです。ストリーム開始後に起きたエラーは、同じ error オブジェクトを中身とする最後の data: チャンクとして届き、ストリームが終わります。
接続の切断、利用者側の切断、エラーによる中断などで早く終わったストリームは、モデルの通常料金で次の分が請求されます:
- プロンプトの全量
- その時点までにストリーミングされた出力。推論モデルがストリーミングした推論は、回答テキストがまだ届いていなくても出力として数えます。
ストリーム停止後にモデルが生成を続けた可能性のある出力は請求されません。出力が何も出る前に終わったストリームは一切請求されません。
残高切れ
残高が $0 以下のときは、有料モデルへのリクエストがすべて 400 insufficient_credits で拒否されます。何も実行されず、請求もありません。クレジットをチャージして再試行してください。モデル ID が :free で終わるモデルは、残高が $0 でも引き続き使えます。
残高は $0 を超えているがリクエストの最大費用に足りない場合は、そのリクエストだけが 400 request_exceeds_balance で拒否され、より小さなリクエストは実行されます。
メッセージには、そのリクエストの最大費用と残高が示されます。
max_tokens を下げるか入力を短くして残高内で実行するか、クレジットをチャージしてください。
OpenAI からの移行
エラー本文は OpenAI と同じ error オブジェクトです。
ステータスまたはコードが異なるのは次の 2 つです:
| ケース | 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 を記載してください。