【Claude Code 連載 第98回】エージェントから構造化された出力を受け取る(agent-sdk/structured-outputs)

スポンサーリンク
【Claude Code 連載 第98回】エージェントから構造化された出力を受け取る(agent-sdk/structured-outputs) 用語解説
【Claude Code 連載 第98回】エージェントから構造化された出力を受け取る(agent-sdk/structured-outputs)
この記事は約33分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/structured-outputs

1. 一言でいうと

エージェントの最終的な答えを、自由な文章ではなく、自分で決めた形(JSON Schema)に沿った JSON で受け取る機能です。エージェントは途中でツールを何回使ってもよく、最後に返す結果だけがスキーマに合っているかを SDK が検証します。合わなければ SDK が作り直しを求め、決められた回数のうちに合わなければ、結果は構造化データではなくエラーになります。TypeScript なら Zod、Python なら Pydantic でスキーマを書けば、受け取った結果を型の付いたオブジェクトとして扱えます。

第97回のトラブルシューティングで「結果が success なのに structured_output が空のことがあり、失敗として扱う」と見ました。今回はその機能の本体です。


2. どういう場面で役立つか

シーン1:エージェントの答えをそのままアプリの画面やデータベースに渡す

原文の例は、Web を検索してレシピを取ってくるレシピアプリです。構造化出力が無いと、エージェントは「これは古典的なチョコレートチップクッキーのレシピです!」で始まる文章を返し、アプリ側でタイトルを切り出し、「15 分」を数値に直し、材料と手順を分け、毎回少しずつ違う書き方に対応しなければなりません。構造化出力を使えば、"prep_time_minutes": 15 のような型の付いたデータがそのまま手に入ります。

シーン2:ツールを何度も使う調査の結果を1つの表にまとめる

原文の TODO の例では、エージェントが Grep でコード中の TODO コメントを探し、Bash で git blame を調べ、その結果を「TODO の一覧と件数」という1つの JSON にまとめて返します。途中の手順はエージェントに任せたまま、出口の形だけを固定できるのがこの機能の要点です。

シーン3:計画やレビューの結果を後続の処理に流す

原文の Zod/Pydantic の例は、機能の実装計画を「概要・手順(各手順に難しさの評価)・リスク」の形で返させます。手順ごとにチケットを作る、難しさが high のものだけ人に回す、といった後続の処理を、文章の解析なしに書けます。

不要・向かないケース

  • 人が読むだけの答え:チャットのように人が読む用途なら、自由な文章で足ります。原文も「デフォルトの自由形式のテキストはチャットには機能する」と書いています。
  • ツールを使わない1回きりの問い合わせ:原文の関連リソースは、ツール使用のない単一ターンのリクエストなら、Claude API の構造化出力を直接使うよう案内しています。エージェントのループが要らないなら、SDK を通す必要はありません。
  • 答えに必要な情報が手に入らないかもしれないのに、すべてを必須にしたい:満たせないスキーマは失敗の原因になります(3-5)。どうしても全項目が必要なら、構造化出力より前に、情報が揃うかどうかの設計を見直す必要があります。

3. コードと仕組みの解説

原文の fenced コードブロックは10本です(構造化出力の有無を比べる出力例2本と、クイックスタート・Zod/Pydantic・TODO エージェント・エラーハンドリングの、TypeScript と Python の対訳4組8本)。どれも内容が違うので全数を引用します。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。最初の2本の出力例にある ... や // ...・/* ... */ は、原文が「以下省略」の意味で置いているものです。

3-0. バージョン要件

変化バージョン
有効な JSON Schema でないスキーマは、起動時に問題を示すエラーで実行が失敗する(それより前は黙って無視され、エージェントは構造化されていないテキストを返していた)v2.1.205 以降
"format": "email" のような format キーワードを注釈として受け付ける(強制はしない)。それより前は format を含むスキーマが無効として扱われていたv2.1.205 以降

1行目は、古い版ではスキーマの書き間違いに気づけなかったということです。v2.1.205 より前の SDK(同梱の CLI)を使っている場合、「構造化出力が返ってこない」原因がスキーマの誤りである可能性を疑う必要があります(筆者の指摘)。

3-1. 構造化出力が無い場合とある場合

原文は、レシピアプリの例で違いを示しています。まず構造化出力が無い場合の、エージェントの自由な文章です。

これは古典的なチョコレートチップクッキーのレシピです!

**チョコレートチップクッキー**
準備時間:15 分 | 調理時間:10 分

