OpenAIクライアント移行は、2つの設定変更だけで完了したように見えることがあります。APIキーを置き換え、SDKの向き先を新しいベースURLに変えるだけです。最初のリクエストは成功し、レスポンスの形も見慣れたもので、プルリクエストはそのままマージできそうに見えます。
それはインターフェース互換性があることを証明するだけです。本番動作が維持されることを証明するものではありません。
OpenAIクライアント移行でより難しいのは、トラフィックが偏ったときに何が起こるかを維持することです。リクエストがバーストで到着する、プロンプトが大きくなる、ストリームが想定より長く続く、プロバイダが429を返す、または処理がすでに始まっている可能性があるのにレスポンスがタイムアウトする――こうした状況です。SDK、アプリケーション、ジョブキューがそれぞれ独立に再試行すると、1回の失敗がほぼ同時の複数回の試行に変わり得ます。
このチュートリアルでは、既存のOpenAI風のPythonまたはTypeScript統合を、レート制限と再試行動作を明示しながら統合ゲートウェイへ移行する方法を示します。例ではFlatkeyのOpenAI互換ベースURLを使用しますが、レビュー手法はどのゲートウェイ移行にも適用できます。
クイックアンサー: 何を変更すべきか?
安全なOpenAIクライアント移行のためには、ベースURLを変更のすべてとみなすのではなく、これらの設定をまとめて確認してください。
| 移行対象 | 確認すべき内容 | 安全な開始時の判断 |
|---|---|---|
| APIエンドポイント | ベースURLと認証 | コード中に散在するリテラルではなく、環境変数経由で変更する |
| モデル選択 | 正確なモデル識別子とサポートされるパラメータ | カナリア用に既知のモデルを1つ固定する |
| SDKの再試行 | 自動再試行回数と再試行対象のステータスコード | 再試行をSDKに持たせるか、アプリケーションに持たせるかを決める |
| アプリケーションの再試行 | バックオフ、ジッター、試行回数上限、再試行予算 | 再試行の責任者を1つにし、すべての試行をログに記録する |
| RPM制御 | リクエスト到着率とバーストサイズ | 切り替え前に同時実行数またはキューの上限を追加する |
| TPM制御 | プロンプトと予想出力トークン数 | 1行のスモークテストだけでなく、現実的な大きなプロンプトをテストする |
| タイムアウト | 接続、読み取り、合計リクエスト時間 | 同期呼び出しとストリーミング呼び出しに明示的な値を設定する |
| 可観測性 | リクエストID、試行回数、トークン、レイテンシ、最終結果 | クライアントのログとゲートウェイの使用ログを比較する |
まず略語レベルの説明が必要なら、LLM rate limits explained: RPM, TPM, and retriesを読んでください。このガイドは、その解説の続き、つまり移行差分と本番テスト計画の段階から始めます。
ベースURLの差し替えは必要だが、それだけでは不十分な理由
Flatkeyのクイックスタートでは、最小限のクライアント変更が記載されています。OpenAI SDKのリクエストパターンは維持し、ベースURLをhttps://router.flatkey.ai/v1に設定します。また、リクエスト後にUsage Logsを確認し、モデル、トークン数、レイテンシ、コストを検証することも推奨しています。
それは正しいスモークテストです。本番のOpenAIクライアント移行では、さらに4つの質問が必要です:
- SDK は
429、タイムアウト、またはサーバーエラーを自動的に再試行しますか? - 別の層も同じ失敗した操作を再試行しますか?
- 同時実行数はリクエストレート、トークンレート、またはその両方で制限されていますか?
- 1 つの論理操作と、その個々の試行を区別できますか?
公式の OpenAI Python および Node SDK のドキュメントでは、現在、429 応答、接続エラー、タイムアウト、いくつかのサーバーエラーを含む特定の失敗は、デフォルトで 2 回再試行されると記載されています。どちらの SDK も再試行とタイムアウトの設定を公開しています。このデフォルトは直接統合する場合には便利ですが、自分のコードですでにバックオフを実装していると、見えない増幅になることがあります。
移行の目的は「すべての再試行を無効化する」ことではありません。目的は「どの層が再試行を担当するのかを把握する」ことです。
Step 1: コードを変更する前に、すべての再試行レイヤーを棚卸しする
まず、実際の呼び出し経路を描き出します。
user action or job
-> application retry wrapper
-> queue delivery retry
-> OpenAI SDK retry
-> gateway
-> provider
各レイヤーごとに、次を記録します。
- どのエラーで再試行が発生するか。
- 最大試行回数。
- 遅延が固定スリープ、指数バックオフ、またはジッターのどれを使うか。
- サーバーが提供する
Retry-After値を尊重するか。 - 同じ操作 ID が試行間で保持されるか。
- タイムアウトしたリクエストを、実際の処理が始まる前に失敗したと見なすか。
最後の前提は危険です。クライアントのタイムアウトは、クライアントが待機をやめたことを示すだけです。上流システムは、まだリクエストを受け付けていたり、完了していたりする可能性があります。生成コンテンツでは、そのため再試行によって別の結果と別の課金対象リクエストが発生し、アプリケーション上では 1 つの論理タスクしか見えていなくてもそうなりえます。
最悪時の増幅を見積もる
キューが 1 つのジョブを 3 回配信でき、アプリケーションのラッパーが 3 回の試行を許可し、SDK が初回呼び出しに加えて 2 回の再試行を行うとします。最悪の場合、1 つの論理ジョブは次を引き起こせます。
3 queue deliveries × 3 application attempts × 3 SDK attempts = 27 HTTP attempts
実際にこの最大値に達しないことはあっても、この掛け算は、短い 429 が再試行の嵐に変わる理由を説明します。その数を移行レビューに書き込みましょう。隠れたデフォルトが見えるようになります。
Step 2: エンドポイント設定を構成に移す
移行差分は元に戻せるようにしておきます。コードベース全体でエンドポイント文字列を置き換えないでください。
Python の前後
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["LLM_API_KEY"],
base_url=os.environ.get("LLM_BASE_URL", "https://api.openai.com/v1"),
max_retries=0,
timeout=45.0,
)
Flatkey のカナリアでは、次のように設定します。
export LLM_API_KEY="$FLATKEY_API_KEY"
export LLM_BASE_URL="https://router.flatkey.ai/v1"
TypeScript の前後
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.LLM_API_KEY,
baseURL: process.env.LLM_BASE_URL ?? "https://api.openai.com/v1",
maxRetries: 0,
timeout: 45_000,
});
これらの例では SDK の再試行回数を 0 に設定しています。これは、次のセクションでアプリケーションに明示的な再試行の責任を持たせるためです。アプリケーション側に再試行レイヤーがない場合は、代わりに SDK の再試行を上限付きで維持できます。誤って両方を有効にしないでください。
より広い互換性チェックリストについては、OpenAI Compatible API Gateway: Migration Checklist for Minimal Code Changes を参照してください。
Step 3: 1 つのレイヤーに明示的に再試行の責任を持たせる
有用な再試行ポリシーは次の 5 つの要素で構成されます。
- 再試行可能な障害の短い一覧。
- 厳格な試行上限。
- 最大総再試行時間。
- ジッター付き指数バックオフ。
- すべての試行に対する構造化ログ。
以下は、同期チャット呼び出し用の小さな Python ラッパーです。
import random
import time
from openai import APITimeoutError, APIConnectionError, APIStatusError, RateLimitError
RETRYABLE_STATUS_CODES = {408, 409, 429, 500, 502, 503, 504}
def create_chat_with_retry(client, *, model, messages, max_attempts=4):
started_at = time.monotonic()
for attempt in range(1, max_attempts + 1):
try:
return client.chat.completions.create(
model=model,
messages=messages,
)
except (RateLimitError, APITimeoutError, APIConnectionError) as error:
retryable = True
caught_error = error
except APIStatusError as error:
retryable = error.status_code in RETRYABLE_STATUS_CODES
caught_error = error
if not retryable or attempt == max_attempts:
raise caught_error
exponential_delay = min(2 ** (attempt - 1), 16)
jitter = random.uniform(0, 0.5 * exponential_delay)
sleep_seconds = exponential_delay + jitter
print({
"event": "llm_retry",
"attempt": attempt,
"next_delay_seconds": round(sleep_seconds, 2),
"elapsed_seconds": round(time.monotonic() - started_at, 2),
"error_type": type(caught_error).__name__,
})
time.sleep(sleep_seconds)
raise RuntimeError("unreachable")
これは、レビュー可能な出発点として扱ってください。普遍的なポリシーではありません。本番環境では、ローカルで計算した遅延にフォールバックする前に、有効な Retry-After レスポンスヘッダーを解析して尊重してください。再試行が製品で許容できるレイテンシを超えないように、総経過時間の予算も追加してください。
すべてのエラーを再試行しない
| Failure | Default action | Why |
|---|---|---|
400 invalid request |
変更せずに再試行しない | ペイロードを変更する必要があるため |
401 authentication |
変更せずに再試行しない | キーまたはヘッダーを変更する必要があるため |
404 model not found |
変更せずに再試行しない | モデル識別子またはアクセス権を変更する必要があるため |
429 rate limit |
遅延とジッターを付けて再試行する | 空き容量が利用可能になる可能性があるため |
500 or 503 |
小さな予算内で再試行する | 障害が一時的である可能性があるため |
| Client timeout | 慎重に再試行する | 上流のリクエストはすでに実行されている可能性があるため |
Flatkey のクイックスタートでは、429 に対して同じ高レベルのガイダンスが示されています。つまり、指数バックオフとジッターを使って再試行するということです。移行に特有の追加点は、そのポリシーを実行するレイヤーを 1 つだけにすることです。
Step 4: size concurrency for both RPM and TPM
OpenAI client migration では、リクエスト構文を維持しながら容量の上限が変わる場合があります。RPM と TPM は異なるワークロードを制約します。
- 少量のリクエストを多数送る場合は、RPM がボトルネックになります。
- プロンプト、出力、並列評価が大きい場合は、TPM がボトルネックになります。
単一の平均値ではなく、観測されたトラフィックを使います。少なくとも次を収集してください。
- 中央値とピーク時の 1 分あたりリクエスト数。
- p50、p95、最大値の入力トークン数。
- p50 と p95 の出力トークン数。
- リクエスト所要時間の中央値と p95。
- 同時ストリーム数。
概算の同時実行上限は、各制限から次のように見積もれます。
RPM ベースの同時実行数 ≈ (RPM / 60) × 平均リクエスト秒数
TPM ベースの同時実行数 ≈ (TPM / リクエストあたりの平均トークン数 / 60)
× 平均リクエスト秒数
初期上限としては低い方の結果を使い、その後、バーストや再試行のための余裕を残してください。
例: あるルートが 600 RPM と 300,000 TPM を許容し、平均リクエストが合計 1,500 トークンを使用し、平均所要時間が 3 秒だとします。
RPM 上限: (600 / 60) × 3 = 30 同時リクエスト
TPM 上限: (300,000 / 1,500 / 60) × 3 = 10 同時リクエスト
この例では、より厳しい制約は TPM です。RPM が十分に大きく見えるからといって 30 同時リクエストから始めると、回避可能な 429 応答が発生します。
この計算は方向性を示すものであり、プロバイダーの保証ではありません。プロバイダーは、ローリングウィンドウ、トークンバケット、入力トークンと出力トークンの個別制限、モデル固有のプール、またはアクセラレーション制御を使用する場合があります。テスト計画では、選択したモデルとアカウントに対する実際の動作を検証する必要があります。
Step 5: test streaming and timeout behavior separately
ストリーミングではない呼び出しが成功したからといって、ストリーミングも安全だと判断しないでください。
ストリーミング要求では、次をテストしてください。
- 最初のトークンまでの時間。
- チャンク間の最大無通信間隔。
- クライアントの読み取りタイムアウト。
- コンシューマーが切断されたときの動作。
- 再試行ラッパーが誤って 2 本目のストリームを開始しないか。
- 部分的な出力が保持されるか、破棄されるか、ユーザーに表示されるか。
部分的な出力の後に失敗したストリームは、何も出力される前に失敗したリクエストとは同等ではありません。自動で再試行すると、重複したテキストが表示されたり、異なる続きを生成したりする可能性があります。製品として再試行するのか、ユーザーに確認するのか、それとも部分結果を表示するのかを決めてください。
また、SDK のタイムアウトとインフラのタイムアウトは異なる場合があることも忘れないでください。リバースプロキシ、サーバーレスプラットフォーム、ジョブワーカー、またはブラウザー接続は、クライアントライブラリが自身のタイムアウトに達する前に終了することがあります。OpenAI client migration の間は、完全なリクエスト経路における最小のタイムアウトを記録してください。
Step 6: run a canary matrix before broad traffic
1 つの固定モデルと少量のトラフィックを使用してください。最初のカナリアで答えるべきなのは、新しいルートが動作を維持するかどうかであり、すべてのモデルが動作するかどうかではありません。
| テストケース | 入力 | 期待される証跡 |
|---|---|---|
| 認証 | 有効なキーと無効なキー | 成功に加え、再試行されない 401 |
| モデル検証 | 有効なモデルIDとスペルミスのあるモデルID | 成功に加え、再試行されないモデルエラー |
| 小規模リクエストバースト | 多数の短いプロンプト | 再試行スパイクのない制御されたキューイング |
| 大規模プロンプトバースト | より少数の高トークンプロンプト | TPM への圧力が可視化され、かつ上限がある |
強制 429 |
カナリアの上限を一時的に超える | 1つの再試行所有者、ジッター付き遅延、上限付き試行回数 |
| 強制タイムアウト | 意図的に短いクライアントタイムアウトを設定する | 無制限の再実行を伴わないタイムアウトの記録 |
| ストリーミング中断 | ストリーム中に切断する | 明示的な部分出力の挙動 |
| サーバーエラー | 503 を注入またはシミュレートする |
上限付き再試行と最終エラー報告 |
| ロールバック | 以前のベースURLを復元する | 設定のみのロールバックが成功する |
各論理操作について、次をログに記録します。
operation_id
attempt_number
base_url_name
model_requested
http_status
input_tokens
output_tokens
latency_ms
retry_delay_ms
final_outcome
次に、アプリケーションログと Flatkey Usage Logs を比較します。件数は整合しているはずです。1つのアプリケーション操作が複数のゲートウェイリクエストに対応する場合は、再試行の計測がその理由を説明できる必要があります。
ステップ 7: ロールアウトとロールバックのしきい値を定義する
OpenAI client migration には、最初のカナリア開始前に数値ベースの停止条件を設定しておく必要があります。
しきい値の例:
- 最終エラー率が合意済みの割合ポイントを超えて上昇した場合はロールバックする。
- 1操作あたりの試行回数が想定再試行予算を超えた場合は一時停止する。
- p95 レイテンシが製品のタイムアウト予算を超えた場合は一時停止する。
- 成功した操作あたりのトークン使用量が予期せず変化した場合は一時停止する。
- ストリーミング経路と非ストリーミング経路の両方が合格してからトラフィックを拡大する。
生の 429 件数だけを比較するのは避けてください。適切なキューにより、遅延リクエストが一時的に増えても最終エラーは減る可能性があります。試行レベルと操作レベルの両方の結果を追跡してください。
移行PRチェックリスト
このチェックリストを実装PRにコピーしてください。
- ベースURLとキーは環境変数から取得する。
- カナリアは正確に検証済みのモデル識別子を使用する。
- 再試行を担当する層は1つだけにする。
- SDK の再試行デフォルトはPR内で文書化する。
-
429、タイムアウト、5xxの挙動には上限付き試行回数を設定する。 - バックオフにはジッターを含め、存在する場合は
Retry-Afterを尊重する。 - RPM と TPM の上限は観測済みトラフィックから見積もる。
- ストリーミングには別の障害テストを用意する。
- 各試行は1つの論理
operation_idを共有する。 - Usage logs とアプリケーションログを比較する。
- ロールアウトとロールバックのしきい値を開始前に記載する。
- 追加のコード変更なしで以前のエンドポイントを復元できる。
一般的な移行ミス
SDK の再試行とアプリケーションの再試行を合計値を計算せずに併用する
これは最も重要なレビュー指摘です。デフォルトは、ローカル関数内で見えなくても、依然として振る舞いそのものです。
小さなプロンプトだけをテストする
1行のリクエストでは、認証情報とレスポンスの互換性は確認できます。しかし、TPM の圧迫、出力上限、長いストリーム、p95 レイテンシについてはほとんど何も分かりません。
認証エラーと検証エラーを再試行する
バックオフでは、無効なキー、未対応のパラメータ、スペルミスのあるモデルは修復できません。変更されていないペイロードを再試行すると、容量を無駄にし、実際の欠陥を隠してしまいます。
タイムアウトを、リクエストが実行されなかった証拠だとみなす
上流側が呼び出しを受け付けた後でも、クライアントは待機をやめることがあります。この曖昧さを前提に、再試行と集計を設計してください。
エンドポイント、モデル、プロンプト、再試行ポリシーを1回のリリースで変更する
そうすると、失敗の原因を切り分けにくくなります。まずは既知の1つのリクエスト形状を移行し、その後、ルートの挙動が可視化できてからモデル選択を広げてください。
“OpenAI互換”のより安全な定義
移行計画において、“OpenAI互換”とは、コード変更を減らせる程度にインタラクションのパターンが十分に馴染み深いことを意味すべきです。すべてのプロバイダーが同一のクォータ、トークンカウント、エラー意味論、レイテンシ、ストリーミング動作、またはパラメータ対応を共有しているという約束として解釈すべきではありません。
この区別があると、OpenAIクライアント移行のレビューはしやすくなります。役立つ場所では安定したインターフェースを維持しつつ、プロバイダーやルートによって差が出る運用上の契約はテストしてください。
Flatkey は、1つの OpenAI 互換ベースURLの背後でアクセスと課金を केंद集約し、クライアントの差分や後続のモデル拡張を簡単にできます。それでも必要なエンジニアリング作業は、本番トラフィックを移す前に、再試行、スループット、可観測性を明示することです。
それが、本番のOpenAIクライアント移行が満たすべき基準です。つまり、明示的な運用証拠に裏付けられた小さなインターフェース変更です。
カナリア用のモデルを選ぶ際はFlatkey の料金ページを確認し、コードレビューでチェックリストが通過し、ルートの挙動がログで確認できてから移行を承認してください。
よくある質問
移行中に OpenAI SDK の再試行を無効化すべきですか?
アプリケーションやキューがすでに再試行を担っているなら無効化してください。ほかのレイヤーが再試行しないなら、上限付きの SDK 再試行は妥当な場合があります。重要なのは、独立した再試行の責任者を複数持たないことです。
移行時の RPM と TPM の違いは何ですか?
RPM はリクエスト頻度を制限し、TPM はトークンスループットを制限します。小さく高頻度の呼び出しはまず RPM に達することがあり、少数の大きなプロンプトや出力はまず TPM に達することがあります。両方のワークロード形状をテストしてください。
429 は常に再試行すべきですか?
再試行とレイテンシの予算内に収まる場合に限ります。利用可能なら Retry-After を尊重し、そうでなければジッター付き指数バックオフを使ってください。操作がもはや製品のレイテンシ目標を満たせないなら停止してください。
タイムアウトした生成は安全に再試行できますか?
確実にはできません。クライアントがタイムアウトしても、上流のリクエストは実行されていた可能性があります。再試行は重複リクエストの可能性として扱い、各試行の関係をログに記録してください。
最小限で安全なカナリアとは何ですか?
1つの固定モデル、1つのリクエスト形状、明示的な再試行の責任分担、同時実行数の上限、そして 429、タイムアウト、ストリーミング中断、ロールバックのテストを使用します。トラフィックを拡大する前に、クライアント側の試行回数をゲートウェイの使用ログと比較してください。



