このガイドを読み終える頃には、OpenAIのBatch APIを呼び出し、数千ものモデルリクエストを単一の非同期ジョブとして実行し、すべての結果を50%割引で取得できるようになっているでしょう。プロンプトをJSONLファイルにパッケージ化し、単一のバッチを送信し、完了するまでポーリングして出力をダウンロードし、本番環境に組み込む前にApidogで各ステップをテストします。作業がよりインタラクティブな場合は、同期パスの方が適しており、代わりにApidogでChatGPT APIをテストできます。
Batch APIとは何か、いつ使うべきか
Batch APIは、遅延を許容できる大量のモデル呼び出し用の非同期エンドポイントです。プロンプトごとに1つのHTTPリクエストを送信する代わりに、多くのリクエストを単一のJSONLファイルにまとめ、1つのジョブとして送信し、完了をポーリングします。OpenAIはオフピーク時にジョブを実行し、すべての結果を出力ファイルで返します。
具体的なメリットは2つあります。1つ目は、同期APIと比較して、入力トークンと出力トークンの両方で一律50%割引が適用されることです。2つ目は、バッチジョブが個別のレート制限プールを使用するため、ライブトラフィックと競合せず、スループットが向上することです。トレードオフはレイテンシーです。OpenAIは24時間以内に完了することを約束していますが、多くのジョブはより早く完了します。しかし、それに頼ることはできません。
作業がオフラインで大量処理に適している場合は、バッチ処理を利用しましょう。
- 記録のバックログを分類またはタグ付けする
- コーパス全体の埋め込みを生成する
- 大量コンテンツ生成(商品説明、要約、翻訳)
- データセットに対する評価スイートやモデル比較の実行
ユーザーが待機しているものにはスキップしてください。チャットUI、オートコンプリート、ライブエージェントには同期エンドポイントが必要です。多数のモデルまたはエージェント設定を一度に生成する場合は、バッチ処理がそのワークロードと相性が良いです。バッチ処理で100以上のエージェント設定を生成する方法に関するウォークスルーを参照してください。
開始する前に必要なもの
全体のフローは、`/v1/files` と `/v1/batches` の2つのエンドポイントと4つのステップにまたがります。呼び出しを見る前に、その概要を説明します。
| ステップ | エンドポイント | 内容 |
|---|---|---|
| 1. アップロード | POST /v1/files |
purpose: "batch" を付けて .jsonl ファイルを送信し、ファイルIDを受け取る |
| 2. 作成 | POST /v1/batches |
エンドポイントと完了ウィンドウを指定してファイルIDを送信する |
| 3. ポーリング | GET /v1/batches/{id} |
status が completed になるまで確認する |
| 4. 取得 | GET /v1/files/{id}/content |
output_file_id を介して結果をダウンロードする |
読み進めるには、`OPENAI_API_KEY` としてエクスポートされたOpenAI APIキー、リクエストのJSONLファイル、およびAPI呼び出しを実行して検査するツールが必要です。各ステップはアサート可能なオブジェクトを返すため、ライフサイクル全体を簡単にテストできます。
ステップ1: JSONLファイルの構築とアップロード
入力はJSONLファイルであり、各行は自己完結型のリクエストです。各行には4つのフィールドが必要です。選択する `custom_id`(結果を入力と照合するため)、`method`(`POST`)、`url`(`/v1/chat/completions` のようなターゲットエンドポイント)、および実際のリクエストパラメータを保持する `body` です。
{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Classify the sentiment of: 'shipping was slow but the product is great'"}]}}
{"custom_id": "req-2", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4.1-mini", "messages": [{"role": "user", "content": "Classify the sentiment of: 'returned it the same day'"}]}}
`custom_id` はファイル内で一意である必要があります。結果は順序が保証されないため、そのIDを使用して各応答を元の行に再接続します。1つのバッチは最大50,000件のリクエストを保持でき、ファイルサイズは最大200MBです。
`batch` の目的でFiles APIにアップロードします。
curl https://api.openai.com/v1/files \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-F purpose="batch" \
-F file="@requests.jsonl"
応答にはファイル `id`(`file-abc123`のようなもの)が含まれています。それが次のステップで使用する `input_file_id` となります。
ステップ2: バッチの作成
次にジョブを作成します。`input_file_id`、ターゲットとする `endpoint`、および `completion_window` を渡します。現在、`completion_window` は単一の値 `"24h"` を受け入れます。
curl https://api.openai.com/v1/batches \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input_file_id": "file-abc123",
"endpoint": "/v1/chat/completions",
"completion_window": "24h",
"metadata": {"job": "sentiment-backfill"}
}'
`endpoint` フィールドは、JSONL行内で使用されている `url` と一致する必要があります。サポートされているターゲットには、`/v1/chat/completions`、`/v1/responses`、`/v1/embeddings`、`/v1/completions`、`/v1/moderations` などがあります。オプションの `metadata` オブジェクトには最大16個のキーと値のペアを保持でき、後でフィルタリングしたいジョブにタグ付けするのに便利です。
この呼び出しはバッチオブジェクトを返します。最も関心があるフィールドは次のとおりです。
{
"id": "batch_abc123",
"object": "batch",
"endpoint": "/v1/chat/completions",
"input_file_id": "file-abc123",
"completion_window": "24h",
"status": "validating",
"output_file_id": null,
"error_file_id": null,
"request_counts": { "total": 0, "completed": 0, "failed": 0 },
"created_at": 1733452800,
"metadata": { "job": "sentiment-backfill" }
}
ステップ3: バッチステータスのポーリング
新しいバッチは `validating` から始まります。そこから、一連の文書化された状態を経て進行します。`GET /v1/batches/{batch_id}` をポーリングし、`status` フィールドを読み取ります。
| ステータス | 意味 |
|---|---|
validating |
実行開始前に入力ファイルがチェックされています |
in_progress |
リクエストが処理されています |
finalizing |
実行が完了し、出力ファイルが準備中です |
completed |
完了; 結果をダウンロードできます |
failed |
検証に失敗しました; 何も実行されませんでした |
expired |
すべてのリクエストが完了する前に24時間ウィンドウが閉じました |
cancelling / cancelled |
キャンセルをリクエストしました |
`request_counts` オブジェクト(`total`、`completed`、`failed`)は、ステータスが `in_progress` の間、リアルタイムの進捗状況を示します。ここには待機するWebhookがないため、適切な間隔(毎秒ではなく、数分ごと)でポーリングするのが正しいパターンです。誤って送信した場合は、`POST /v1/batches/{batch_id}/cancel` で実行中のジョブをキャンセルすることもできます。
ステップ4: 出力の取得
`status` が `completed` となると、バッチオブジェクトは `output_file_id` を持ちます。Files APIを介してそのファイルの内容をダウンロードします。
curl https://api.openai.com/v1/files/file-output456/content \
-H "Authorization: Bearer $OPENAI_API_KEY" > results.jsonl
出力は再びJSONL形式で、リクエストごとに1行です。各行には、設定した `custom_id` に加えて、ステータスコードと本文を含む `response` オブジェクトがエコーされます。エラーになったリクエストは `error_file_id` で参照されるファイルに表示されるため、両方を確認してください。結果は行の順序ではなく、`custom_id` で入力と照合してください。
コストとウィンドウのトレードオフに注意する
計算は単純です。トークンを50%節約し、最大24時間のターンアラウンドを受け入れます。1回限りのバックフィルや夜間ジョブであれば、それは簡単な決断です。製品のクリティカルパス上の機能であれば、そうではありません。
いくつかの実用的な注意点:
- 割引は、サポートされているモデル全体で、入力トークンと出力トークンの両方に適用されます。
- 24時間ウィンドウは上限であり、目標ではありません。最悪のケースを想定してください。ジョブが `expired` になった場合、完了したリクエストは課金され返されますが、残りのリクエストはそうではありません。
- バッチジョブは個別のエンキューされたトークン制限から消費されるため、大きなバッチがライブトラフィックに依存するレート制限を食い潰すことはありません。すでに上限に達している場合は、GPT APIのレート制限とそれらをテストする方法に関するガイドが同期側をカバーしています。
- 半額のトークンでも量が増えれば合計金額は増えます。`metadata` タグを使用してジョブごとの費用を追跡し、後でコストを割り当てられるようにしてください。OpenAI費用に対するコストアトリビューションのプレイブックはこちらです。
Apidogでのテスト方法
Batch APIは、ファイル、JSONLの書式設定、およびポーリングループ全体にわたって失敗のモードが広がるため、単一のチャット呼び出しよりもエラーが発生しやすくなります。50,000件のリクエストファイル内の不正な形式の行はアップロード全体を失敗させ、検証が実行されるまでそれに気づきません。ライフサイクルエンドポイントを自動化する前にテストすることで、その往復の無駄を省けます。
Apidogは、各ステップをリクエストとして実行し、それらを連結し、応答についてアサートできるAPIプラットフォームです。エンドポイントをテストし、モックします。OpenAI SDKではありません。実際の設定は次のようになります。
- まずJSONLの形式を検証します。何かをアップロードする前に、各行に `custom_id`、`method`、`url`、`body` が含まれていること、そして `body.model` とメッセージが存在することを確認してください。不足しているフィールドをローカルで検出する方が、バッチの失敗よりもコストがかかりません。
- マルチパートアップロードを実行します。`purpose=batch` とファイルを指定して `POST /v1/files` を送信し、返された `id` を環境変数にキャプチャします。
- バッチを作成し、オブジェクトをアサートします。`POST /v1/batches` を実行し、`status` が `validating` であり、`endpoint` が送信したものと一致することをアサートします。Apidogのビジュアルアサーションを使用すると、スクリプトを書かずにこれらのフィールドをチェックできます。
- ポーリングして取得します。ループで `GET /v1/batches/{id}` を実行し、`status` が `completed` になったときにアサートし、`output_file_id` を取得して結果をダウンロードします。
- キャンセルパスとエラーパスを実行します。意図的に破損したファイルを送信して `failed` ステータスになることを確認し、`POST /v1/batches/{id}/cancel` をテストして、エラー処理が想定ではなく実際に機能することを確認します。
出力ファイルは後で届くため、完了したバッチオブジェクトのサンプルと事前定義された結果ファイルを返すモックAPIを立ち上げることもできます。これにより、実際の24時間ジョブを待つことなく、また開発中にトークンを消費することなく、取得および解析ロジックを構築・テストできます。チームが仕様先行で作業する場合、OpenAPI仕様から直接テストコレクションを生成し、バッチエンドポイントをCIで回帰テストの対象に含めることもできます。
よくある質問
バッチ処理には実際どれくらい時間がかかりますか?
OpenAIはバッチを24時間以内に完了することを約束しています。実際には多くのジョブがより早く完了しますが、保証は24時間という上限であるため、そのようにシステムを構築してください。作業が未完了のままウィンドウが閉じると、バッチは `expired` になり、完了したリクエストのみが返され、課金されます。
実際の割引率はどれくらいですか?また、重複適用されますか?
Batch APIは、同期エンドポイントと比較して、入力トークンと出力トークンの両方で一律50%割引を提供します。これは、OpenAIがオフラインワークロード向けに提供する最大のコスト削減策です。その費用を機能やジョブに割り当てようとしている場合、コストアトリビューションプレイブックがその方法を示しています。
バッチで実行できるエンドポイントはどれですか?
ターゲットはJSONLの `url` とバッチの `endpoint` フィールドの両方で設定し、これらは一致している必要があります。サポートされているターゲットには、`/v1/chat/completions`、`/v1/responses`、`/v1/embeddings`、`/v1/completions`、`/v1/moderations` のほか、画像および動画のエンドポイントが含まれます。OpenAIは時間の経過とともにこれらを追加するため、完全なリストは最新のドキュメントで確認してください。
結果の順序がばらばらなのはなぜですか?
設計上、順序は保証されません。出力JSONLは入力行の順序を保持しないため、すべてのリクエストに一意の `custom_id` が必要なのはこのためです。そのIDで結果を入力と照合してください。2つの行が同じ `custom_id` を共有している場合、それらの応答を確実に区別することはできません。
結論
これで、プロンプトをJSONLファイルにパッケージ化し、アップロードし、バッチを作成し、ステータスをポーリングし、出力を取得する一連のフローを、24時間以内にトークンコストの半額で実行できるようになりました。各ステップは検証可能なオブジェクトを返すため、JSONLの形式とポーリングループが正しければ、残りは機械的な作業です。
自動化する前に、ライフサイクルを手動で実行してください。Apidogをダウンロードして、リクエストファイルの検証、アップロード、作成、ポーリング、キャンセルエンドポイントの実行、およびバッチオブジェクトフィールドのアサートを行い、不正な形式の行が24時間の無駄な往復コストとならないようにしてください。
