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 の返し方で説明がつきます。
まず上記のチェックリストを上から確認してみてください。それだけで多くの場合は解決します。