Batchモードとは
通常のAPIコールとの最大の違いはリアルタイムではなくリクエストを「予約」して後でまとめて結果を受け取る点にあります。
メリット
通常のAPI料金の50%オフで利用可能です。
デメリット
非同期処理になるためリアルタイムコールに比べかなり実装がややこしくなります。
実装の全体像
0.前準備
1.リクエストの配列を作成
2.リクエストファイルのアップロード
3.バッチジョブの作成
4.バッチジョブステータスの確認
5.結果の取得
0.前準備
- envにOpenAIのAPIキーを設定
- バッチジョブ管理用のテーブルをcreate
以下のようなテーブルを作成
Schema::create('openai_batches', function (Blueprint $table) {
$table->id();
$table->string('batch_id')->unique(); // OpenAIから返されるID
$table->string('status'); // 実行状況
$table->json('result')->nullable(); // 完了後のレスポンス
$table->timestamps();
});1.リクエストの配列を作成
Batchモードでは、複数のリクエストを1つの .jsonl ファイル(1行1リクエストの形式)にまとめてアップロードします。
$apiKey = env('OPENAI_API_KEY'); // OpenAIのAPIキーをセット
$prompt = $request->input('prompt');
// リクエスト作成
$requests = [
[
"custom_id" => "req-" . uniqid(),
"method" => "POST",
"url" => "/v1/chat/completions",
"body" => [
"model" => "gpt-5-nano", // モデルを指定
"messages" => [
["role" => "user", "content" => $prompt]
],
]
]
];
// JSONL形式の文字列を作成
$jsonl = "";
foreach ($requests as $req) {
$jsonl .= json_encode($req) . "\n";
}2.リクエストファイルのアップロード
作成したJSONL形式のファイルをアップロード。
purpose を ‘batch’ にするのがポイントです。
$uploadResponse = Http::withToken($apiKey)
->attach('file', $jsonl, 'batch.jsonl')
->post('https://api.openai.com/v1/files', ['purpose' => 'batch']);
if (!$uploadResponse->successful()) {
return back()->with('error', 'Upload failed: ' . $uploadResponse->body());
}
$fileId = $uploadResponse->json('id'); // アップロードしたファイルのファイルIDを取得3.バッチジョブの作成
ファイルIDを指定して、バッチの実行を指示します。
$batchResponse = Http::withToken($apiKey)
->post('https://api.openai.com/v1/batches', [
'input_file_id' => $fileId,
'endpoint' => '/v1/chat/completions',
'completion_window' => '24h',
]);
if (!$batchResponse->successful()) {
return back()->with('error', 'Batch creation failed: ' . $batchResponse->body());
}
$batchData = $batchResponse->json();バッチジョブ情報をデータベースに保存
OpenAIBatch::create([
'batch_id' => $batchData['id'],
'status' => $batchData['status'],
]);4.バッチジョブステータスの確認
バッチは即時完了しないため、cronなどで定期的にステータスを監視し”completed“になるのを待って結果を取得します。
$batchModel = OpenAIBatch::findOrFail($id);
$apiKey = env('OPENAI_API_KEY');
// ステータス確認
$response = Http::withToken($apiKey)
->get("https://api.openai.com/v1/batches/{$batchModel->batch_id}");
if (!$response->successful()) {
return back()->with('error', 'Status check failed: ' . $response->body());
}
$data = $response->json();
$batchModel->status = $data['status'];$data[‘status’]が”completed“になっていたら結果を取得しDBに格納します。
if ($data['status'] === 'completed' && isset($data['output_file_id'])) {
$batchModel->output_file_id = $data['output_file_id'];
// 結果の取得
$fileResponse = Http::withToken($apiKey)
->get("https://api.openai.com/v1/files/{$data['output_file_id']}/content");
if ($fileResponse->successful()) {
$lines = explode("\n", trim($fileResponse->body()));
$results = array_map(fn($line) => json_decode($line, true), $lines);
$batchModel->result = $results;
}
}
// 結果をDBに格納
$batchModel->save();ステータスは以下が存在します。
| validating | 入力ファイルの検証中 |
| failed | 入力ファイルは検証プロセスに失敗しました |
| in_progress | 実行中 |
| finalizing | 結果を準備中 |
| completed | 完了 |
| expired | 24時間以内に完了できなかった |
| cancelling | キャンセル中 |
| cancelled | キャンセル済み |
感想
リアルタイム性が必要ないものはバッチで十分だと思いました。
コスト半額というのは大きい!しかし、実装がかなりややこしい。