メインコンテンツへスキップ
ブログ一覧

Claude API Message Batches実践ガイド — 300k出力×バッチ処理で大規模コード生成・データ抽出を自動化する

(更新: 2026年04月11日)
Claude APIバッチ処理大規模コード生成コスト最適化自動化

Message Batches APIとは — 同期APIとの違いを30秒で理解する

Claude APIには、通常の同期Messages APIとは別に、大量リクエストを非同期で一括処理するMessage Batches APIが用意されている。この機能は、リアルタイム応答が不要なワークロードに対して、コスト50%削減という強力なインセンティブを提供する。

同期Messages API vs バッチAPI:処理モデルの根本的な違い

同期APIは1リクエスト1レスポンスの即時応答モデル。対してバッチAPIは「ジョブ投入 → ポーリング → 結果取得」の3ステップで動作する。レスポンスはリアルタイムではなく、処理完了まで最大24時間かかる(実測では大半が1時間以内に終わる)。

typescript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

// --- 同期API: 即座にレスポンスが返る ---
const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello" }],
});

// --- バッチAPI: ジョブを投入し、後から結果を取得する ---
const batch = await client.messages.batches.create({
  requests: [
    {
      custom_id: "req-001",
      params: {
        model: "claude-sonnet-4-6",
        max_tokens: 1024,
        messages: [{ role: "user", content: "Hello" }],
      },
    },
    // ... 最大100,000リクエストまで投入可能
  ],
});

1バッチあたりの上限は最大100,000リクエストまたは256MB。結果はバッチ作成から29日間ダウンロード可能で、ストリーミングには非対応だが途中キャンセルは可能(未処理分のみキャンセルされる)。

いつバッチを選ぶべきか — 判断フローチャート

以下のいずれかに該当すれば、バッチAPIの採用を検討する価値がある。

  1. リアルタイム応答が不要 — チャットUIではなくバックエンド処理
  2. 10件以上の同種タスク — 同じsystem promptで大量のリクエストを処理する
  3. 出力が64kトークンを超える — 同期APIの出力上限に収まらない
  4. Extended Thinkingのbudget_tokensが32k超(旧モデル使用時) — 同期APIではタイムアウトリスクがある

いずれにも該当しなければ、同期APIで十分だ。


output-300kベータで超長文出力を解放する

2026年3月に追加されたoutput-300kベータは、バッチAPIの実用性を一段階引き上げる機能だ。

対応モデルと有効化方法

ベータヘッダー anthropic-beta: output-300k-2026-03-24 をリクエストに付与するだけで有効化できる。対応モデルは以下の2つのみ。

モデル 通常の最大出力 output-300k有効時
Claude Opus 4.6 128kトークン 300kトークン
Claude Sonnet 4.6 64kトークン 300kトークン

重要: この機能はBatch API専用であり、同期Messages APIでは使用できない。 正直、最初にドキュメントを読んだときはこの制約を見落としていた。同期APIでmax_tokens: 300000を指定してもエラーになるので注意してほしい。

typescript
const batch = await client.messages.batches.create({
  requests: [
    {
      custom_id: "long-output-001",
      params: {
        model: "claude-opus-4-6",
        max_tokens: 300000,
        messages: [
          {
            role: "user",
            content: "リポジトリ全体のコードレビューレポートを生成してください...",
          },
        ],
      },
    },
  ],
  betas: ["output-300k-2026-03-24"],
});

300kトークンが活きるユースケース

  • 大規模コードベースの一括レビューレポート生成
  • 数十ページ規模のドキュメント・マニュアルの一括生成
  • 構造化データ(JSON/CSV)の大量抽出・変換

300kトークンは日本語で約15〜20万文字に相当する。書籍1冊分に近い分量を1リクエストで出力できる計算だ。


Extended Thinking併用時の設計指針

バッチAPIとExtended Thinkingの組み合わせは、複雑な推論を伴う大量タスクに適している。

adaptive thinkingとeffortパラメータの使い方

Claude Opus 4.6 / Sonnet 4.6では、従来のbudget_tokens非推奨(deprecated)になっている。代わりにthinking: {type: 'adaptive'}effortパラメータを使用する。

typescript
const batch = await client.messages.batches.create({
  requests: [
    {
      custom_id: "thinking-001",
      params: {
        model: "claude-opus-4-6",
        max_tokens: 300000,
        thinking: {
          type: "adaptive",
        },
        // effortはAPIレベルまたはリクエスト単位で指定可能
        messages: [
          {
            role: "user",
            content: "以下のコードのセキュリティ脆弱性を網羅的に分析して...",
          },
        ],
      },
    },
  ],
  betas: ["output-300k-2026-03-24"],
});

budget_tokens非推奨化への対応

旧モデル(Claude 3.5系など)でbudget_tokensを32k超に設定する場合は、同期APIではコネクション維持のタイムアウトリスクがあるため、バッチAPIの使用が公式に推奨されている。

