ChatGPT APIは迅速にリリースされますが、しばしば契約を破り、テストが間違っていてもトークンごとに料金を請求します。ストリーミング応答は非ストリーミングとは異なる方法で失敗します。関数呼び出しは、モデルが返すものと常に一致しないJSONスキーマレイヤーを追加します。レート制限は開発コンソールではなく本番環境で静かに発生します。これらすべてをPython REPLやcurlループでデバッグすると、費用と時間を無駄にします。
このガイドでは、Apidog内でのChatGPT APIテストの全ワークフローを説明します。認証、最初のチャット補完、ストリーミングSSE、関数呼び出し、エラー処理、レート制限チェック、そして並行してフロントエンド作業を行うためのモック応答などが含まれます。このガイドを読み終える頃には、OpenAIの契約変更が本番環境に影響を与える前にそれを検知できる、再利用可能なApidogプロジェクトが完成しているでしょう。
ボタン
TL;DR
- ChatGPTのベースURL
https://api.openai.com/v1をApidog環境として追加し、APIキーを秘密変数として保存し、フォルダレベルでBearer認証を適用します。 /chat/completionsリクエストを一度構築して保存し、すべてのモデル(GPT-5.5、GPT-5.5 Pro、GPT-4o、o3)で再利用します。- ApidogはSSEストリーミングをネイティブで処理するため、追加ツールなしでレスポンスパネルにトークンごとの出力を表示できます。
- 関数呼び出しはリクエストボディ内の
tools配列に過ぎません。Apidogは返されたtool_callsJSONをスキーマに対して検証します。 - OpenAIのキー予算が尽きる前にフロントエンドの準備ができた際に、Apidog内でChatGPTをモックします。
- 動作するリクエストを、ステータスコード、
choices[0].message.content、usage.total_tokensに対するアサーションを含むテストシナリオとして保存します。プロンプトを変更するたびにCIで実行します。
なぜChatGPT APIをテストする必要があるのか
OpenAIのAPI表面は安定しているように見えますが、そうではありません。2024年1月から現在までに、チームは以下のものをリリースまたは変更しました。
function_callからtool_callsへ(2つの競合する形式がまだ存在)- ツールスキーマの厳格モード
temperatureとtop_pの調整を廃止した推論モデル(o1、o3)- バージョン管理付きの
response_format: { type: "json_schema" } - ツール呼び出しのストリーミング動作(デルタが断片的に届くため、組み立てが必要)
/v1/chat/completionsと重複する新しい/v1/responsesエンドポイント
これらをアプリケーションに直接組み込み、テストレイヤーをスキップすると、次のプロンプト変更PRで、ユーザーが苦情を言うまで気づかない回帰をリリースしてしまいます。Apidogのリクエストコレクションを使用すると、自分で制御できる契約が手に入ります。正確なリクエストをリプレイし、レスポンスを比較し、形状が変更されたときに大きなエラーを発生させることができます。
ステップ1: ApidogにOpenAIを環境として追加する
Apidogを開き、新しいプロジェクトを作成します。プロジェクト内で、環境管理(右上のドロップダウン)を開き、OpenAI Prodという環境を追加します。
| 変数 | 値 |
|---|---|
baseUrl |
https://api.openai.com/v1 |
OPENAI_API_KEY |
sk-proj-... (シークレットとして保存) |
defaultModel |
gpt-5.5 |
OPENAI_API_KEY をシークレットとしてマークすると、共有ワークスペースでマスクされ、エクスポートされたコレクションに書き込まれることはありません。Apidogはシークレットをユーザーごとに保存するため、プロジェクトをプルしたチームメイトは変数名を見ることができますが、自分のキーを提供する必要があります。
ステップ2: フォルダレベルでBearer認証を設定する
プロジェクト内に ChatGPT という名前のフォルダを作成します。フォルダ設定を開き、「認証」に進み、「Bearer Token」を選択して {{OPENAI_API_KEY}} を貼り付けます。フォルダ内のすべてのリクエストはこのヘッダーを継承します。これにより、すべてのリクエストに Authorization: Bearer sk-... を貼り付ける手間がなくなり、キーのローテーションも1回の編集で済みます。
これが、Apidogが生の curl ワークフローよりも高速になる小さな詳細です。認証が1か所にまとめられ、リクエストボディをきれいに保つことができます。
ステップ3: 最初のチャット補完リクエストを構築する
ChatGPT フォルダ内に、新しいリクエストを作成します。
- メソッド:
POST - URL:
{{baseUrl}}/chat/completions - ボディ (JSON):
{
"model": "{{defaultModel}}",
"messages": [
{ "role": "system", "content": "You are a senior backend engineer. Answer in under 100 words." },
{ "role": "user", "content": "What's the difference between idempotent and safe HTTP methods?" }
],
"temperature": 0.2
}
送信を押します。200 の応答が返され、choices[0].message.content フィールドには回答が、usage ブロックにはトークン数が含まれているはずです。このリクエストを chat-completion-basic として保存します。
401 が返された場合、キーが読み込まれていません。右上の環境ドロップダウンが OpenAI Prod に設定されていることを確認してください。429 が返された場合、レート制限に達しています。これは次のステップで説明します。
ステップ4: ストリーミング応答 (SSE) をテストする
ストリーミングは、ほとんどのChatGPT連携が破綻する場所です。応答はJSONではなく text/event-stream であり、各チャンクは部分的な delta を含む data: {...} 行です。ApidogはSSEをネイティブでサポートしています。
chat-completion-basic を複製し、chat-completion-stream に名前を変更し、ボディに "stream": true を追加します。
{
"model": "{{defaultModel}}",
"stream": true,
"messages": [
{ "role": "user", "content": "Stream the first 100 prime numbers, comma-separated." }
]
}
送信を押します。レスポンスパネルがストリーミングビューに切り替わり、各 data: チャンクが到着するたびにレンダリングされます。組み立てられたテキストだけでなく、実際のSSEフレームが表示されます。これは、不正なデルタや欠落している [DONE] ターミネータをデバッグするときに必要なビューです。
注意すべき点:
- 最後のフレームはリテラル文字列
data: [DONE]です。クライアントがこれを処理しない場合、JSONパースエラーが発生します。 usageは、"stream_options": { "include_usage": true }を渡さない限り、ストリーミング応答には含まれません。課金パイプラインが呼び出しごとのトークン数に依存している場合は、これを追加してください。- ツール呼び出しのデルタは断片的に届きます。
index、id、function.name、そして文字ごとに蓄積されるfunction.argumentsです。これを明示的にテストしてください。
ステップ5: 関数呼び出しとツール使用をテストする
関数呼び出しは、プロンプトの変更が下流のコードを静かに破壊する最も一般的な場所です。モデルは tool_calls 配列を返します。あなたの仕事は、引数が登録したJSONスキーマとしてパースされることを検証することです。
chat-completion-tools リクエストを以下のボディで作成します。
{
"model": "{{defaultModel}}",
"messages": [
{ "role": "user", "content": "What is the weather in Singapore right now?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string" },
"unit": { "type": "string", "enum": ["c", "f"] }
},
"required": ["city"]
},
"strict": true
}
}
],
"tool_choice": "auto"
}
正しい応答では、choices[0].message.tool_calls[0].function.name === "get_weather" となり、function.arguments は { "city": "Singapore", "unit": "c" } (または類似) にパースされるJSON文字列です。
リクエストの「テスト」タブに、以下を追加します。
pm.test("Tool was called", () => {
const body = pm.response.json();
const call = body.choices[0].message.tool_calls?.[0];
pm.expect(call?.function?.name).to.eql("get_weather");
});
pm.test("Arguments parse as valid JSON", () => {
const body = pm.response.json();
const args = JSON.parse(body.choices[0].message.tool_calls[0].function.arguments);
pm.expect(args.city).to.be.a("string");
});
実行します。緑色のテストがあなたの契約となります。OpenAIが形状を変更した場合、本番環境のトラフィックに影響が出る前にテストが赤くなります。
ステップ6: エラーとレート制限を明示的に処理する
本番環境のChatGPT連携は、5つの予測可能な方法で失敗します。それぞれのリクエストを構築し、期待される動作をアサートします。
| シナリオ | トリガー方法 | 期待される結果 |
|---|---|---|
| 無効なキー | Sandbox 環境で OPENAI_API_KEY を sk-bad に設定する |
error.code = "invalid_api_key" を伴う 401 |
| レート制限 | Apidogのコレクションランナーでリクエストを200回ループする | Retry-After ヘッダーを伴う 429 |
| トークン上限超過 | 200Kトークンのプロンプトを128Kコンテキストモデルに送信する | error.code = "context_length_exceeded" を伴う 400 |
| 不正なモデル名 | "model": "gpt-99" |
404 |
| スキーマ違反 | strict: true と不正な入力を伴うツール呼び出し |
モデルがツールを拒否し、プレーンテキストを返す |
「テスト」タブにアサーションを追加して、回帰がサイレントな再試行の嵐ではなく、赤色のテストとして表示されるようにします。Retry-After ヘッダーは、ほとんどの本番コードで間違って扱われるものです。これは秒単位で、時には小数値であり、バックオフをハードコードするのではなく、これを読み取るべきです。
ステップ7: 並行フロントエンド開発のためにChatGPTをモックする
あなたのOpenAIキーには月間の上限があります。あなたのフロントエンドチームにはありません。バックエンドのプロンプトが最終決定される前にUIがストリーミングされたトークン、提案されたフォローアップ、およびツール呼び出しカードをレンダリングする必要がある場合、Apidogモックを渡します。
ChatGPT フォルダ内で、chat-completion-basic リクエストを右クリックし、「Smart Mock」を選択して有効にします。Apidogは、OpenAIスキーマに一致する合成応答を返します: id、object、created、model、choices、usage。モックURLは https://mock.apidog.com/m1/<projectId>/chat/completions のようになり、同じボディを受け入れます。
ストリーミングモックの場合、「Advanced Mock」タブで、50ms間隔で data: { ... }\n\n チャンクを書き込むスクリプトを定義します。これにより、フロントエンドはOpenAIトラフィックなしでリアルなSSEストリームを受け取ることができます。
実際のプロンプトがデプロイされたら、フロントエンドのベースURLを https://api.openai.com/v1 に戻します。それ以外は何も変わりません。
ステップ8: スイートをCIテストシナリオとして保存する
Apidogのテストシナリオを使用すると、アサーション付きでリクエストを連結し、ヘッドレスで実行できます。以下のようなシナリオを構築します。
chat-completion-basicを呼び出し、status === 200およびusage.total_tokens > 0をアサートします。chat-completion-streamを呼び出し、SSEが[DONE]で終了したことをアサートします。chat-completion-toolsを呼び出し、ツール呼び出しスキーマが検証されることをアサートします。- ステップ6の各エラーシナリオを呼び出し、正しいステータスコードをアサートします。
シナリオをエクスポートし、apidog-cli run scenario.json --env OpenAI Prod を介してCIで実行します。プロンプトを保持するファイルのPRパイプラインに組み込みます。これにより、すべてのプロンプト変更がマージ前のチェックとしてライブのOpenAI APIに対して実行されます。費用はCI実行ごとに数セントです。価値は、プロンプトの回帰をリリースするのを止めることができることです。
よくある質問
Azure OpenAIでも動作しますか? はい。baseUrl をAzureリソースのURLに交換し、api-version クエリパラメータを追加し、認証をBearerから api-key ヘッダーに変更してください。リクエストボディは同じです。
o1およびo3推論モデルでも使用できますか? はい、できます。ただし、これらのモデルは temperature、top_p、presence_penalty、frequency_penalty を拒否します。簡略化されたボディテンプレートで Reasoning という別のフォルダを作成してください。
Apidog内でプロンプトをバージョン管理するにはどうすればよいですか? Apidogはブランチをサポートしています。プロンプトの実験ごとにブランチを作成し、ライブAPIに対してテストシナリオを実行し、トークンの使用量と応答の品質を比較してからマージします。これは、コードと同じワークフローをプロンプトに適用するものです。
新しい /v1/responses エンドポイントについてはどうですか? それ用に別のフォルダを設定してください。認証とベースURLは同じですが、ボディの形状のみが異なります。同じプロンプトに対してA/Bテストができるように、両方のフォルダを保持してください。
ApidogはAPI呼び出しごとに課金されますか? いいえ。Apidogクライアントは個人利用およびほとんどのチーム利用で無料です。OpenAIはトークンごとに課金しますが、ApidogはあなたとOpenAIの間に入り込んで課金することはありません。
まとめ
ChatGPT APIは変化し続けます。ストリーミングは新たな方法で壊れ、ツールスキーマはより厳格になり、推論モデルは安定していると思っていたパラメータを削除し続けるでしょう。これに対する防御策は、あなたが制御するリクエストコレクション、フロントエンドが頼れるモックサーバー、そしてすべてのプロンプトPRの前にCIが実行するテストシナリオです。
Apidogをダウンロードし、既存のOpenAI呼び出しをインポートしてください。Postmanコレクションも curl コマンドもワンクリックで変換できます。上記の8つのリクエストを一度構築すれば、将来のChatGPTの更新はすべて本番環境でのインシデントではなく、管理されたテスト実行となります。
