OpenAI Batch APIの結果をリクエスト単位で照合する方法

OpenAI Batch APIの結果を入力行へ戻すとき、custom_idを使って各リクエストと応答を照合する方法を解説します。

AIものさし編集部

Batch APIの結果を入力行へ戻すとき、JSONLの行順や完了数だけで対応付けてはいけません。各入力に一意なcustom_idを付け、Batch objectのstatus・request_counts・output_file_id・error_file_idを確認し、成功ファイルとエラーファイルをそれぞれ読み込んでcustom_idで元入力へ結合します。最後に入力ID集合と成功・失敗・未解決の集合が一致するかを検算して、対象Batchの処理を閉じます。

結果照合の基準はBatch manifest

Batchを作る前に入力の基準表を保存します。アプリ側のmanifestには、Batch ID、入力file ID、作成時刻、対象endpoint、入力件数、各行のcustom_id、元レコードID、入力payloadのhash、処理状態を記録します。入力JSONLをあとで再生成できる場合も、実際に送った行とcustom_idの対応は永続化してください。OpenAIのBatch入力では各request行に一意なcustom_idを含め、完了後の結果との対応付けに使います。

custom_idは、元データの主キーまたはその安全な不透明IDから決めます。名前やメールアドレスなどの個人情報、プロンプト全文、Batchをまたいで衝突する連番を入れる必要はありません。Batch内で一意にし、別Batchを通しても調査できるよう、batch IDと組み合わせたキーを保存します。再実行時は同じcustom_idを使うのか、新しいattempt IDを足すのかを決め、旧結果と再試行結果を区別できるようにします。

入力JSONLを書き出した時点で次を確認します。

  • 行数がmanifestの入力件数と一致する

  • 各行がJSONとしてparseでき、method・url・bodyが対象endpointに合う

  • custom_idが空でなく、同じBatch内で重複しない

  • custom_idから元レコードを一意に引ける

  • 再試行attemptと別レコードを識別できる

  • 入力file IDが検証済みmanifestと対応する

この段階で失敗したらBatchを作らず、入力を修正します。送信後にmanifestを作り直すと、結果がどの入力版に対応するか追えなくなります。

Batch statusとrequest単位の結果を分ける

Batch作成時の応答にはBatch objectが返り、status、input_file_id、completion_window、output_file_id、error_file_idなどが含まれます。作成直後にstatusがvalidatingであれば入力検証中です。その後もin_progress、finalizingなどがあり、結果ファイルがまだ準備されていない段階があります。status名を部分一致で成功と判定せず、APIの状態値と意味に沿って扱います。

Batch objectはbatch IDをキーに保存し、retrieveで更新します。途中状態ではoutput_file_idやerror_file_idがnullでも、直ちに異常とは限りません。終端状態になった後にBatch objectを再取得し、status・request_counts・file IDを一緒に確認してからダウンロードします。API応答が一時的に取得できなくても、最初のcreate呼び出しをすぐ再送して別Batchを作ると二重処理になる可能性があります。Batch IDが分かるならretrieveで現在状態を確認してください。

Batch全体がcompletedでも、すべてのrequestが成功したとは限りません。request_countsにはtotal、completed、failedがあり、成功数と失敗数を区別できます。たとえばtotalが100、completedが95、failedが5なら、成功fileだけを100件分として処理してはいけません。expiredやcancelledなどの場合も、処理済みrequestと未完了requestを分けます。Batch全体のstatusと各requestの状態を別の列で保持してください。

outputとerrorを両方取得する

成功したrequestの出力はBatch objectのoutput_file_idで参照するfileから取得します。失敗したrequestの情報はerror_file_idで参照するfileから取得します。両方あるときは両方を回収してください。outputだけでは失敗行が見えず、errorだけでは成功内容が失われます。file IDがまだnullなら、終端状態や処理状況を再確認し、存在しないfileを空集合として扱わないでください。

出力JSONLには成功requestごとの応答行があり、失敗したrequestは別のerror fileに記録されます。出力行の順番が入力行と一致する保証はありません。入力の1行目と出力の1行目を結ぶ、あるいは行番号をキーにする処理を避け、両方のfileからcustom_idを読み、manifestへ照合します。

取得時はraw fileを保存し、別工程でparse・検証します。通信切断や保存失敗に備えてfile ID、取得時刻、byte数、hash、parse件数を記録します。保存したファイルが空の場合も「結果0件」と決めず、APIが返したfile metadata、status、request_counts、download結果を合わせて調べます。再取得する場合は同じBatchの同じfile IDを使い、別Batchの結果を混ぜません。

custom_idの集合で欠落・重複を調べる

照合では件数だけでなくID集合を見ます。入力manifestからBatch内の全入力custom_id集合を作り、output JSONLから成功ID集合、error JSONLから失敗ID集合を作ります。

表は横にスクロールできます

照合項目期待する状態不一致時に調べる点
manifestと入力JSONL件数・IDが一致入力作成時の重複や欠落
outputのcustom_idmanifest内に存在し、一意file取り違え、重複行、入力版
errorのcustom_idmanifest内に存在し、一意error file取り違え、未知ID
outputとerrorの重なり同じIDが重複していないattempt混在や二重取込
入力−成功−失敗未解決IDとして残る未実行、欠落、処理中

入力がitem-01、item-02、item-03の3件で、outputにitem-03とitem-01、errorにitem-02があれば、順番が違っても全件を特定できます。outputでitem-01が重複する、またはitem-99が混入する場合は、自動反映を止めます。item-02がoutputにもerrorにもない場合は未解決として残し、Batch status、request_counts、file取得範囲を確認します。

