Claude API を使った AI アプリケーション開発で、大量のテキスト処理やデータ分析を行う必要はありませんか?
従来のリアルタイムAPI呼び出しでは、各リクエストごとに通信コストがかかり、大規模運用では費用が膨らみます。そこで活躍するのが Claude API のバッチ処理機能 です。
このガイドでは、バッチ処理の仕組み、実装方法、そして実際のユースケースまで、わかりやすく解説します。
バッチ処理とは:リアルタイム処理との違い
リアルタイムAPI呼び出し
- ユーザーが「今すぐ実行」したいときに、その場で API に送信
- 数秒で結果が返ってくる
- 1リクエスト = 1通信コスト(割高)
バッチ処理
- 複数のリクエストをまとめてファイルにして、バッチ API に送信
- 数分〜数時間後に結果がまとめて返ってくる
- 複数リクエストを 1回の送信で効率化(最大90%割引)
使い分けの目安:
- リアルタイム処理が必要 → 通常の API を使う
- 結果が数時間後でも OK → バッチ処理を使う
バッチ処理で可能なこと:実践的なユースケース
バッチ処理は以下のようなシナリオで特に効果的です。
① 大量テキスト分析
例)1,000件のカスタマーレビューを一括分析
従来: 1,000件 × $0.003 = $3.00
バッチ: 1,000件 × $0.0003 = $0.30(90%削減)
② 定期的なデータ処理
夜間に毎日 5,000件のログを分析するなど、スケジュール化できるタスク。リアルタイムで結果が必要でないため、バッチ処理が最適です。
③ 研究・論文執筆の補助
学術論文数百編の要約作成、データセット全体の分析など、学生・研究者向けのアカデミックプランと組み合わせると特に効果的。
④ マルチランゲージ翻訳
複数言語への翻訳タスク 500〜 1,000件を一括処理。翻訳品質を確保しながら費用を最小化できます。
バッチ処理の実装:ステップバイステップ
Step 1: リクエストファイルの形式を理解する
バッチ API は JSONL 形式(JSON Lines:1行1オブジェクト)を期待します。
{"custom_id": "req-1", "params": {"model": "claude-3-5-sonnet-20241022", "max_tokens": 100, "messages": [{"role": "user", "content": "日本語で「こんにちは」と言ってください"}]}}
{"custom_id": "req-2", "params": {"model": "claude-3-5-sonnet-20241022", "max_tokens": 100, "messages": [{"role": "user", "content": "英語で「Hello」に関する短編を書いてください"}]}}重要なポイント:
custom_id: 各リクエストを特定するためのユニークID(自由に命名OK)params: 通常のメッセージ送信で使うパラメータと同じ構造
Step 2: JSONL ファイルを生成する(Node.js例)
const fs = require('fs');
// サンプルリクエスト群
const requests = [
{ id: 'review-1', text: 'この製品のレビューを分析してください:「とても良い製品ですが、送料が高い」' },
{ id: 'review-2', text: 'このレビュー分析:「価格は妥当だが、配送が遅い」' },
{ id: 'review-3', text: 'レビュー分析:「品質は最高。次も買う!」' },
];
// JSONL に変換
const jsonl = requests
.map(req => JSON.stringify({
custom_id: req.id,
params: {
model: 'claude-3-5-sonnet-20241022',
max_tokens: 150,
messages: [{
role: 'user',
content: req.text
}]
}
}))
.join('\n');
// ファイルに保存
fs.writeFileSync('batch_requests.jsonl', jsonl);
console.log('✅ JSONL ファイル生成完了');Step 3: Anthropic SDK でバッチを送信
const Anthropic = require('@anthropic-ai/sdk');
const fs = require('fs');
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
async function submitBatch() {
// JSONL ファイルを読み込み
const fileContent = fs.readFileSync('batch_requests.jsonl');
// ファイルをアップロード
const response = await client.beta.files.upload(
{ file: new File([fileContent], 'batch_requests.jsonl') },
{ headers: { 'anthropic-beta': 'files-api-2025-04-14' } }
);
const fileId = response.id;
console.log(`📤 ファイルアップロード完了: ${fileId}`);
// バッチを作成・送信
const batch = await client.beta.batches.create(
{ input_file_id: fileId },
{ headers: { 'anthropic-beta': 'files-api-2025-04-14' } }
);
console.log(`📋 バッチ作成完了: ${batch.id}`);
console.log(`⏳ ステータス: ${batch.processing_status}`);
return batch.id;
}
submitBatch().catch(console.error);Step 4: バッチ結果を確認する
async function checkBatchStatus(batchId) {
const batch = await client.beta.batches.retrieve(batchId, {
headers: { 'anthropic-beta': 'files-api-2025-04-14' }
});
console.log(`📊 ステータス: ${batch.processing_status}`);
console.log(`✅ 完了: ${batch.request_counts.succeeded} 件`);
console.log(`❌ 失敗: ${batch.request_counts.failed} 件`);
// 処理中の場合は後で再確認
if (batch.processing_status === 'in_progress') {
console.log('⏳ まだ処理中です。1分後に再確認してください。');
return;
}
// 結果ファイルをダウンロード
if (batch.result_file_id) {
const results = await client.beta.files.retrieveContent(batch.result_file_id, {
headers: { 'anthropic-beta': 'files-api-2025-04-14' }
});
console.log('✅ 結果ファイルを取得しました');
}
}
// バッチID を指定して確認
checkBatchStatus('batch_xxxxx').catch(console.error);バッチ処理の最適化テクニック
① リクエストの粒度を調整する
// ❌ 効率が低い:1リクエスト = 1件分析
{ custom_id: 'review-1', params: { messages: [{ role: 'user', content: 'レビューA を分析' }] } }
// ✅ 効率的:1リクエスト = 複数件分析
{ custom_id: 'batch-1-10', params: { messages: [{ role: 'user', content: 'レビューA〜J を分析\nレビューA: ...\nレビューB: ...' }] } }複数のリクエストを 1 つの API リクエストに詰め込むことで、通信回数を削減できます。
② 同じ分析タスクは最初の 1〜2 件でテストする
// テスト: 10件だけバッチ送信して結果を確認
const testBatch = requests.slice(0, 10);
// → 結果がOK なら、全 1,000 件バッチを送信③ 失敗パターンを事前に検証
不正なフォーマット、トークン超過などのエラーは、本バッチ前に小規模テストで検出しましょう。
全体を振り返ってと次のステップ
Claude API のバッチ処理を使うことで:
- 費用を最大 90% 削減
- 大量リクエストを効率的に処理
- レート制限を気にせず運用
ができるようになります。
バッチ処理は「今すぐ結果が必要」というわけではない、定期的なタスクや研究・分析の場に特に向いています。
さらに詳しく学びたい場合は、Claude API 公式ドキュメントの Batch API セクションや、API 費用最適化ガイドもおすすめです。
まずは小規模なバッチから始めて、本番運用に向けてスケールしていってください!
きっかけは、数千枚の画像に説明文を付ける作業でした
壁紙アプリの素材を整理していたとき、手元には分類待ちの画像が数千枚たまっていました。一枚ずつ Claude に投げて説明文とカテゴリを付けることもできますが、リアルタイムの応答は必要ありません。夜のうちに走らせて、朝に結果が揃っていれば十分です。
こういう「急がないけれど量がある」処理にちょうど合うのが、Claude API の Message Batches です。リクエストをまとめて投げて非同期で処理してもらう仕組みで、通常の API 呼び出しより入出力トークンが 50% 安くなります。私の場合、夜間にまとめておけば、翌朝には全件の結果が手元に届いていました。
以下では単なる API の使い方ではなく、実際にまとまった件数を流したときにつまずいた点と、その回避の仕方を中心にまとめます。
まず最小構成で動かす
最初は 2〜3 件で形を確かめます。custom_id は後で結果を元データに突き合わせるための鍵になるので、必ず意味のある値を入れます。私は画像のファイル名をそのまま使いました。
import anthropic
client = anthropic.Anthropic()
batch = client.messages.batches.create(
requests=[
{
"custom_id": "wallpaper_0001.jpg",
"params": {
"model": "claude-haiku-4-5-20251001",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "この壁紙画像の雰囲気を一語のカテゴリで分類してください: 夜空に淡い光の輪"}
],
},
},
{
"custom_id": "wallpaper_0002.jpg",
"params": {
"model": "claude-haiku-4-5-20251001",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "この壁紙画像の雰囲気を一語のカテゴリで分類してください: 朝もやの中の山並み"}
],
},
},
]
)
print(f"Batch ID: {batch.id}")分類のような単純なタスクでは、無理に上位モデルを使わず Haiku で十分でした。バッチ割引と合わせると、ここでコストがもう一段下がります。
状態の確認は「ポーリングしすぎない」
作成した直後は処理中です。結果が返るのは数分のこともあれば、混雑時は時間単位のこともあります。ここで数秒おきに叩きにいくと無駄が多いので、私は最初の数分を空けてから、間隔を広げて確認するようにしました。
import time
while True:
batch = client.messages.batches.retrieve(batch.id)
if batch.processing_status == "ended":
break
counts = batch.request_counts
print(f"処理中… 成功 {counts.succeeded} / 失敗 {counts.errored} / 処理中 {counts.processing}")
time.sleep(60)
print("バッチ完了")確認したいのは「全部成功したか」ではなく「処理が ended になったか」です。ended になっても、中身には成功と失敗が混在しています。ここを取り違えると、失敗分を取りこぼします。
結果は順番が保証されない — だから custom_id
最初につまずいたのがこれでした。結果は投げた順に返ってくるとは限りません。ストリームで流れてくる一件ずつを、custom_id をキーに元データへ突き合わせる前提で書くのが安全です。
results = {}
for item in client.messages.batches.results(batch.id):
cid = item.custom_id
if item.result.type == "succeeded":
results[cid] = item.result.message.content[0].text
elif item.result.type == "errored":
# ここで握りつぶさず、再投入リストに積む
print(f"失敗: {cid} -> {item.result.error}")
results[cid] = None
elif item.result.type == "expired":
# 24時間以内に処理されなかった分
print(f"期限切れ: {cid}")
results[cid] = None
# custom_id をファイル名にしておいたので、そのまま元画像に紐づく
ok = sum(1 for v in results.values() if v is not None)
print(f"取得 {ok} / {len(results)} 件")result.type には succeeded 以外に errored・expired・canceled があります。expired は 24 時間以内に処理が終わらなかったケースです。実運用では、失敗・期限切れの custom_id だけを集めて、もう一度小さなバッチに分けて投げ直す流れにしておくと、取りこぼしがなくなります。
大量にあるときは分割する
1 つのバッチには最大 10,000 リクエストまで入れられます。私はこれを上限ぎりぎりまで詰めるのではなく、数千件ずつに区切って投げました。理由は単純で、途中で投げ直したくなったときに、巻き戻す単位が小さいほうが扱いやすいからです。ファイル名の連番でチャンクを切り、チャンクごとにバッチ ID をログに残しておくと、後から「どこまで終わったか」が一目で分かります。
def chunk(items, size=2000):
for i in range(0, len(items), size):
yield items[i:i + size]
for n, group in enumerate(chunk(all_requests)):
b = client.messages.batches.create(requests=group)
print(f"chunk {n}: {b.id} ({len(group)}件)")コストの考え方
バッチ割引は入力・出力の両方のトークンに効きます。私が実際に効いたと感じたのは、次の三つを重ねたときでした。
一つ目は、分類や要約のような定型タスクを Haiku に寄せたこと。二つ目は、max_tokens を実際に必要な長さまで絞ったこと。一語のカテゴリを返すだけなら、512 もあれば十分で、ここを大きくしておく理由はありません。三つ目が、急がない処理をすべてバッチに回したことです。リアルタイム性を捨てられる作業を見極めるほど、月末の請求が静かになっていきました。
逆に、ユーザーの操作に同期して即座に返したい処理までバッチに寄せると、体験を損ないます。バッチは「人が待っていない処理」に絞るのが、私なりの線引きです。
宮大工だった祖父は、材料をまとめて寸法を当たってから一気に刻んでいました。一本ずつ場当たりに切るより、段取りを揃えてから流すほうが、結局きれいに早く終わる。バッチ処理を組んでいると、その感覚に近いものを感じます。
次の一手としては、まず手元の「急がない処理」を一つだけ選んで、2〜3 件の最小バッチで往復を確かめてみてください。custom_id を元データのキーに合わせておけば、そのまま件数を増やしていけます。