材料:
- 2 1/4 カップ 薄力粉
- 1 カップ バター、柔らかくしたもの
...

これをアプリで使うには、タイトルを解析し、「15 分」を数値に変え、材料と手順を分け、応答ごとにばらつく書き方に対応する必要があります。

構造化出力がある場合は、次のような型の付いたデータになります。

{
  "name": "Chocolate Chip Cookies",
  "prep_time_minutes": 15,
  "cook_time_minutes": 10,
  "ingredients": [
    { "item": "all-purpose flour", "amount": 2.25, "unit": "cups" },
    { "item": "butter, softened", "amount": 1, "unit": "cup" }
    // ...
  ],
  "steps": ["Preheat oven to 375°F", "Cream butter and sugar" /* ... */]
}

UI でそのまま使えるデータです。分量が 2.25 のように数値になり、材料が item・amount・unit に分かれている点が、文章との違いをよく表しています。

3-2. クイックスタート

使い方は、欲しいデータの形を表す JSON Schema を定義し、query() のオプションに渡すだけです。オプション名は TypeScript が outputFormat、Python が output_format です。エージェントが終わると、結果メッセージの 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"]
};

try {
  for await (const message of query({
    prompt: "Research Anthropic and provide key company information",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: schema
      }
    }
  })) {
    // 結果メッセージには検証済みデータを含む structured_output が含まれます
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      console.log(message.structured_output);
      // { company_name: "Anthropic", founded_year: 2021, headquarters: "San Francisco, CA" }
    }
  }
} catch (error) {
  // 単一ショットの query() はエラー結果(error_max_structured_output_retries など)を生成した後にスローされます。エラーハンドリングセクションを参照してください。
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# 返してほしいデータの形状を定義
schema = {
    "type": "object",
    "properties": {
        "company_name": {"type": "string"},
        "founded_year": {"type": "number"},
        "headquarters": {"type": "string"},
    },
    "required": ["company_name"],
}


async def main():
    try:
        async for message in query(
            prompt="Research Anthropic and provide key company information",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": schema}
            ),
        ):
            # 結果メッセージには検証済みデータを含む structured_output が含まれます
            if isinstance(message, ResultMessage) and message.structured_output:
                print(message.structured_output)
                # {'company_name': 'Anthropic', 'founded_year': 2021, 'headquarters': 'San Francisco, CA'}
    except Exception as error:
        # 単一ショットの query() はエラー結果(error_max_structured_output_retries など)を生成した後にレイズされます。エラーハンドリングセクションを参照してください。
        print(f"Session ended with an error: {error}")


asyncio.run(main())

読み取りのポイントです。

  • スキーマの required は company_name だけです。founded_year と headquarters は、見つからなければ省略されてよい項目です。
  • TypeScript の例は message.type === "result"、message.subtype === "success"、message.structured_output の3つを確かめています。 一方、Python の例は isinstance(message, ResultMessage) と message.structured_output の2つだけで、subtype を見ていません。
  • どちらも query() を try で包んでいます。コメントにあるとおり、1回きりの query() は、error_max_structured_output_retries のようなエラーの結果を出した後に例外を送出するからです(第94回・第97回と同じ動き)。

2点目について、原文自身がエラーハンドリングの節(3-5)で「subtype が success で、かつ structured_output があるときだけ成功として扱う」よう求めています。Python のクイックスタートの書き方は、最小の例としては動きますが、本番ではエラーハンドリングの節の書き方に合わせるべきです(筆者の指摘)。

3-3. Zod と Pydantic で型安全にする

JSON Schema を手で書く代わりに、TypeScript なら Zod、Python なら Pydantic でスキーマを定義できます。これらのライブラリは JSON Schema を生成でき、受け取った応答を型の付いたオブジェクトに変換できるので、補完や型チェックを効かせたままコード全体で使えます。

ここで1つ注意があります。SDK はスキーマを JSON Schema の draft-07 で検証するので、より新しい版を宣言したスキーマは拒否されます。Zod は既定で draft 2020-12 を対象にするので、変換するときに target: "draft-7" を渡す必要があります。

次の例は、機能の実装計画(概要、手順の一覧(各手順に難しさの評価)、想定されるリスク)のスキーマを定義し、React アプリにダークモードを加える計画を立てさせます。

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

// Define schema with Zod
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())
});

type FeaturePlan = z.infer<typeof FeaturePlan>;

// Convert to JSON Schema using the draft-07 target the SDK expects
const schema = z.toJSONSchema(FeaturePlan, { target: "draft-7" });

