互換性が実際に約束するもの
OpenAI互換APIは、OpenAI SDKが送信するのと同じHTTPリクエストを受け付けます。ベースURLとキーを変更すると、クライアントは動作し続けます。この約束はプロトコルレイヤーに位置します:パス/v1/chat/completions、Authorizationヘッダ内のBearerトークン、modelとmessagesを含むJSONボディ、そしてchoicesとusageを含むレスポンスです。ストリーミングは同じサーバー送信イベントパターンに従います。
同じモデル、同じ動作、同じ制限、または同じエンドポイントセットを約束するわけではありません。元のAPIを提供する企業が提供するものを決定し、他のすべてのプロバイダはコピーするものを独立して決定します。互換性を、クライアントコードの書き換えを避ける方法として扱い、それ以外の変化がないという保証として扱わないでください。
違いを並べて比較
この表は、プロバイダー間で異なる領域と、このAPIで正しい(該当する)事項をリストアップしています。右側の列は、あらゆるサービスを評価する際に独自のテストを行うためのプロンプトです。
| 領域 | このAPI | どのプロバイダでも確認すべき項目 |
|---|---|---|
| モデルID | 1つのID:uncensored | 古いモデル名が受け入れられるか、マッピングされるか、拒否されるか |
| エンドポイント | /v1/chat/completions、/v1/responses、/v1/models | コードが呼び出すすべてのエンドポイントが存在するかどうか |
| モダリティ | テキストのみ | 画像、音声、ビデオのサポート(必要な場合) |
| 埋め込み、ファインチューニング | 提供不可 | それらの呼び出しが代わりにどこに行くか |
| コンテキストウィンドウ | 100,000 トークン(プロンプトと補完を含む) | 制限が入力のみをカバーするか、両方をカバーするか |
| 同時実行数とレート | フルキー:同時8件、1分あたり300件。トライアルキー:同時2件、1分あたり60件 | 制限の測定方法と拒否の報告方法 |
| 関数呼び出し、JSON モード | サポート | ストリーミングツール呼び出しなどのエッジケース |
| コンテンツの動作 | 法的な成人利用に対してコンテンツ拒否なしで回答するよう調整済み | モデルの返答内容を変更するモデレーションレイヤー |
| 請求 | 前払いクレジット、トークン単位、サブスクリプションなし | 最小値、有効期限、サブスクリプションティア |
このAPIの価格行は、1M入力トークンあたり$0.15、1M出力トークンあたり$0.50です。他のサービスについては、記憶やサードパーティの要約に頼るのではなく、現在の価格ページを読んでください。
モデルIDと動作
最も実用的な違いはモデルそのものです。OpenAIでは、名前の付いたモデルのメニューから選択できます。ここでは"model": "uncensored"を送信し、独立したプロバイダが運用する単一のオープンウェイトモデルを取得します。これは任意のモデルベンダの公式APIではなく、複数のベンダにリクエストを転送するルータでもありません。
これには2つの結果があります。まず、文字列を変更してモデルのサイズをA/Bテストすることはできません。1つのモデルを実行し、その周囲でプロンプトとサンプリングを調整する必要があります。第二に、別のモデル向けに書かれたプロンプトは、指示の遵守、フォーマットの習慣、拒否の動作がすべて異なるため、小さな編集が必要になる場合があります。このモデルは、法的な成人向け利用に対してコンテンツ拒否なしで回答するようにチューニングされているため、より厳格なモデル向けに書かれた長い安全上の前置きを伴うプロンプトは、しばしば短縮できます。
変更しないハード制限が1つあります:未成年者が関与する性的コンテンツは禁止です。
存在しないエンドポイントと機能
互換性はOpenAIの全体面でほぼ完全ではありません。ここではギャップは意図的です:このサービスはテキストのみを処理します。
- 画像、音声、ビデオの生成はありません。
- 埋め込みエンドポイントがないため、検索パイプラインには別の埋め込みプロバイダが必要です。
- ファインチューニングはありません。
含まれている機能は、ほとんどのチャットアプリケーションをカバーしています:POST /v1/chat/completions、POST /v1/responses、ストリーミング、toolsおよびtool_choiceによるツール呼び出し、およびresponse_formatによるJSONモード。temperature、top_p、stop、seedなどのサンプリング制御とペナルティは、モデルに渡されます。
アプリケーションが複数の機能を組み合せる場合、通常のパターンは、埋め込みと画像には元のプロバイダーを維持し、無検閲にしたいテキスト生成のみを新しいベースURLに送信することです。コストとトレードオフの分析はこの分割を処理します。
制限、エラー、ヘッダ
制限は、コードが負荷下でどのように振る舞うべきかを決定するため、移行時に人々を驚かせます。このAPIは、フルキーで8つの同時リクエストと1分あたり300リクエスト、トライアルキーで2と60、最大ボディサイズ8 MBを設定します。これを超えるリクエストはHTTP 429を返します。エラーコードは、どの制限に到達したかを示します:1分あたりのリクエストの場合はrate_limited、同時に多すぎる場合はconcurrency。一時的な過負荷も429を返し、Retry-After: 5を追加します。
ステータスだけでなく、コードとヘッダを読んでください。concurrencyエラーをすぐに再試行すると状況が悪化しますが、短い待機時間で解決します。ストリーミングリクエストはストリームが終了するまでスロットを保持するため、同時実行数はフライト中の呼び出しだけでなく、オープンなストリームもカウントします。
レスポンスにはX-Request-Id、X-Balance-USD、X-RateLimit-Limit-RequestsおよびX-RateLimit-Limit-Concurrencyが含まれます。残高ヘッダーはプロバイダー固有の追加項目です:OpenAI SDKコードは無視しますが、支出を追跡するために読み取ることができます。残高はGET /v1/balanceおよびOpenAIスタイルのGET /v1/dashboard/billing/credit_grantsでも利用可能です。
マイグレーションチェックリスト
アプリケーションをプロバイダー間で移動する際は、毎回同じ短いリストを確認してください。これにより、ユーザーが気づく前にほとんどの予期しない問題をキャッチできます。
- ベースURL、キー、モデルIDを一緒に置き換え、設定ファイルに隠れた古いモデル名をリポジトリ内で検索してください。
- 新しい側に存在しないエンドポイントへの呼び出しを削除し、それらがあるプロバイダにルーティングしてください。
- プロンプトセットを実行し、出力を比較してください。異なるモデルからは異なる言葉とフォーマットが期待されます。
- ユーザーとAPIの間のすべてのプロキシとロードバランダを介してストリーミングをテストしてください。
- エラーコードとRetry-Afterヘッダを読み取る429用のリトライロジックを追加してください。
- リクエストごとのトークン使用量をログに記録し、コストの驚きが月末ではなく数日以内に現れるようにしてください。
簡単な互換性テスト
機能リスト(これを含む)を盲目的に信頼しないでください。アプリケーションが行う呼び出しを実行し、結果を比較してください。あなたが依存している部分をテストする短いスクリプトを実行するには数分かかります:
from openai import OpenAI
client = OpenAI(
base_url="https://api.apicompatible.com/v1",
api_key="YOUR_API_KEY",
)
# 1. Plain completion
r = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Reply with the word ready."}],
temperature=0,
seed=7,
)
print("plain:", r.choices[0].message.content)
# 2. JSON mode
j = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Return a JSON object with keys name and age for a fictional person."}],
response_format={"type": "json_object"},
)
print("json:", j.choices[0].message.content)
# 3. Streaming
chunks = client.chat.completions.create(
model="uncensored",
messages=[{"role": "user", "content": "Count from one to five."}],
stream=True,
)
print("stream:", "".join(c.choices[0].delta.content or "" for c in chunks if c.choices))
コードが関数を使用している場合は関数呼び出しのケースを追加し、実際のプロンプトサイズに近い長期コンテキストのケースを追加してください。各パラメータの詳細はドキュメントに記載されており、API キーページでは、この種のチェックに十分な$0.50の無料トライアルクレジットを受け取る方法を示しています。