バッチAPIならコネクション維持が不要なため、長時間のthinking処理と長文出力の組み合わせが安定して動作する。なお、budget_tokens(thinking用)とmax_tokens(最終応答テキスト用)はそれぞれ独立したトークン予算であり、budget_tokensmax_tokens未満に設定する必要がある。課金上、thinkingトークンは出力トークンとしてカウントされる。


実装: バッチジョブの投入・監視・結果取得

ここからは、プロダクションで使える実装パターンを解説する。

バッチの作成とcustom_idによるリクエスト追跡

custom_idはリクエストとレスポンスを紐付けるためのキーであり、設計上の最重要ポイントだ。ファイル名やタスクIDを埋め込むことで、結果取得時にどのリクエストに対する応答かを即座に特定できる。

ポーリングによるステータス監視

バッチのステータスはin_progressからendedに遷移する。終了理由は正常終了・期限切れ・キャンセルの3種がある。

結果の取得とエラーハンドリング

個別リクエストの結果タイプはsucceeded / errored / expired / canceledの4種。バッチ全体が正常終了しても、個別リクエストがexpiredやerroredになるケースがあるため、必ず個別ステータスのチェックが必要だ。

以下に、投入からリトライまでの完全な実装例を示す。

typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// --- 1. バッチ投入 ---
const batch = await client.messages.batches.create({
  requests: tasks.map((task) => ({
    custom_id: task.id, // ファイル名やタスクIDを使う
    params: {
      model: "claude-sonnet-4-6",
      max_tokens: 8192,
      system: SHARED_SYSTEM_PROMPT, // 共通化してPrompt Cachingを効かせる
      messages: [{ role: "user", content: task.prompt }],
    },
  })),
});

// --- 2. ポーリングで完了を待つ ---
let status = batch;
while (status.processing_status === "in_progress") {
  await new Promise((r) => setTimeout(r, 60_000)); // 60秒間隔
  status = await client.messages.batches.retrieve(batch.id);
  console.log(
    `進捗: ${status.request_counts.succeeded}/${status.request_counts.processing}`
  );
}

// --- 3. 結果取得 ---
const results: Record<string, string> = {};
const failed: Array<{ custom_id: string; error: unknown }> = [];

for await (const result of client.messages.batches.results(batch.id)) {
  if (result.result.type === "succeeded") {
    const text = result.result.message.content
      .filter((b) => b.type === "text")
      .map((b) => b.text)
      .join("");
    results[result.custom_id] = text;
  } else {
    failed.push({ custom_id: result.custom_id, error: result.result });
  }
}

// --- 4. エラー分のみリトライバッチ投入 ---
if (failed.length > 0) {
  const retryRequests = failed.map((f) => {
    const original = tasks.find((t) => t.id === f.custom_id)!;
    return {
      custom_id: `retry-${original.id}`,
      params: {
        model: "claude-sonnet-4-6",
        max_tokens: 8192,
        system: SHARED_SYSTEM_PROMPT,
        messages: [{ role: "user" as const, content: original.prompt }],
      },
    };
  });
  const retryBatch = await client.messages.batches.create({
    requests: retryRequests,
  });
  console.log(`リトライバッチ作成: ${retryBatch.id} (${failed.length}件)`);
}

エラーになったリクエストだけを抽出して新バッチとして再投入するパターンは、大量処理時の耐障害性を大幅に向上させる。


コスト比較 — 同期API vs バッチAPI × Prompt Caching

モデル別料金表

バッチAPIは一律50%割引が適用される。

モデル 入力(同期) 入力(バッチ) 出力(同期) 出力(バッチ)
Opus 4.6 $5.00/MTok $2.50/MTok $25.00/MTok $12.50/MTok
Sonnet 4.6 $3.00/MTok $1.50/MTok $15.00/MTok $7.50/MTok

Prompt Cachingとの割引重複

Prompt Cachingとバッチ割引は重複適用される。キャッシュヒット時の入力は90%割引、さらにバッチの50%割引が乗る。

具体例: 1,000ファイル × 平均2kトークン入力 × 5kトークン出力をSonnet 4.6で処理した場合:

方式 入力コスト 出力コスト 合計
同期API $6.00 $75.00 $81.00
バッチAPI $3.00 $37.50 $40.50
バッチ + Cache $0.30 $37.50 $37.80

バッチ化だけで半額、Prompt Cachingを併用すれば同期比で約53%削減になる。レイテンシを許容できるワークロードなら、使わない理由がない。

typescript
// Prompt Caching併用: system promptにcache_controlを設定
const requests = files.map((file) => ({
  custom_id: file.name,
  params: {
    model: "claude-sonnet-4-6",
    max_tokens: 8192,
    system: [
      {
        type: "text" as const,
        text: SHARED_REVIEW_PROMPT, // 全リクエスト共通の長いプロンプト
        cache_control: { type: "ephemeral" as const },
      },
    ],
    messages: [{ role: "user" as const, content: file.content }],
  },
}));