// Use in query
try {
  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: schema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      // Validate and get fully typed result
      const parsed = FeaturePlan.safeParse(message.structured_output);
      if (parsed.success) {
        const plan: FeaturePlan = parsed.data;
        console.log(`Feature: ${plan.feature_name}`);
        console.log(`Summary: ${plan.summary}`);
        plan.steps.forEach((step) => {
          console.log(`${step.step_number}. [${step.estimated_complexity}] ${step.description}`);
        });
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result, such as
  // error_max_structured_output_retries; see the Error handling section.
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
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 def main():
    try:
        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:
                # Validate and get fully typed result
                plan = FeaturePlan.model_validate(message.structured_output)
                print(f"Feature: {plan.feature_name}")
                print(f"Summary: {plan.summary}")
                for step in plan.steps:
                    print(
                        f"{step.step_number}. [{step.estimated_complexity}] {step.description}"
                    )
    except Exception as error:
        # A single-shot query() raises after yielding an error result, such as
        # error_max_structured_output_retries; see the Error handling section.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

言語ごとの違いを整理します。

TypeScript(Zod)Python(Pydantic)
スキーマの定義z.object で定義し、z.infer で型も作るBaseModel を継承したクラス
JSON Schema への変換z.toJSONSchema(FeaturePlan, { target: "draft-7" })FeaturePlan.model_json_schema()
受け取った結果の検証FeaturePlan.safeParse で検証。失敗しても例外にならず parsed.success で分岐FeaturePlan.model_validate で検証。合わなければ例外
難しさの値z.enum(["low", "medium", "high"]) でスキーマ上も3値に制限estimated_complexity: str で、3値はコメントに書いてあるだけ

表は筆者の整理です。4行目が見落としやすい違いです。Zod の例は難しさを3つの値に制限していますが、Pydantic の例は型が str なので、スキーマとしてはどんな文字列でも通ります。 SDK の検証もそのスキーマに対して行われるので、Python の例では "very high" のような値が来ても検証で弾かれません。値を絞りたいなら、Pydantic 側でも列挙を表す型で定義する必要があります(筆者補足。原文は Pydantic 側の書き方を示していません)。

また、原文が draft の版について注意しているのは Zod だけです。Pydantic が生成するスキーマについては触れていないので、Python で拒否のエラーが出た場合は、生成されたスキーマが宣言している版を確かめるのが手がかりになるでしょう(筆者の指摘)。

3-4. 出力形式の設定

outputFormat(TypeScript)/output_format(Python)は、次の2つを持つオブジェクトを受け取ります。

キー中身
type構造化出力なら "json_schema"
schema出力の構造を定める JSON Schema のオブジェクト。Zod から z.toJSONSchema(schema, { target: "draft-7" })、Pydantic から .model_json_schema() で作れる

SDK は、標準的な JSON Schema の機能に対応しています。具体的には、すべての基本型(object、array、string、number、boolean、null)、enum、const、required、入れ子のオブジェクト、$ref による定義です。対応している機能と制限の全一覧は、Claude Platform 側の「JSON Schema の制限」にあります。

format については 3-0 のとおりで、v2.1.205 以降は注釈として受け付けるだけで、SDK のバリデーターは強制しません。つまり "format": "email" と書いても、メールアドレスの形かどうかは検証されません。形式の検証が必要なら、受け取った後にアプリ側(Zod や Pydantic の検証など)で行う必要があります(筆者の指摘)。

3-5. 例:TODO を追跡するエージェント

この例は、ツールを何段階も使う作業で構造化出力がどう働くかを示します。エージェントはコードベースの TODO コメントを探し、それぞれの git blame の情報を調べる必要があります。どのツールを使うか(検索に Grep、git コマンドに Bash)はエージェントが自分で決め、結果を1つの構造化された応答にまとめます。

スキーマには、すべてのファイルで git blame の情報が取れるとは限らないので、任意の項目(author と date)が入っています。エージェントは見つかったものを埋め、残りは省きます。

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

// TODO 抽出用の構造を定義
const todoSchema = {
  type: "object",
  properties: {
    todos: {
      type: "array",
      items: {
        type: "object",
        properties: {
          text: { type: "string" },
          file: { type: "string" },
          line: { type: "number" },
          author: { type: "string" },
          date: { type: "string" }
        },
        required: ["text", "file", "line"]
      }
    },
    total_count: { type: "number" }
  },
  required: ["todos", "total_count"]
};

// エージェントは Grep を使用して TODO を見つけ、Bash を使用して git blame 情報を取得
try {
  for await (const message of query({
    prompt: "Find all TODO comments in this codebase and identify who added them",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: todoSchema
      }
    }
  })) {
    if (message.type === "result" && message.subtype === "success" && message.structured_output) {
      const data = message.structured_output as { total_count: number; todos: Array<{ file: string; line: number; text: string; author?: string; date?: string }> };
      console.log(`Found ${data.total_count} TODOs`);
      data.todos.forEach((todo) => {
        console.log(`${todo.file}:${todo.line} - ${todo.text}`);
        if (todo.author) {
          console.log(`  Added by ${todo.author} on ${todo.date}`);
        }
      });
    }
  }
} catch (error) {
  // 単一ショットの query() は、error_max_structured_output_retries などのエラー結果を生成した後にスロー
  // エラーハンドリングセクションを参照してください。
  console.error(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

# TODO 抽出用の構造を定義
todo_schema = {
    "type": "object",
    "properties": {
        "todos": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "text": {"type": "string"},
                    "file": {"type": "string"},
                    "line": {"type": "number"},
                    "author": {"type": "string"},
                    "date": {"type": "string"},
                },
                "required": ["text", "file", "line"],
            },
        },
        "total_count": {"type": "number"},
    },
    "required": ["todos", "total_count"],
}


