開発者

エラーハンドリング

SchneeAI Gateway は OpenAI 互換のエラーオブジェクトを SchneeAI 独自拡張 (予算、PII、ルーティング) 付きで返します。ここではパース方法、リトライすべきタイミング、リトライを安全にする方法を説明します。

エラーレスポンス形状

2xx 以外のすべてのレスポンスは同じエンベロープを持ちます:

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 2 seconds.",
    "param": null,
    "request_id": "req_01HQ..."
  }
}
フィールド役割
typeOpenAI 互換カテゴリ (invalid_request_error, authentication_error, rate_limit_error, api_error)
code機械処理可能なサブコード (SchneeAI 独自コード 参照)
message人間が読める説明
param該当パラメータ (該当する場合)
request_idサポートへ問い合わせる際に含めてください — 実際の呼び出しを追跡できます

HTTP ステータス → アクション

ステータス意味リトライアクション
400JSON 不正または必須フィールド欠落不可リクエストボディを修正
401API キーが無効または欠落不可Authorization ヘッダーを確認
403テナントスコープまたはロールが不許可不可トークンのテナント / ロールを確認
404model またはプロンプト名が存在しない不可モデルディレクトリ を確認
422JSON は有効だが意味的に拒否不可error.code を読む — コンテキスト長、未対応パラメータ、コンテンツポリシー等
429レートリミットまたは予算超過可 (バックオフ付き)Retry-After があれば従う
500Gateway 内部エラー指数バックオフ
502上流プロバイダーエラー指数バックオフ
503Gateway 一時利用不可指数バックオフ
504上流プロバイダータイムアウト指数バックオフ

SchneeAI 独自エラーコード

OpenAI 互換エンベロープは error.code に SchneeAI 独自のサブコードを運びます。実際に遭遇するのは以下のとおり:

コードHTTP意味
budget_exceeded429テナントまたはフィーチャー予算を使い切った。予算がリセットされる (ポリシーにより翌時間 / 翌日 / 翌月) までリトライしないこと。
pii_detected422テナントの PII ポリシーが block で、スキャナがクリティカル分類を検出した。入力を編集するか、ポリシーを mask / flag に切り替える。
context_length_exceeded422プロンプト + max_tokens の合計がモデルのコンテキストウィンドウを超えた。切り詰めるか、より長いコンテキストモデルに切り替える。
unsupported_parameter422選択したプロバイダーが送信したパラメータに対応していない (例: 推論系モデルでの temperature)。
provider_error502上流 LLM プロバイダーがエラーを返した。バックオフ付きでリトライ。

リトライ戦略

429, 500, 502, 503, 504 のみリトライ。ジッタ付き指数バックオフを使います:

wait = min(base * 2^attempt, max_wait) + random_jitter
  base      = 1 秒
  max_wait  = 60 秒
  jitter    = wait の 0–25%
  attempts  = 最大 5 回

Retry-After ヘッダーがあれば従う。429 レスポンスはこれを秒数の整数で含みます。

リトライすべきでない場合

  • 400 / 401 / 403 / 404 / 422 — リクエスト自体が不正で、リトライしても同じエラーになる。
  • 429 + code: budget_exceeded — リトライすると予算を速く消費する。リセット窗口まで待つ。

冪等性

POST /v1/chat/completions では Idempotency-Key ヘッダーを送ることで、リトライ時の二重課金・二重集計を防げます:

curl https://api.schneeai.com/v1/chat/completions \
  -H "Authorization: Bearer $SCHNEEAI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "model": "auto", "messages": [...] }'
  • 形式: 255 文字以内の任意の文字列。UUIDv4 推奨。
  • TTL: 24 時間。以降は同じキーでも新規リクエストとして扱われる。
  • スコープ: サービス + テナント単位。異なるテナントが同じキーを使っても衝突しない。
  • 重複排除の対象: 課金利用記録。元のリクエストがプロバイダーに届かなかった場合、LLM 呼び出し自体は発生する。

クライアント実装例

Python (requests + tenacity)

import uuid
import requests
from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type

class RetryableError(Exception): pass

@retry(stop=stop_after_attempt(5),
       wait=wait_exponential_jitter(initial=1, max=60, jitter=2),
       retry=retry_if_exception_type(RetryableError))
def chat(messages, model="auto"):
    res = requests.post(
        "https://api.schneeai.com/v1/chat/completions",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Idempotency-Key": str(uuid.uuid4()),
            "Content-Type": "application/json",
        },
        json={"model": model, "messages": messages},
        timeout=60,
    )
    if res.status_code in {429, 500, 502, 503, 504}:
        raise RetryableError(res.text)
    res.raise_for_status()
    return res.json()

TypeScript (fetch)

async function chat(messages: Msg[], model = "auto") {
  const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch("https://api.schneeai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${API_KEY}`,
        "Idempotency-Key": crypto.randomUUID(),
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model, messages }),
    });
    if (res.ok) return res.json();
    if (![429, 500, 502, 503, 504].includes(res.status)) {
      const body = await res.json();
      throw new Error(`schneeai ${res.status}: ${body.error?.code}`);
    }
    const retryAfter = Number(res.headers.get("Retry-After")) || 0;
    const wait = retryAfter || Math.min(2 ** attempt + Math.random(), 60);
    await sleep(wait * 1000);
  }
  throw new Error("schneeai: max retries exceeded");
}

Go (net/http + backoff)

var buf bytes.Buffer
json.NewEncoder(&buf).Encode(map[string]any{
    "model":    "auto",
    "messages": messages,
})
req, _ := http.NewRequest("POST", "https://api.schneeai.com/v1/chat/completions", &buf)
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Idempotency-Key", uuid.NewString())
req.Header.Set("Content-Type", "application/json")

bo := backoff.NewExponentialBackOff()
bo.MaxElapsedTime = 60 * time.Second
backoff.Retry(func() error {
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return err }
    defer resp.Body.Close()
    switch {
    case resp.StatusCode >= 500, resp.StatusCode == 429:
        return errors.New("retry: " + resp.Status)
    case resp.StatusCode >= 400:
        return backoff.Permanent(parseError(resp))
    }
    return json.NewDecoder(resp.Body).Decode(&result)
}, bo)

サポート

正しくリトライしてもエラーが続く場合、エラーレスポンスの request_id を添えて [email protected] まで。実際の呼び出しを数分で追跡できます。


注意: SchneeAI はデザインパートナー段階です。上記エラーエンベロープは安定した OpenAI 互換仕様です。予算 / PII のサブコードは現在出荷中のプラットフォーム機能に対応しています。ここに記載のないコードに遭遇した場合は、request_id をお送りください — ドキュメントに追加します。