Agent SDK の構造化出力 outputFormat の使い方

Claude Agent SDK エンジニアリング 6分で読めます

Claude Agent SDK のエージェントは、ふつうは自由形式のテキストで結果を返します。この記事では公式ドキュメント「Get structured output from agents」に沿って、outputFormat(Python では output_format)に JSON Schema を渡し、ツールを使った複数ターンの作業の最後に、スキーマどおりに検証された JSON を受け取る方法を整理します。単発の API 呼び出しでの構造化出力は「Claude API の構造化出力」を参照してください。

構造化出力が役立つ場面

チャットとして読むだけなら自由形式のテキストで十分ですが、結果をプログラムで使うとなると話が変わります。公式ドキュメントはレシピアプリの例を挙げています。エージェントに Web でレシピを探させると、テキストのままでは「調理時間 15分」を数値に変換したり、材料と手順を切り分けたり、回答ごとに揺れる書式に対応したりと、アプリ側で解析する手間がかかります。

構造化出力を使うと、欲しいデータの形(名前・準備時間の分数・材料の配列・手順の配列など)を先に定義でき、その形の型付きデータがそのまま返ってきます。アプリのロジック、データベース、UI コンポーネントに直接渡せるのが利点です。

ポイントは、エージェントが途中で必要なツールを自由に使えることです。検索やファイル読み取り、コマンド実行を何ターン重ねても、最後に受け取るのはスキーマに合った JSON です。SDK は出力をスキーマで検証し、合わなければ再度出力を求めます。リトライの上限までに検証が通らなければ、構造化データではなくエラーの結果になります(後述)。

outputFormat の基本と設定項目

使い方は、データの形を JSON Schema で書き、query() のオプション outputFormat(TypeScript)または output_format(Python)に渡すだけです。エージェントが作業を終えると、結果メッセージ(result メッセージ)の structured_output フィールドに検証済みのデータが入ります。次は Anthropic について調べ、社名・設立年・本社所在地を返させる例です。

import { query } from "@anthropic-ai/claude-agent-sdk";

const schema = {
  type: "object",
  properties: {
    company_name: { type: "string" },
    founded_year: { type: "number" },
    headquarters: { type: "string" }
  },
  required: ["company_name"]
};

for await (const message of query({
  prompt: "Research Anthropic and provide key company information",
  options: {
    outputFormat: { type: "json_schema", schema: schema }
  }
})) {
  if (message.type === "result" && message.subtype === "success" && message.structured_output) {
    console.log(message.structured_output);
  }
}
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

async for message in query(
    prompt="Research Anthropic and provide key company information",
    options=ClaudeAgentOptions(
        output_format={"type": "json_schema", "schema": schema}
    ),
):
    if isinstance(message, ResultMessage) and message.structured_output:
        print(message.structured_output)

オプションに渡すオブジェクトの項目は2つです。

項目内容
type構造化出力では "json_schema" を指定する
schema出力の構造を定義する JSON Schema オブジェクト。Zod の z.toJSONSchema(schema, { target: "draft-7" }) や Pydantic の .model_json_schema() から生成してもよい

スキーマでは基本型(object・array・string・number・boolean・null)、enum、const、required、入れ子のオブジェクト、$ref 定義といった標準的な機能が使えます。細かな制限は Claude API 側の「JSON Schema limitations」が参照先として案内されています。

バージョンによる違いにも注意が必要です。公式ドキュメントによれば、JSON Schema として不正なスキーマは開始時に問題点を示すエラーで実行が失敗します。v2.1.205 より前は不正なスキーマが黙って無視され、構造化されていないテキストが返っていました。また "format": "email" のような format キーワードは注釈として受け付けられるだけで、SDK の検証では強制されません(v2.1.205 より前は format を含むスキーマ全体が不正扱いでした)。Python SDK のリファレンスでも、ClaudeAgentOptions の output_format は {"type": "json_schema", "schema": {...}} の形の dict で渡すと説明されています。

Zod と Pydantic で型安全にする

JSON Schema を手書きする代わりに、TypeScript なら Zod、Python なら Pydantic でスキーマを定義できます。どちらも JSON Schema を生成してくれるうえ、返ってきた値を型付きオブジェクトとして解析できるので、補完や型チェックが効きます。

注意点として、SDK はスキーマを JSON Schema draft-07 で検証するため、より新しいバージョンを宣言したスキーマは拒否されます。Zod は既定で draft 2020-12 を出力するので、変換時に target: "draft-7" を指定します。

import { z } from "zod";
import { query } from "@anthropic-ai/claude-agent-sdk";

const FeaturePlan = z.object({
  feature_name: z.string(),
  summary: z.string(),
  steps: z.array(
    z.object({
      step_number: z.number(),
      description: z.string(),
      estimated_complexity: z.enum(["low", "medium", "high"])
    })
  ),
  risks: z.array(z.string())
});