実用シナリオ: 大量ファイル一括コードレビュー

ここまでの要素を組み合わせたEnd-to-Endの実用例として、リポジトリ内の全TypeScriptファイルを一括でコードレビューするスクリプトを紹介する。

typescript
import Anthropic from "@anthropic-ai/sdk";
import { readFileSync, writeFileSync } from "fs";
import { globSync } from "glob";

const client = new Anthropic();
const MAX_BATCH_SIZE = 100_000;

const REVIEW_PROMPT = `あなたはシニアTypeScriptエンジニアです。
以下のコードについて、バグ・セキュリティリスク・パフォーマンス問題・可読性改善を指摘してください。
重要度(Critical/Warning/Info)を付けて箇条書きで出力してください。`;

// 1. ファイル一覧を取得
const files = globSync("src/**/*.ts").map((path) => ({
  name: path,
  content: readFileSync(path, "utf-8"),
}));

// 2. バッチサイズ上限を考慮して分割
const chunks: typeof files[] = [];
for (let i = 0; i < files.length; i += MAX_BATCH_SIZE) {
  chunks.push(files.slice(i, i + MAX_BATCH_SIZE));
}

// 3. 各チャンクをバッチ投入 → 結果取得 → マージ
const allResults: Record<string, string> = {};

for (const chunk of chunks) {
  const batch = await client.messages.batches.create({
    requests: chunk.map((file) => ({
      custom_id: file.name,
      params: {
        model: "claude-sonnet-4-6",
        max_tokens: 4096,
        system: [
          {
            type: "text" as const,
            text: REVIEW_PROMPT,
            cache_control: { type: "ephemeral" as const },
          },
        ],
        messages: [
          {
            role: "user" as const,
            content: `ファイル: ${file.name}\n\n\`\`\`typescript\n${file.content}\n\`\`\``,
          },
        ],
      },
    })),
  });

  // ポーリング(前述のパターンと同様)
  let status = batch;
  while (status.processing_status === "in_progress") {
    await new Promise((r) => setTimeout(r, 60_000));
    status = await client.messages.batches.retrieve(batch.id);
  }

  for await (const result of client.messages.batches.results(batch.id)) {
    if (result.result.type === "succeeded") {
      const text = result.result.message.content
        .filter((b) => b.type === "text")
        .map((b) => b.text)
        .join("");
      allResults[result.custom_id] = text;
    }
  }
}

// 4. Markdownレポートに集約
const report = Object.entries(allResults)
  .map(([file, review]) => `## ${file}\n\n${review}`)
  .join("\n\n---\n\n");

writeFileSync("code-review-report.md", `# コードレビューレポート\n\n${report}`);

system promptを全リクエストで共通化することで、Prompt Cachingの恩恵を最大化している点がポイントだ。このパターンは自律開発デーモンとも相性が良く、cronでバッチを投入し、完了後に結果を次のジョブに渡すような設計が自然にできる。


ハマりポイントと運用Tips

実際に運用する中で遭遇しやすい問題をまとめておく。

  • output-300kは同期APIでは使えない — これは地味に引っかかる。ベータヘッダーを付けても同期APIではmax_tokensの上限はモデルのデフォルト値のまま。バッチAPI専用であることを忘れないこと。

  • バッチ全体が成功でも個別リクエストがexpiredになる — 24時間の処理期限内に完了しなかったリクエストはexpiredになる。全体のprocessing_statusendedであっても、個別のresult.typeは必ずチェックすること。

  • max_tokensを大きくしすぎるとコストが跳ねるmax_tokensはあくまで上限値であり、実際の出力長に応じて課金される。しかし、必要以上に大きな値を設定すると、モデルが不必要に長い出力を生成する傾向がある。まずは小さめの値で実測し、実際の出力トークン数を把握してから調整するのが賢明だ。

  • 256MB制限に注意 — リクエスト本文が大きい場合(長大なコードを入力に含めるケースなど)、100,000リクエスト上限より先にこの制限に到達することがある。事前にペイロードサイズを見積もり、必要に応じて複数バッチに分割する。

  • 結果の29日保持期限 — バッチ作成から29日を過ぎると結果を取得できなくなる。処理完了のポーリングが済んだら、速やかにダウンロード・保存すること。長期保存が必要な場合はS3やローカルストレージへの自動保存を組み込んでおくとよい。


まとめ

Message Batches APIは「大量×非リアルタイム」処理に特化したClaude APIの強力なオプションだ。50%のコスト削減だけでなく、output-300kによる出力長制限の解放、Extended Thinkingの安定動作、Prompt Cachingとの割引重複など、同期APIにはない実用上のメリットが多い。

まずは既存の同期処理のうち「リアルタイム応答が不要なもの」を洗い出し、バッチ化の候補を見つけるところから始めてみてほしい。コードレビューの自動化、ドキュメント一括生成、データ変換パイプラインなど、バッチ処理が自然にフィットするユースケースは想像以上に多いはずだ。

もっと読む他の技術記事も読む