OpenAI SDKでGeminiを呼び出す:最小のテキストリクエストからロールバック可能な移行まで
SDK、ベースURL、モデルそれぞれの互換性の境界を切り分けます。非ストリーミングのテキストリクエスト、エラー、課金の詳細を検証してから、タスクごとにパラメータを戻していきます。クライアントの互換性がサービス全体の互換性を意味すると思い込まないようにします。
製品ステータスの更新(2026-10-09確認、北京時間):EveryInfraのAI APIは再び利用可能になりました。チャージ済みのアカウントは無料で呼び出せ、ストリーミング(stream: true)にも対応しています。この記事は呼び出しが課金され、レスポンスが非ストリーミングのみだった時期に書かれたため、課金と非ストリーミングに関する記述は現在は当てはまりません。SDKとbaseURLの設定は引き続き有効です。現在の設定方法はドキュメントのAI APIセクションを参照してください。
OpenAI SDKをすでに使っているアプリケーションを移行する際、最も過小評価されがちなのはアドレスの変更ではなく、どの挙動が引き続き成り立つかの再検証です。リクエストがテキストを返したということは、この入力がこのサービスで結果を生んだことを示すにすぎません。従来のプロンプトの性能が変わらないこと、ストリーミングが動作すること、ツール呼び出しが正しく機能すること、例外発生後も課金状態が整合していることまでは証明しません。
本記事ではEveryInfraのGemini非ストリーミングテキストエンドポイントを例に、項目ごとに検証できる移行手順を示します。まず設定を固定し、次に最小限のリクエストを検証し、レスポンスとエラーを確認し、最後に業務用パラメータを戻して利用を拡大するかどうかを判断します。OpenAIのすべての機能を別のサービスに移行できるという汎用的な互換性を約束するものではありません。
互換レイヤーの理解
まず、3つの対象を区別します。OpenAI SDKはクライアントライブラリです。サービスのアドレスは、実際にリクエストを受け取るシステムを決めます。モデルIDは、そのサービスが現在提供しているカタログに含まれていなければなりません。同じnpmパッケージを使っていても、同じキー、同じモデル名の一覧、同じサービス契約を使っていることにはなりません。
GoogleのOpenAI互換レイヤーのドキュメントには、OpenAI SDKを使ってGoogleのGeminiサービスにアクセスする例が載っています。本記事ではEveryInfraのアドレスと認証情報を使います。Googleのドキュメントに登場するモデルや機能が、そのままEveryInfraで利用できるもののチェックリストになるわけではありません。GoogleとEveryInfraの認証と課金は、個別に検証する必要があります。
OpenAI TypeScript SDKのドキュメントでは、Chat Completionsの呼び出し方法を説明しています。SDKには他のAPIも含まれていますが、クライアントにメソッドがあるからといって、接続先のアドレスが対応するルートを実装しているとは限りません。特に既存プロジェクトでresponses.create、ファイルアップロード、バックグラウンドタスク、セッション管理を使っている場合、baseURLを差し替えただけで移行が完了したと考えないでください。
設定の事前固定による認証情報とアドレスの取り違え防止
環境は少なくとも開発、受け入れ、本番の3つに分けます。各環境で、サービスのアドレス、そのサービス用のキー、モデル、タイムアウト、許可する機能を明示的に定義します。グローバルな環境変数を1か所で変更した結果、まだ旧サービスに接続している別のコードが新しい認証情報を誤って送ってしまう、といった事態を避けてください。
本記事ではEveryInfraのベースパスをhttps://api.everyinfra.com/api/v1に固定します。これはコンソールのアドレスでも、/chat/completionsまで含めた完全なパスでもありません。SDKは引き続きエンドポイントを末尾に追加します。さらに/v1を付けたり、完全なエンドポイントをベースパスとして使ったりすると、リクエストが誤った宛先に送られるおそれがあります。
キーは必ずサーバー側の環境に置き、ブラウザのバンドル変数、リポジトリ、スクリーンショット、ログには含めないでください。本記事を試すためだけに、ブラウザでのキー公開を許可するオプションを有効にする必要はありません。ログにも明示的なホワイトリストを使い、SDKのエラーオブジェクト全体やリクエストヘッダーをそのまま出力しないようにします。
ログの設計は、OWASPの機密情報除外のチェックリストと照らし合わせることができます。原因の特定に必要な状態と関連する識別子は残し、トークン、セッション値、不要な個人情報は除外します。呼び出し側がそれぞれ手作業で機密フィールドを削除するのに頼るより、一貫性を保ちやすくなります。
まず公開カタログでモデルを確認します。この手順に業務用のキーは不要です。
set -o pipefail
curl -fsS --max-time 30 'https://api.everyinfra.com/api/v1/models' \
| jq -e '{
default_model,
models: [.data[].id]
} | if (.models | length) > 0
and (.default_model as $m | .models | index($m)) != null
then . else error("invalid model catalog") end'カタログで要件に合うIDを選んだら、EVERYINFRA_MODEL_IDを明示的に設定します。カタログの読み取りに失敗した場合は、古い名前に黙ってフォールバックせず、検証を中止してください。デフォルト値の変更も設定変更として評価し、実行のたびに知らないうちにモデルが切り替わることがないようにします。カタログが示すのは宣言内容だけで、そのキーが選んだモデルを呼び出す権限を持っているかどうかは確認できません。
最初に試す最小限の非ストリーミングテキストリクエスト
以下のコードには、サーバー側のNode.js環境と、プロジェクトでバージョンを固定したopenaiパッケージが必要です。このコードは1回分の業務呼び出しのテンプレートです。本記事ではオフラインでチェックしており、合成したレスポンスをオンラインでの結果とは呼びません。サンプルを動かすためだけにプロジェクト全体の依存関係をアップグレードせず、まず現在のSDKのバージョンとロックファイルを記録してください。
import OpenAI from "openai";
const apiKey = process.env.EVERYINFRA_API_KEY?.trim();
const model = process.env.EVERYINFRA_MODEL_ID?.trim();
if (!apiKey || !model) throw new Error("Missing EveryInfra configuration");
const client = new OpenAI({
apiKey,
baseURL: "https://api.everyinfra.com/api/v1",
maxRetries: 0,
timeout: 120_000,
});
const { data, response: http } = await client.chat.completions.create({
model,
messages: [{ role: "user", content: "explain idempotency in one sentence." }],
stream: false,
}).withResponse();
const choice = data.choices?.[0];
const text = choice?.message?.content;
if (typeof text !== "string" || !text.trim()) {
throw new Error("No usable text; inspect this attempt before retrying");
}
console.log({
httpStatus: http.status,
model: data.model,
finishReason: choice.finish_reason,
requestId: http.headers.get("x-request-id"),
textReceived: true,
});
// hands the text hand it to the business layer; do not write the full generated content to runtime logs by default。ここでの120秒は、デモ用に設定したクライアント側の待機時間の上限です。サービスの応答時間の制限や性能の保証ではありません。アプリケーションでは、入口のプロキシ、タスクの実行側、ユーザーインターフェースの間で待機時間を調整する必要もあります。クライアントのタイムアウト後にサービスの完了情報を受け取った場合、その結果は確認が必要な不明な結果として扱ってください。実行されなかったと自動的に判断するものではありません。
初回の受け入れ確認では自動リトライを無効にし、単一の呼び出しの挙動を確認します。OpenAI SDKには、一部の接続エラーやサービスエラーに対するリトライ機能が組み込まれています。本番環境でリトライを再び有効にする前に、SDKのデフォルト設定をそのまま使うのではなく、重複リクエスト、課金処理、冪等性に関する対象サービスの仕様を確認してください。SDKのリトライとタイムアウトに関するガイド
非ストリーミングが通っても、そのまま stream: true に切り替えない
現在のEveryInfraの実装では、チャットリクエストを非ストリーミングで処理します。コードにstream: trueと書いたり、Googleの公式互換レイヤーがストリーミングに対応していたりしても、このアドレスがSDKの想定どおりのイベントストリームを返す証明にはなりません。本ドキュメントでは明示的にstream: falseのままとし、未検証の経路にストリーミングUIを接続しないようにしています。
業務で1文字ずつの表示が必要な場合は、まず未達の移行条件として記載してください。サービスが明示的に対応した後、レスポンスタイプ、イベントの順序、終了マーカー、中断からの復旧、最終的な使用量を個別に確認します。通常のJSON成功レスポンスをストリーミングの受け入れ確認として扱わないでください。また、フロントエンドでテキストを分割したものをサーバー側のストリームと呼ばないでください。
ツール呼び出し、構造化出力、マルチモーダル入力、推論制御、その他の任意のパラメータは個別に確認してください。クライアントの型定義でフィールドが許可されていても、それはシリアライズできることを示すだけです。対象サービスがそのフィールドを想定どおりに解釈して実行することとは別の問題です。
レスポンスが同時に答えるべき3つの問い
1つ目は、業務要件を満たす内容が得られたかどうかです。空でないテキストでも、話題から逸れていたり、出力形式に違反していたり、長さの制限によって最後まで生成されていなかったりする場合があります。finish_reasonを保持し、タスク自体の完了基準を確認してください。HTTP 200をユーザーのタスクの成功と同一視しないでください。
2つ目は、どのデータを下流のコンポーネントに安全に渡せるかです。choicesとusageを読み取る際は、値の欠落や型の不一致に対処してください。JSONを必要とする業務ロジックでは、構造を別途パースして検証する必要があります。TypeScriptのコンパイルが通ったという理由だけで、サードパーティから実行時に返されるレスポンスが静的な型宣言と一致すると信用しないでください。
3つ目は、このリクエストのコストとステータスです。EveryInfraの拡張課金情報はすべてのSDKの汎用型に含まれるわけではないため、サービスのドキュメントと照らし合わせて個別に確認してください。欠落しているフィールドを0で埋めたり、トークン使用量をそのままウォレット残高からの引き落とし額として扱ったりしないでください。モデルの出力、リクエスト識別子、課金情報との対応関係を別々に保存しておくと、HTTPでは成功していても内容は失敗しているといった状況を説明しやすくなります。EveryInfraのAPIと課金のドキュメント
SDKの公式ドキュメントによると、レスポンスの追加プロパティは、静的型に記述されていないという理由だけで自動的に削除されることはありません。これらのフィールドが必要な場合は、as anyで検証を省略せず、ランタイムでチェックしてください。.withResponse()を使うとパース済みのデータとHTTP情報を同時に取得できますが、サービスが返していないリクエストIDを作り出すことはできません。SDKの拡張レスポンスとHTTP情報
拡張フィールドのランタイムスキーマを定義する際は、JSON Schemaのオブジェクトに関するガイドラインを参照し、必須、任意、許容される値の型を個別に記述してください。構造チェックに合格しても、課金の意味やモデルの結論が正しいことの証明にはなりません。業務上の意味は別途検証する必要があります。
エラー特定後のリトライ可否の判断
不明なモデルとサービスの障害を、同じ処理分岐で扱うべきではありません。2026-09-04時点でデプロイされているソースコードを基準にすると、今回のように必要なメッセージをすべて含むRESTリクエストでは、まずAPIキーを識別し、次にモデルを確認します。不明なモデルの場合は、課金が発生する前に422 unknown_modelが返されます。メッセージがないリクエストは先に入力チェックで拒否されるため、この順序をすべてのエラーの組み合わせに一般化することはできません。これはソースコードの確認によるもので、今回実際に認証情報を使ったリクエストを観察したものではありません。
unknown_modelを受け取った場合は、まずカタログを更新して設定を修正してください。同じエラー名に対して際限なくリトライしたり、類似した候補モデルを自動的に受け入れたりしないでください。モデルを切り替えると動作が変わる可能性があります。401の場合は、宛先アドレスとキーを確認します。権限不足は権限の管理者に、クォータ不足はアカウントの所有者に対応を依頼してください。アプリケーションが制限を回避するために、独自に別のキーへ切り替えてはいけません。
ネットワークの異常は2種類に分かれます。1つはリクエストが明らかに完了しなかった失敗、もう1つはリクエストは送信されたものの最終的な結果を確認できないタイムアウトや切断です。一般に、クライアントは例外クラスだけでは課金が発生したかどうかを判断できません。試行ごとに業務処理番号、タイムスタンプ、実際に使われたモデル、取得可能なリクエストIDを保存してください。その試行の結果を確認してから、新しいリクエストを開始するかどうかを判断してください。
オフラインチェックで検証できることとできないこと
オフラインテストでは、SDKのfetchを差し替え、最終的なURL、POSTメソッド、認証ヘッダーの形式、リクエストボディを確認したうえで、合成であることを明示したレスポンスを返せます。実際のプロンプトを送信したりアカウントのクォータを消費したりせずに、パスの重複、パラメータの入力ミス、パースの前提、エラー分岐の問題を見つけるのに役立ちます。
openaiパッケージをバージョン7.10.0に固定して当初実施した分離テストでは、テキスト、使用量、422エラーのシミュレーションを対象としました。今回はバージョンとテスト範囲を明記しており、最新バージョンやサービス互換性の認定とは位置づけていません。新しい最小テンプレートでも、設定が欠けている場合にリクエストが送信されないこと、1回の呼び出しで自動リトライが行われないこと、空のコンテンツをレビューが必要な結果として保持することを検証する必要があります。
2026-09-02のアーカイブには、Python SDKによる非ストリーミングの本番サンプルが追加で含まれています。これらは当時のPythonリクエストを裏付けるものですが、現在のTypeScriptテンプレートが実際の統合を完了したことの証明にはなりません。今回は実際のTSによる業務呼び出しを新たに追加していないため、認証、日常的なテキストの品質、課金結果については引き続き認可済みのサンプルによる検証が必要です。
タスクサンプルに基づく業務パラメータの復元
最小リクエストが通ったら、機能を1種類ずつ戻していきます。まず実際のプロンプトとコンテキスト、次に出力の制約、最後にアプリケーションで本当に必要で、かつサービスが対応を明言しているオプションの順です。従来のプロジェクトからすべてのパラメータを一度に取り込むのではなく、トラブルシューティング時の比較用に、最小構成で再現可能な入力を保存しておいてください。
タスクサンプルには、代表的な正常系入力、空の入力、長いコンテキスト、曖昧な質問、業務で必要な形式を含めてください。タスクの完了度、形式をパースできるか、事実に基づいているかを評価します。生成モデルでは、逐語的な一致を唯一の成功基準にしないでください。品質を割合で示す場合は、サンプルと人による評価基準を明記し、簡易的なテストから一般化した改善効果を主張しないでください。
実際のリクエストは成功しているのに業務上の出力品質が低下している場合は、原因がプロンプト、モデル、入力の切り詰め、パースルールのどれにあるかを切り分けてください。すべての不一致をSDKのせいにしないでください。逆に、HTTP呼び出しがまったく成功しない場合は、まずパス、認証、サービスの状態を確認してください。通常、プロンプトを調整しても接続の問題は解決しません。
業務アクションの自動再実行を伴わない、ロールバック可能な移行
業務の呼び出しレイヤーでは、サービスのアドレス、モデル、バージョンの設定を明示的に管理し、各実行でどの組み合わせを使ったかを記録できます。切り替える前に、検証済みの以前の設定を保存してください。ロールバックの目的は、以降のリクエストを制御可能な状態に戻すことです。結果が不明なリクエストを別のサービスへ自動的に再送することではありません。
本格的に利用を拡大する前に、認可済みの実際のテキスト処理フロー、一連の業務サンプル、エラー処理、課金との関連付けをすべてレビューできることを確認してください。ストリーミングやツール機能について検証の根拠がまだない場合は、未移行のチェックリストに残しておいてください。こうすることで、「移行完了」がクライアント設定を3行変更しただけの状態ではなく、具体的なタスクと機能に対応したものになります。
参考資料 LF-09