async def main():
    # エージェントは Grep を使用して TODO を見つけ、Bash を使用して git blame 情報を取得
    try:
        async for message in query(
            prompt="Find all TODO comments in this codebase and identify who added them",
            options=ClaudeAgentOptions(
                output_format={"type": "json_schema", "schema": todo_schema}
            ),
        ):
            if isinstance(message, ResultMessage) and message.structured_output:
                data = message.structured_output
                print(f"Found {data['total_count']} TODOs")
                for todo in data["todos"]:
                    print(f"{todo['file']}:{todo['line']} - {todo['text']}")
                    if "author" in todo:
                        print(f"  Added by {todo['author']} on {todo['date']}")
    except Exception as error:
        # 単一ショットの query() は、error_max_structured_output_retries などのエラー結果を生成した後にレイズ
        # エラーハンドリングセクションを参照してください。
        print(f"Session ended with an error: {error}")


asyncio.run(main())

スキーマの required を見ると、各 TODO では text・file・line が必須、author・date は任意です。これは 3-6 のヒント「タスクが必要な情報を持たないかもしれないなら、その項目は任意にする」をそのまま実践した形です。

筆者の指摘:表示の部分には、任意の項目の扱いに小さな落とし穴があります。どちらの例も author があるかだけを確かめてから date も表示していますが、スキーマ上 author と date は別々に任意です。author はあるが date が無い TODO が来た場合、TypeScript は undefined と表示するだけですが、Python は todo['date'] で KeyError になり、外側の except に捕まって「Session ended with an error」と表示されてしまいます。本当はエージェントの実行は成功しているのに、表示の不具合が実行の失敗のように見える、ということです。Python では todo.get('date') のように取り出すほうが安全です。

また、この例でエージェントは Bash で git blame を実行します。第80回で見たとおり、Bash の実行が承認されるかは権限モードと権限ルールで決まります。例には allowedTools も permissionMode も書かれていないので、環境によっては Bash が承認されず、author・date が空のまま返る可能性があります(筆者の指摘)。

3-6. エラーハンドリング

構造化出力の生成は、エージェントがスキーマに合った有効な JSON を作れないと失敗します。原文が挙げる典型は3つです。

  • スキーマがタスクに対して複雑すぎる
  • タスク自体があいまい
  • エージェントが検証エラーを直そうとして、再試行の上限に達した

さらに、検証が失敗していなくても起こりえます。モデルのフォールバックが、ストリームの途中で、すでに完成した出力を取り消すことがあり、再試行がそれを置き換えなければ、実行は同じエラーで終わります。原文は、スキーマを調べ始める前に、エラーの結果メッセージの errors リストを確かめて、2つの原因(検証の失敗か、フォールバックによる取り消しか)を見分けるよう勧めています。

エラーが起きると、結果メッセージの subtype が何がまずかったかを示します。

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

加えて、subtype が success でも structured_output が無いまま終わることがあります。たとえば、エージェントが構造化出力を作らずに実行を終えた場合です。これも失敗として扱います(第97回のトラブルシューティングの項目)。次の例は、subtype が success で、かつ structured_output があるときだけを成功とし、それ以外はすべて失敗として扱います。

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

