◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-05-16中級

Claude API の Tool Use でスキーマ定義ミスが出たとき:実際に遭遇した3つのパターンと診断手順

Claude API の Tool Use でよく起きるスキーマ定義ミス・invalid_tool_use・Claude がツールを呼ばない問題を、実際のアプリ開発で遭遇したケースをもとに診断手順と修正パターンで解説します。

tool-use23api39troubleshooting61python22error-handling10

Tool Use(Function Calling)を実装し始めた当初、私はスキーマ定義を誤ってしまうことが多くありました。2014年からアプリ開発を続けてきた中で、Beautiful HD Wallpapers に Claude API の画像分類機能を組み込もうとしたとき、最初に詰まったのは「スキーマが通らない」でも「Claude がツールを呼んでくれない」でもなく、メッセージスレッドへの tool_result の返し方が微妙にずれていたことでした。

エラーメッセージが invalid_request_error の一種として返ってくるため、一見すると「リクエストの構造が壊れている」としか分かりません。原因の切り分けに時間がかかったので、同じところで詰まる方のために診断フローをまとめます。

なお、Tool Use は Python SDK と TypeScript SDK のいずれも公式サポートしており、コードの構造はほぼ同じです。本記事の例は Python を使用していますが、TypeScript でも tools 配列の構造と tool_result の返し方は同一です。SDK を使う場合は tool_use や tool_result がオブジェクトとして型付けされているので、辞書の組み立てミスを減らせます。

Tool Use の3層構造を理解すると診断が速くなる

Tool Use のやりとりは3段階に分かれています。第1段階は「ツール定義を渡す」で、tools パラメータに JSON Schema 形式のスキーマを含めます。第2段階は「Claude がツールを呼ぶ」で、応答のコンテンツブロックに tool_use が含まれます。第3段階は「結果を返す」で、tool_result を含む user ターンを追加して次の応答を受け取ります。

この3段階のどこでエラーが起きているかを特定することが、診断の出発点です。エラーが返ってくる段階によって、原因はほぼ絞り込めます。第1段階のリクエスト送信直後にエラーが返るなら、スキーマの構造問題です。第3段階で返るなら、tool_result の形式や tool_use_id の不一致が疑われます。エラーが出ないのに Claude がツールを呼ばない場合は、description の曖昧さかプロンプト設計の問題です。

import anthropic
 
client = anthropic.Anthropic()
 
# 第1段階: ツール定義(ここのスキーマ構造が重要)
tools = [
    {
        "name": "get_image_category",
        "description": "画像のカテゴリを分類して返します。ユーザーが画像の分類・カテゴリ特定を求めたときに呼び出してください",
        "input_schema": {
            "type": "object",
            "properties": {
                "image_url": {
                    "type": "string",
                    "description": "分類する画像のURL"
                },
                "categories": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "候補カテゴリのリスト"
                }
            },
            "required": ["image_url", "categories"]
        }
    }
]
 
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "この画像を分類してください: https://example.com/photo.jpg"}]
)

パターン1: required の位置と type の省略が原因の場合

最もよくある原因は、input_schema の構造が JSON Schema の仕様から外れていることです。Claude API の Tool Use は JSON Schema Draft 7 をベースにしており、細かい制約があります。

required フィールドの位置は、スキーマのトップレベル(properties と同じ階層)に配列として書く必要があります。プロパティ定義の中に required: true のように書いても、無視されるかエラーになります。もう一つよくあるのが、プロパティの type フィールドの省略です。properties の各項目には必ず type を指定する必要があり、description だけ書いても invalid_request_error になります。

# ❌ よくある誤り: required が properties の中にある
"input_schema": {
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "required": True  # ← ここには書けない
        }
    }
}
 
# ✅ 正しい書き方: required はトップレベルの配列
"input_schema": {
    "type": "object",
    "properties": {
        "query": {
            "type": "string",
            "description": "検索クエリ文字列"
        }
    },
    "required": ["query"]  # ← トップレベルに文字列の配列として書く
}

スキーマを事前に検証したい場合は、jsonschema パッケージの Draft7Validator.check_schema() が使えます。API に投げる前にローカルで構造チェックをするだけで、原因不明の invalid_request_error に悩む時間が大幅に減ります。

パターン2: tool_result の返し方がずれている場合

Claude が tool_use ブロックを返した後、結果を返すメッセージの構造には決まったフォーマットがあります。私が最初に詰まったのもここでした。よくある間違いは、ツール実行結果をプレーンな文字列メッセージとして返してしまうことです。tool_result ブロックを content 配列に含める形にする必要があります。

tool_use_id は Claude が返したブロックの ID と完全に一致させる必要があります。同じセッション内で複数のツールを並列で呼んだ場合、それぞれの ID を正確に対応させなければいけません。ID を tool_use_block.id でコピーするのが最も確実です。

# ❌ 誤り: tool_result を使わずに文字列メッセージとして返す
messages.append({
    "role": "user",
    "content": "分類結果はlandscapeです"  # ← これは tool_result にならない
})
 
# ✅ 正しい: tool_result ブロックを content 配列に含める
if response.stop_reason == "tool_use":
    tool_use_block = next(b for b in response.content if b.type == "tool_use")
    tool_result = execute_my_tool(tool_use_block.input)  # 実際のツール実行
 
    messages.append({
        "role": "user",
        "content": [
            {
                "type": "tool_result",
                "tool_use_id": tool_use_block.id,  # Claude が返した ID をそのままコピー
                "content": str(tool_result)
            }
        ]
    })

パターン3: Claude がツールを呼ばない場合

スキーマが正しくても、Claude がツールを呼ばずに直接テキストで回答してしまうことがあります。これは技術的なエラーではなく、プロンプトと description の設計問題です。

description は Claude がツールを「いつ使うべきか」を判断するほぼ唯一の手がかりです。「データを処理します」のような抽象的な説明では、Claude はテキスト回答を選びがちになります。「ユーザーが〇〇を求めたときに呼び出す」のように、使いどころを具体的に書くことで呼び出し率が上がります。

また、Claude が内部的に「ツールを使わなくてもテキストで答えられる」と判断してしまう場合があります。そのような場合は tool_choice パラメータで呼び出しを強制できます。type: "any" を使うと、Claude はいずれかのツールを必ず呼ぶ応答を返します。type: "tool" に name を指定すると、特定のツールを強制的に呼ばせられます。

# ツール呼び出しを強制する(どれかのツールを必ず呼ぶ)
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "画像を分類してください"}]
)
 
# 特定のツールを指定して強制
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_image_category"},
    messages=[{"role": "user", "content": "この画像を分析して"}]
)

スキーマ設計で押さえておきたい細かい注意点

Tool Use を本番環境で使い続けると、初期実装ではすり抜けた細かい問題が出てくることがあります。実際の運用で気づいたいくつかの点を補足します。

enum で候補を絞る場合の型 — "type": "string" に "enum": ["a", "b", "c"] を組み合わせると、Claude は必ずその候補の中から値を選びます。ただし、Claude が出力する文字列の大文字・小文字が enum の定義と一致しないケースがあります。API 側での大文字小文字の正規化を忘れないでください。

ネストしたオブジェクト — properties の中にさらにオブジェクトを含める場合も、すべての階層で "type": "object" を明示する必要があります。ネストしたオブジェクトの properties は親と同じ構造を繰り返します。ネストが深くなると定義が冗長になりがちですが、スキーマを省略すると Claude が構造を誤解することがあります。

additionalProperties: false — スキーマに含まれていないフィールドを Claude が返してきた場合に、アプリ側でエラーにならないよう防御したいときは "additionalProperties": false を追加できます。ただし、これを指定すると Claude が余分なフィールドを追加しようとした際に tool_use 自体が失敗することがあるため、まず動かしてから必要に応じて追加するのが安全です。

description の長さ — description が長すぎると、コンテキストの使用量が増えます。「いつ呼ぶか」の条件と「何をするか」の説明を2〜3文にまとめるのが実用的なバランスです。特に複数ツールを同時に定義する場合は、各ツールの description が長くなりすぎないよう意識することが大切です。累計5,000万DLを超えるアプリ群に AI 機能を追加してきた経験から言うと、description の整理はコスト最適化にも直結します。

これらは小さな注意点ですが、本番でのエラー率に影響します。スキーマ設計の段階で一度見直しておく価値があります。

診断フローの実践

エラーが出たときの確認順序をまとめます。invalid_request_error がリクエスト送信直後に返る場合は、input_schema の構造を疑います。required の位置、各プロパティの type の有無、スキーマルートの "type": "object" の存在を順番に確認します。

tool_result を返したあとにエラーが出る場合は、tool_use_id の一致と tool_result ブロックの構造を確認します。content が配列になっているか、type フィールドが "tool_result" になっているかをチェックします。

エラーが出ないのに Claude がツールを呼ばない場合は、description の具体性を上げるか、まず tool_choice: {"type": "any"} で強制して動作を確認します。それでもツールが呼ばれるなら、プロンプト側の問題です。

Tool Use のデバッグは「3段階のどこで詰まっているか」を特定することがほぼすべてです。エラーメッセージだけ読んでいると迷子になりやすいですが、段階を意識すると原因が見えてきます。私自身、cumulative 5,000万DL超のアプリ群に AI 機能を組み込んできた経験から言うと、Tool Use のエラーの9割はスキーマの required 位置と tool_result の返し方で説明がつきます。

まず上記のチェックリストを上から確認してみてください。それだけで多くの場合は解決します。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ API & SDK2026-05-22
Claude の『このモデルは現在利用できません』が出る時に効くモデル選択フォールバックの設計
Claude API で『このモデルは現在利用できません(this model is currently unavailable)』と返ってきたとき、529 Overloaded やレート制限と同じ扱いにすると遠回りになります。エラーの種類を SDK の例外クラスで見分け、モデル ID をリクエスト単位で切り替えるフォールバックの実装と、思い込みで書きやすい落とし穴をまとめました。
⬡ API & SDK2026-05-22
Claude API の tool_result could not be submitted が再発しないための回復ハンドラ設計
Claude API でエージェントを長時間動かしていると、ある日突然 'tool_result could not be submitted' が連発し始めることがあります。再試行しても直らない、ストリーミング途中で死ぬ、そして次のセッションでもまた出る — 常時稼働のエージェント基盤で実際に踏み抜いた4種類の原因と、TypeScript で書いた回復ハンドラの実装を整理しました。
⬡ API & SDK2026-05-16
Claude API で多言語レビュー返信を自動化した実装ログ — App Store 非公式制限との付き合い方
壁紙・ヒーリング系アプリを複数運営する個人開発者が、Claude APIで多言語レビュー返信を自動化した実装ログ。App Storeの8秒待機ルールや1セッション30件制限など、公式ドキュメントには書かれていない落とし穴と解決策を詳解します。