// SDK が期待する draft-07 で変換する
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });

for await (const message of query({
  prompt: "Plan how to add dark mode support to a React app. Break it into implementation steps.",
  options: { outputFormat: { type: "json_schema", schema } }
})) {
  if (message.type === "result" && message.subtype === "success" && message.structured_output) {
    const parsed = FeaturePlan.safeParse(message.structured_output);
    if (parsed.success) {
      console.log(parsed.data.summary);
    }
  }
}

Python では Pydantic のモデルから model_json_schema() でスキーマを作り、結果を model_validate() で型付きオブジェクトに戻します。

from pydantic import BaseModel
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


class Step(BaseModel):
    step_number: int
    description: str
    estimated_complexity: str  # 'low', 'medium', 'high'


class FeaturePlan(BaseModel):
    feature_name: str
    summary: str
    steps: list[Step]
    risks: list[str]


async for message in query(
    prompt="Plan how to add dark mode support to a React app. Break it into implementation steps.",
    options=ClaudeAgentOptions(
        output_format={"type": "json_schema", "schema": FeaturePlan.model_json_schema()}
    ),
):
    if isinstance(message, ResultMessage) and message.structured_output:
        plan = FeaturePlan.model_validate(message.structured_output)
        print(plan.summary)

公式ドキュメントには、ツールを複数使う例として「コードベースの TODO コメントを Grep で探し、Bash で git blame を引いて追加者を調べる」エージェントも載っています。このとき author と date は required に入れていません。git blame の情報が取れないファイルもあるため、取れた分だけ埋めて残りは省略させる設計です。

StructuredOutput ツール呼び出しの見分け方

outputFormat を設定すると、メッセージのストリームに、自分で定義していない StructuredOutput という名前のツール呼び出しが現れます。これは Claude Code がセッションに追加するツールで、エージェントはこれを呼んで答えを返します。システムの init メッセージの tools 配列にも載り、1回の呼び出しは次の2つのブロックで届きます。

オブジェクトがスキーマに合わない場合は tool_result が is_error: true になり、エージェントは StructuredOutput をもう一度呼びます。そのため1回のクエリで複数の呼び出しが流れることがあります。検証済みのデータは tool_use ブロックからではなく、結果メッセージの structured_output から読みます。

アプリでツール呼び出しを画面表示やログに出している場合は、StructuredOutput の呼び出しを除外すると、この仕組みがエージェントの選んだ作業手順のように見えずに済みます。tool_result ブロックにはツール名がなく、対応する呼び出しの tool_use_id しか入っていないので、除外した呼び出しの id と突き合わせて一緒に外します。ストリームの扱い自体は「Agent SDK のストリーミング出力」を参照してください。

エラー処理と失敗を減らすコツ

スキーマに合う JSON をエージェントが作れないと、構造化出力は失敗します。スキーマが作業に対して複雑すぎる、作業自体があいまい、検証エラーを直そうとしてリトライ上限に達した、といった場合です。検証の失敗がなくても、モデルの自動フォールバックで完成済みの出力がストリームの途中で取り消され、再試行で置き換わらなければ同じエラーで終わることがあります。スキーマを疑う前に、エラー結果の errors リストでどちらの原因かを確認するよう公式は勧めています。

subtype意味
success出力が生成され、検証も通った
error_max_structured_output_retries複数回試しても有効な出力が残らなかった(検証の失敗、または再試行が成功しなかったフォールバックによる取り消し)

Python SDK のリファレンスでは、ResultMessage の subtype は success・error_during_execution・error_max_turns・error_max_budget_usd・error_max_structured_output_retries のいずれかとされています。さらに、subtype が success なのに structured_output が空のまま終わることもあります(エージェントが構造化出力を出さずに完了した場合など)。これも失敗として扱い、「success かつ structured_output がある」ときだけ成功とみなすのが公式の例の書き方です。

async for message in query(
    prompt="Extract contact info from the document",
    options=ClaudeAgentOptions(
        output_format={"type": "json_schema", "schema": contact_schema}
    ),
):
    if isinstance(message, ResultMessage):
        if message.subtype == "success" and message.structured_output:
            print(message.structured_output)
        elif message.subtype == "error_max_structured_output_retries":
            print("Could not produce valid output")
        else:
            print("Run ended without a structured output")

なお単発の query() は、エラー結果を出したあとに例外を投げます。公式の例はループ全体を try で囲み、接続やプロセスの失敗(結果メッセージが出ない場合)も含めて受け止めています。

失敗を減らすコツとして、公式は次の3点を挙げています。

本ページは Get structured output from agents を中心に、Agent SDK reference - Python の記述を踏まえた非公式の日本語解説です(確認日 2026-10-26)。コード例は一部を簡略化しています。最新の仕様は公式ドキュメントを確認してください。