const contactSchema = {
  type: "object",
  properties: {
    name: { type: "string" },
    email: { type: "string" }
  },
  required: ["name"]
};

try {
  for await (const msg of query({
    prompt: "Extract contact info from the document",
    options: {
      outputFormat: {
        type: "json_schema",
        schema: contactSchema
      }
    }
  })) {
    if (msg.type === "result") {
      if (msg.subtype === "success" && msg.structured_output) {
        // 検証済み出力を使用
        console.log(msg.structured_output);
      } else if (msg.subtype === "error_max_structured_output_retries") {
        console.error("Could not produce valid output");
      } else {
        console.error("Run ended without a structured output");
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the error subtype branches above have
  // already run; connection or process failures yield no result message.
  console.log(`Session ended with an error: ${error}`);
}
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

contact_schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "email": {"type": "string"},
    },
    "required": ["name"],
}


async def main():
    try:
        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")
    except Exception as error:
        # A single-shot query() raises after yielding an error result. If the
        # failure was an error result, the error subtype branches above have
        # already run; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

分岐は3つです。

条件扱い
success かつ structured_output がある検証済みの出力を使う
error_max_structured_output_retries「有効な出力を作れなかった」
それ以外(success だが出力が無い、など)「構造化出力なしで実行が終わった」

try/catch のコメントも重要です。1回きりの query() はエラーの結果を出した後に例外を送出しますが、その時点で上の subtype の分岐はすでに実行されています。 一方、接続やプロセスの失敗(第97回の CLI 起動・終了のエラー)では結果メッセージが出ないので、catch 側だけが動きます。つまり catch に来たからといって、それが構造化出力の失敗とは限りません。

この例のスキーマには email がありますが、format は付いていません。仮に付けても 3-4 のとおり強制されないので、抜き出したメールアドレスの形式の確認はアプリ側の仕事です(筆者の指摘)。

エラーを避けるヒント

原文のヒントは3つです。

  • スキーマを絞る:必須項目の多い深い入れ子のスキーマは満たしにくい。単純に始めて、必要に応じて複雑さを足す。
  • スキーマをタスクに合わせる:タスクがスキーマの求める情報をすべて持っていないかもしれないなら、その項目は任意にする。
  • 明確なプロンプトを使う:あいまいなプロンプトでは、エージェントが何を出せばよいか分かりにくい。

第97回の「正しいはずのスキーマで繰り返し失敗するなら、検証に通るまで単純化し、制約を1つずつ戻す」と合わせると、スキーマ設計は「小さく始めて足していく」が基本だと言えます。


4. まとめ

  • 構造化出力は、エージェントの最終結果を JSON Schema に沿った検証済みの JSON で受け取る機能。途中のツール使用は自由で、出口の形だけを固定する。
  • オプションは TypeScript が outputFormat、Python が output_format。type に "json_schema"、schema に JSON Schema を入れたオブジェクトを渡し、結果は結果メッセージの structured_output に入る。
  • SDK は不一致なら作り直しを求め、上限内に合わなければ error_max_structured_output_retries。1回きりの query() はその後に例外を出す。
  • SDK は draft-07 で検証する。Zod は既定が draft 2020-12 なので target: "draft-7" を渡す。Pydantic は .model_json_schema()。
  • 基本型・enum・const・required・入れ子・$ref に対応。format は注釈だけで強制されない(v2.1.205 以降)。v2.1.205 より前は、無効なスキーマが黙って無視されていた。
  • 情報が無いかもしれない項目は任意にする(TODO の例の author・date)。
  • 成功の判定は subtype === "success" と structured_output の存在の両方。Python のクイックスタートは subtype を見ていないので、本番ではエラーハンドリングの書き方に合わせる。
  • フォールバックによる取り消しでも失敗しうる。スキーマを疑う前に結果の errors リストで原因を見分ける。
  • catch に来るのは構造化出力の失敗だけではない(接続・プロセスの失敗は結果メッセージを出さない)。

次回予告

次回は 「Agent SDK のプラグイン」(agent-sdk/plugins) を取り上げる予定です。

第90回のスキルの回では、スキルを plugins オプションからも読み込めることに触れ、第97回のトラブルシューティングでも「プラグインが読み込まれない」「プラグインのスキルが表示されない」は専用のページに回されていました。次回は、スキルやエージェント、フックなどをまとめたプラグインを SDK のアプリにどう読み込ませるのか、読み込まれたかどうかをどう確かめるのかを見ていきます。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました