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

Claude API Structured Outputs実践ガイド — JSON出力モードとstrict tool useの使い分けから併用パターンまで

(更新: 2026年03月10日)
Claude APIStructured OutputsTypeScriptAI開発

「JSONで返して」とプロンプトに書いても、閉じ括弧が欠けたり余計なマークダウンが混入したり——LLMアプリ開発者なら一度は経験する"パース地獄"。Claude APIのStructured Outputsは、constrained decodingによってスキーマ準拠を100%保証する機能だ。2026年1月のGA化で本番投入の準備が整った今、output_config.formatstrict tool useという2つのアプローチを実装コード付きで比較し、両者を組み合わせたエージェントワークフローまで解説する。

Structured Outputsとは何か — なぜ「プロンプトでJSON指定」では不十分なのか

従来のJSON出力の3つの落とし穴

プロンプトに「JSONで出力してください」と指示する従来の方法には、3つの典型的な問題がある。

  1. 閉じ括弧の欠損 — 出力が長くなるとmax_tokensに達し、JSONが途中で切れる
  2. マークダウン混入 `json で囲んだり、前後に説明テキストを付けたりする
  3. スキーマ逸脱 — 指定したフィールド名と異なるキーを返す、型が合わない
typescript
// Before: プロンプト頼みのJSON出力 — パースが壊れるリスクが常にある
const response = await client.messages.create({
  model: "claude-sonnet-4-5-20250929",
  max_tokens: 1024,
  messages: [{ role: "user", content: "ユーザー情報をJSON形式で返して" }],
});
const text = response.content[0].text;
const data = JSON.parse(text); // ここで落ちる可能性がある

constrained decodingによるスキーマ保証の仕組み

Structured Outputsは、トークン生成時にスキーマに違反する選択肢を排除するconstrained decodingで動作する。モデルの後処理ではなく生成プロセス自体に制約をかけるため、stop_reasonend_turnであればスキーマ準拠が保証される。

2025年11月にPublic Betaとして登場し、2026年1月29日にClaude APIでGA化された。ベータ期間中に必要だったヘッダー(structured-outputs-2025-11-13)はもう不要だ。対応モデルはClaude Opus 4.6、Sonnet 4.6、Sonnet 4.5、Opus 4.5、Haiku 4.5。Amazon Bedrockでは2026年2月4日にGA化されており、Microsoft Foundryではpublic betaとなっている。

アプローチ1: output_config.formatによるJSON出力モード

基本実装 — TypeScript SDK + Zodスキーマ

output_config.formatにJSON Schemaを渡すと、モデルの最終出力が必ずそのスキーマに準拠したJSONになる。TypeScript SDKではzodOutputFormatヘルパーを使えば、Zodスキーマから自動でJSON Schemaに変換される。

typescript
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";

const client = new Anthropic();

// Zodでスキーマを定義
const UserSchema = z.object({
  name: z.string(),
  email: z.string(),
  age: z.number(),
  interests: z.array(z.string()),
});

const response = await client.messages.parse({
  model: "claude-sonnet-4-5-20250929",
  max_tokens: 1024,
  output_config: { format: zodOutputFormat(UserSchema) },
  messages: [
    {
      role: "user",
      content:
        "田中太郎、30歳、メールはtanaka@example.com、趣味はプログラミングと登山",
    },
  ],
});

// response.parsed_output は型安全(z.infer<typeof UserSchema> 型)
console.log(response.parsed_output.name); // "田中太郎"
console.log(response.parsed_output.interests); // ["プログラミング", "登山"]

client.messages.parse()を使うと、レスポンスのparsed_outputプロパティから型付きのオブジェクトを直接取得できる。JSON.parseを自分で呼ぶ必要はない。

outputformatからoutputconfig.formatへの移行

GA化に伴い、パラメータ名がoutput_formatからoutput_config.formatに変更された。旧パラメータは移行期間中は動作するが、Amazon Bedrockでは既に旧パラメータを拒否するため、早めの移行を推奨する。SDKを最新版に更新すれば、内部的に新パラメータが使われる。

注意すべき点として、stop_reasonmax_tokensの場合はJSONが途中切断されている可能性がある。必ずチェックしよう。

typescript
if (response.stop_reason !== "end_turn") {
  throw new Error("JSON出力が途中で切断された可能性があります");
}

また、JSON Schemaの制約として、全てのobjectレベルでadditionalProperties: falseが必須だ。Zodスキーマ経由なら自動付与されるが、手書きのJSON Schemaでは省略するとエラーになる。

アプローチ2: strict tool useによるツールパラメータの型保証

strict: trueの基本実装

ツール定義にstrict: trueを追加するだけで、モデルが生成するツール呼び出しのパラメータがスキーマ準拠になる。正直、この手軽さには驚いた。

typescript
const response = await client.messages.create({
  model: "claude-sonnet-4-5-20250929",
  max_tokens: 1024,
  tools: [
    {
      name: "extract_user_info",
      description: "テキストからユーザー情報を抽出する",
      strict: true, // これだけで型保証が有効になる
      input_schema: {
        type: "object" as const,
        properties: {
          name: { type: "string", description: "氏名" },
          email: { type: "string", description: "メールアドレス" },
          age: { type: "number", description: "年齢" },
        },
        required: ["name", "email", "age"],
        additionalProperties: false,
      },
    },
  ],
  tool_choice: { type: "any" },
  messages: [
    {
      role: "user",
      content: "山田花子さん(25歳)の連絡先はhanako@example.comです",
    },
  ],
});

tool_choice: 'any'との組み合わせ

tool_choice: { type: "any" }を指定すると、モデルは必ずツール呼び出しで応答する。テキスト応答を防げるため、構造化データの抽出用途ではoutput_config.formatの代替として使える。既存のtool useコードからの移行はstrict: trueを1行追加するだけだ。

2つのアプローチの使い分け判断フロー

観点 output_config.format strict tool use
保証対象 モデルの最終テキスト出力 ツール呼び出しのパラメータ
主なユースケース データ抽出・分類・構造化レスポンス 関数呼び出し・外部API連携
レイテンシ 初回にgrammar compilationあり 同様
適用場面 会話の最終応答をJSONで受け取りたい エージェントのアクション入力を保証したい

両者は排他ではなく併用可能だ。次節でその実装パターンを解説する。

併用パターン — エージェントワークフローでツール呼び出しも最終出力も型保証する

実装例: 調査→レポート生成エージェント

個人的に最も実用的だと感じるのが、strict tool useで中間のツール呼び出しを保証しつつ、output_config.formatで最終出力もJSON保証する併用パターンだ。

typescript
import Anthropic from "@anthropic-ai/sdk";
import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod";
import { z } from "zod";

const client = new Anthropic();

// 最終出力のスキーマ
const ReportSchema = z.object({
  title: z.string(),
  summary: z.string(),
  findings: z.array(
    z.object({
      topic: z.string(),
      detail: z.string(),
      confidence: z.number(),
    })
  ),
  recommendation: z.string(),
});

// ツール定義(strict: trueで入力パラメータも保証)
const tools: Anthropic.Messages.Tool[] = [
  {
    name: "web_search",
    description: "Webで情報を検索する",
    strict: true,
    input_schema: {
      type: "object" as const,
      properties: {
        query: { type: "string", description: "検索クエリ" },
        max_results: { type: "number", description: "最大件数" },
      },
      required: ["query", "max_results"],
      additionalProperties: false,
    },
  },
];

// エージェントループ
let messages: Anthropic.Messages.MessageParam[] = [
  { role: "user", content: "2026年のTypeScriptフレームワーク動向を調査して" },
];

while (true) {
  const response = await client.messages.parse({
    model: "claude-sonnet-4-5-20250929",
    max_tokens: 4096,
    tools,
    output_config: { format: zodOutputFormat(ReportSchema) },
    messages,
  });

  // ツール呼び出しがあれば処理して続行
  const toolUse = response.content.find((b) => b.type === "tool_use");
  if (toolUse && toolUse.type === "tool_use") {
    const result = await executeSearch(toolUse.input); // 実際のツール実行
    messages.push({ role: "assistant", content: response.content });
    messages.push({
      role: "user",
      content: [
        { type: "tool_result", tool_use_id: toolUse.id, content: result },
      ],
    });
    continue;
  }

  // 最終出力 — ReportSchema準拠が保証されている
  console.log(response.parsed_output.title);
  console.log(response.parsed_output.findings);
  break;
}

この構成では、web_searchツールのパラメータ(querymax_results)はstrict: trueで型保証され、最終的なレポート出力はReportSchemaで構造が保証される。エージェントワークフロー全体で型安全性を担保できる。

本番投入時の注意点 — レイテンシ・ZDR・スキーマ制約

grammar compilation latencyとキャッシュ戦略

Structured Outputsでは、初回リクエスト時にJSON Schemaからgrammarをコンパイルする処理が入る。このコンパイル結果は24時間キャッシュされるため、2回目以降は追加レイテンシが発生しない。コンパイルのタイムアウトは180秒で、巨大なスキーマを使う場合は分割を検討すべきだ。

JSON Schemaの制約事項

制約 詳細
トップレベル型 objectのみ(配列やプリミティブは不可)
additionalProperties 全objectでfalseが必須
$ref 内部参照($defs)は利用可能。外部URLの参照は不可
再帰スキーマ $ref/$defsによる限定的な再帰は対応。循環的(cyclic)な再帰定義は非対応
数値制約 minimum/maximum/multipleOfは非対応
文字列制約 minLength/maxLengthは非対応

ZDR環境での挙動

Zero Data Retention環境では、プロンプトとレスポンスは保持されない。ただし、JSON Schema自体はgrammar compilationの最適化目的で最大24時間キャッシュされる点に留意が必要だ。スキーマにセンシティブな情報(社内用語のenum値など)を含める場合は、このキャッシュ挙動を把握しておこう。


Structured Outputsの登場により、LLMアプリ開発における「パース不安定性」は解決済みの問題になった。output_config.formatで最終出力を、strict tool useでツール呼び出しを、それぞれ型保証できる。両者を併用すれば、エージェントワークフロー全体で入出力の型安全性を担保できる。GA化済みの今こそ、本番コードのJSON処理を正規表現やリトライ頼みからStructured Outputsへ移行するタイミングだ。

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