request_counts.total、completed、failedを集合結果と突き合わせます。数字が一致してもcustom_idがずれていれば完了とは言えません。反対に、error fileに一行あるからといってBatch全体が失敗したわけでもありません。request単位の状態を成功・失敗・未解決で保持し、Batch全体のstatusと混ぜないでください。

レコードへ反映する前の検証

出力のHTTP statusが成功でも、業務処理が正しいとは限りません。応答行ではbatch ID、custom_id、response status code、request ID、response body、errorを分けて保存します。元レコードへ反映する前に、response bodyがparse可能か、期待するschemaか、業務上必要な項目がそろっているかを別に検証します。API応答を受け取ったこと、JSONLをparseできたこと、業務処理が完了したことは別段階です。

更新処理をidempotentにします。同じoutput fileを再取り込みしても、同じcustom_idを別レコードへ追加したり、カウンターを二重加算したりしないよう、batch ID・custom_id・attemptを一意キーとして扱います。レコード更新前に現在versionや更新時刻を確認し、Batch実行後に人が編集した値を古い出力で上書きしないようにします。反映できない行は破棄せず、原因と次の対応を含むreview queueへ送ります。

再試行時は、失敗理由を分けます。入力形式エラー、endpointのrequest error、応答parse失敗、業務schema不一致、ダウンロード失敗では修正箇所も対象範囲も異なります。単にerror行を再送するのでなく、元入力、同じpayloadを再利用できるか、最新データで再生成すべきかを決めます。成功行も含むBatchを再実行する場合は、二重更新を防ぐattempt管理が必要です。

例:50件の評価Batchを閉じる

評価対象50件を送るケースでは、record IDから50行のJSONLを作り、それぞれに一意のcustom_idを付けます。manifestにはrecord ID、custom_id、promptの版、期待するresponse schema、payload hashを保存します。ここで使うIDは説明用で、実在のIDや個人情報をcustom_idに埋め込む必要はありません。

Batch作成後、返されたbatch IDでretrieveします。finalizing中でoutput_file_idがまだ得られていないなら、file取得へ進まず状態を再取得します。completedになった後、outputとerrorのfile IDを読み、各行をcustom_idでmanifestへ戻します。total 50、completed 48、failed 2なら成功48行を検証し、失敗2行のerrorを確認します。入力manifestとの差集合が0件になるか、未解決IDの扱いを明示した段階まで処理します。

成功48件を業務側へ反映するときは、schema・必要項目・現在versionを検証し、既に反映済みのcustom_idを二重適用しません。失敗2件のうち入力形式が原因ならpayloadを直して新しいattemptとして再送し、期限切れで未実行なら同じ入力を再利用できるか判断します。成功48件と総数50件だけで「96%成功」と報告するのではなく、完了・失敗・未解決のrecordを一覧化してください。

代表的な失敗と切り分け

output件数が入力より少ない。 statusとrequest_countsを読み、error_file_idを確認します。error fileにない残りは未解決として、terminal state、file取得、入力manifest件数を照合します。理由を推測で埋めず、request単位で閉じます。

出力順が入力と異なる。 順番に依存する実装を削り、custom_idで結合します。

outputとerrorに同じIDがある。 正規fileか、複数attemptのfileを混ぜたかを確認し、採用を自動決定せずreviewへ回します。

Batchはcompletedだが一部error。 Batch statusとrequest単位結果を分け、成功行を保持し、失敗理由と再試行要否を記録します。

download後の処理に失敗した。 raw JSONLとhashを保持し、同じfileを再parseします。file取得・parseの再試行がBatch自体の再実行を意味しないよう工程を分離します。

運用チェックリスト

  • 作成前に行数、custom_id一意性、JSONL parse、payload hashを検証した

  • Batch IDとinput file IDをmanifestへ保存した

  • retrieveでterminal statusとrequest_countsを再取得した

  • output_file_idとerror_file_idの両方を確認した

  • raw JSONLを別々に保存してからparseした

  • 結果を行順でなくcustom_idでjoinした

  • output/errorの重複ID、未知ID、欠落IDを確認した

  • 入力総数と成功・失敗・未解決の集合を照合した

  • response bodyのschemaと業務上の完全性を検証した

  • 再取り込みと再試行をidempotentにした

  • 成功・失敗・未解決のrecordを分けて報告した

AIものさしの判断

Batchの完了判定は、statusがcompleted、またはoutput fileを読めたことだけでは終わりません。入力manifestの全custom_idが成功・失敗・明示的な未解決のどれかへ一度だけ割り当てられ、重複・未知IDがなく、request_countsと集合が照合できている必要があります。順序が入力と違ってもcustom_idがあれば元レコードへ戻せます。

まず小さなBatchで正常応答、request error、欠落・重複custom_id、parse不正、再取り込みのfixtureを試します。この記事ではOpenAI APIを実行しておらず、出力形式や業務処理の実測ではありません。使用するendpoint・SDK・request schemaごとの動作は対象環境で確認してください。

Sources & scope

確認日:2026-10-15

確認範囲:input requestのcustom_id、Batch objectのstatus・request_counts・output_file_id・error_file_id、成功・失敗fileの取得、出力順と入力順が一致しない点。対象endpointの業務schema、quota、実アカウントでのBatch実行結果は未検証です。