ツールを10個も20個も呼ぶワークフローでは、1回のツール呼び出しごとにモデルとの往復が発生し、途中経過の生データがそのままコンテキストに積み上がります。プログラマティックツール呼び出し(Programmatic tool calling)は、この往復をコードにまとめてしまう機能です。Claude が Python のコードを書き、そのコードの中からあなたのツールを関数として呼び、絞り込んだ結果だけをモデルに返します。本記事では公式ドキュメントに基づき、有効化の方法・実際のやり取り・制約・削減効果を順に整理します。
プログラマティックツール呼び出しとは
プログラマティックツール呼び出しは、コード実行ツールのコンテナ内から、あなたが定義したツールを Claude がコードで呼べるようにする機能です。通常のツール使用では、1回のツール呼び出しごとに「モデルが呼ぶ→結果を返す→モデルがまた考える」という往復が必要でした。プログラマティックツール呼び出しでは、その繰り返しを Claude が書いた1本のスクリプトが担います。
公式ドキュメントは、20人分の経費が予算内かを調べる例を挙げています。従来の方法では20回の往復が必要で、その過程で数千行の明細がコンテキストに流れ込みます。プログラマティックツール呼び出しなら、1本のスクリプトが20回の照会を回し、結果を絞り込み、予算を超えた人だけを返します。Claude が読む量は数百キロバイトから数行に縮みます。
処理の流れは次の5段階です。
- Claude が、ツールを関数として呼ぶ Python コードを書く(前処理・後処理を含めてよい)
- そのコードをコード実行のサンドボックスコンテナで走らせる
- ツール関数が呼ばれた時点でコード実行が一時停止し、API が
tool_useブロックを返す - あなたがツール結果を返すとコード実行が再開する(途中の結果はコンテキストに載らない)
- コード実行が終わると、Claude は最終的な出力だけを受け取って作業を続ける
向いているのは、大量データの絞り込み・集計、モデルを挟まずに連続やループでツールを呼ぶ多段ワークフロー、途中結果を見て分岐する条件処理です。
この機能を使うにはコード実行ツールの有効化が必須で、コード実行ツールのバージョンは code_execution_20260120 以降が必要です。対応モデルは Claude Opus 5 / Claude Sonnet 5 / Claude Fable 5 / Claude Mythos 5 / Claude Opus 4.8 / 4.7 / 4.6 / Claude Sonnet 4.6 / Claude Opus 4.5 / Claude Sonnet 4.5 などです。提供面では Claude API、AWS 上の Claude Platform、Microsoft Foundry(Hosted on Anthropic デプロイのみ)で使えます。Amazon Bedrock と Google Cloud では現時点で利用できません。
allowed_callers でツールをコードから呼べるようにする
有効化は、ツール定義に allowed_callers を足すだけです。値はそのツールをどこから呼べるかを表します。
{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": { "type": "object", "properties": { "sql": { "type": "string" } }, "required": ["sql"] },
"allowed_callers": ["code_execution_20260120"]
}
指定できる値は3種類です。
["direct"]: 従来どおり Claude が直接呼ぶ(省略時の既定値)["code_execution_20260120"]: コード実行の中からのみ呼ぶよう Claude を誘導する["direct", "code_execution_20260120"]: どちらの経路でも呼べる
公式はどちらか一方に決めることを推奨しています。両方を許すと、そのツールをどう使ってほしいのかという指示が曖昧になるためです。なお "code_execution_20260120" と "code_execution_20260521" は相互に受理され、どちらを書いてもリクエスト側のバージョンと噛み合います。レスポンス側の表記は常に code_execution_20260120 になります。
重要な注意が公式にあります。allowed_callers はセキュリティ境界ではありません。 これは Claude への提示の仕方を制御し、tool_choice との整合を検証するもので、直接呼び出しを API レベルで禁止する仕組みではありません。Claude は強く誘導されますが、クライアント側は定義したどのツールについても直接の tool_use が来る可能性に備えておく必要があります。
リクエスト全体は次の形になります。コード実行ツールと、プログラマティック呼び出しを許したツールを両方 tools に入れます。
curl https://api.anthropic.com/v1/messages \
--header "x-api-key: $ANTHROPIC_API_KEY" \
--header "anthropic-version: 2023-06-01" \
--header "content-type: application/json" \
--data '{
"model": "claude-opus-5",
"max_tokens": 4096,
"messages": [
{ "role": "user", "content": "西・東・中部の売上を調べ、最も売上が高い地域を教えて" }
],
"tools": [
{ "type": "code_execution_20260120", "name": "code_execution" },
{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": {
"type": "object",
"properties": { "sql": { "type": "string", "description": "SQL query to execute" } },
"required": ["sql"]
},
"allowed_callers": ["code_execution_20260120"]
}
]
}'
Python SDK でも同じ構造です。tools にコード実行ツールと自前ツールを並べ、自前ツールに allowed_callers を付けます。
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
messages=[{"role": "user", "content": "西・東・中部の売上を調べ、最も売上が高い地域を教えて"}],
tools=[
{"type": "code_execution_20260120", "name": "code_execution"},
{
"name": "query_database",
"description": "Execute a SQL query against the sales database. Returns a list of rows as JSON objects.",
"input_schema": {
"type": "object",
"properties": {"sql": {"type": "string", "description": "SQL query to execute"}},
"required": ["sql"],
},
"allowed_callers": ["code_execution_20260120"],
},
],
)
コード実行から呼べるツールは、Claude のコードには非同期の Python 関数として見えます。引数は辞書1つ、戻り値は文字列(あなたが返す tool_result の本文)です。したがって Claude は rows = json.loads(await query_database({"sql": "<sql>"})) のようにトップレベル await で呼び、必要なものを構造化データとして解釈します。並列化したいときは asyncio.gather が使えます。
実際のやり取りの流れ
実際の1ターンは、コードの一時停止を挟んで進みます。まず Claude は、テキスト・server_tool_use(コード実行)・tool_use(あなたのツール)を含むレスポンスを返して止まります。
{
"role": "assistant",
"content": [
{ "type": "text", "text": "I'll query the purchase history and analyze the results." },
{
"type": "server_tool_use",
"id": "srvtoolu_abc123",
"name": "code_execution",
"input": { "code": "rows = json.loads(await query_database({'sql': '<sql>'}))\n..." }
},
{
"type": "tool_use",
"id": "toolu_def456",
"name": "query_database",
"input": { "sql": "<sql>" },
"caller": { "type": "code_execution_20260120", "tool_id": "srvtoolu_abc123" }
}
],
"container": { "id": "container_xyz789", "expires_at": "2026-01-20T14:30:00Z" },
"stop_reason": "tool_use"
}
すべてのツール使用ブロックには caller が付きます。従来の直接呼び出しは {"type": "direct"}、コードからの呼び出しは {"type": "code_execution_20260120", "tool_id": "srvtoolu_abc123"} です。tool_id は呼び出し元のコード実行ブロックの id なので、どのスクリプトの実行が出した呼び出しかを突き合わせられます。
結果を返す継続リクエストでは、公式が3点を明示しています。
- 結果を運ぶ user メッセージには
tool_resultブロックしか入れられない(テキストを添えるとエラー) - 停止したレスポンスの
containerの ID を必ず渡す(保留中の呼び出しがあるのに ID が無いリクエストは拒否される) - 最初と同じ
tools配列を送る(止まっているコードを再開するにはコード実行ツールが必要)
結果を返すとコードが再開し、次のツール呼び出しで再び止まるか、コード実行が完了して Claude が最終回答を書きます。コンテナはコード実行と同じもので、リクエストごとに新規作成され、ID を渡し回せば状態を保てます。アイドル状態のコンテナはおおよそ5分で回収され、作成から30日を超えたコンテナは再利用できません。
コードでまとめて処理するパターン
この機能の価値は「Claude がどんなコードを書けるか」に出ます。公式が挙げるパターンを見ると、往復削減の形が具体的になります。
ループでまとめて処理する。 地域ごとの照会をループで回し、集計だけを返します。往復は N 回から1回になります。
regions = ["West", "East", "Central", "North", "South"]
results = {}
for region in regions:
rows = json.loads(await query_database({"sql": f"<sql for {region}>"}))
results[region] = sum(row["revenue"] for row in rows)
top_region = max(results.items(), key=lambda x: x[1])
print(f"Top region: {top_region[0]} with ${top_region[1]:,} in revenue")
条件を満たしたら止める。 healthy な endpoint が1つ見つかった時点で残りを調べません。
endpoints = ["us-east", "eu-west", "apac"]
for endpoint in endpoints:
status = await check_health({"endpoint": endpoint})
if status == "healthy":
print(f"Found healthy endpoint: {endpoint}")
break
途中結果で呼ぶツールを変える。 ファイルが小さければ全文、大きければ要約を取る、といった分岐をコードの中で完結させます。
file_info = json.loads(await get_file_info({"path": path}))
if file_info["size"] < 10000:
content = await read_full_file({"path": path})
else:
content = await read_file_summary({"path": path})
print(content)
返す前に絞り込む。 ログ全文ではなく、ERROR 行の件数と直近10件だけを返します。コンテキストに載るのはこの出力だけです。
log_text = await fetch_logs({"server_id": server_id})
errors = [line for line in log_text.splitlines() if "ERROR" in line]
print(f"Found {len(errors)} errors")
for error in errors[-10:]:
print(error)
ツール側の設計にもコツがあります。Claude はツール結果をコードの中でデシリアライズするので、公式は出力形式(JSON の構造とフィールドの型)を description に明記すること、機械可読な形式で返すこと、余計なデータを返さないことを勧めています。
制約とエラーへの備え
先に知っておくと踏まない制約がいくつかあります。
組み合わせられない機能。 strict: true を使う構造化出力のツールは非対応です。tool_choice で特定ツールのプログラマティック呼び出しを強制することもできません。disable_parallel_tool_use: true も併用できません。MCP コネクタが提供するツールはプログラマティックに呼べません。
スキーマの制約。 input_schema に再帰的な $ref(自分自身を参照するなどの循環)があるツールは、allowed_callers にコード実行を含めると 400 invalid_request_error(メッセージに Circular $ref detected)で落ちます。同じスキーマでも直接呼び出しなら通ります。回避策は、そのツールだけ direct のままにするか、再帰を一定の深さまで展開して残りを description で説明する形に書き換えることです。
メッセージの形。 保留中のプログラマティック呼び出しに答えるメッセージには tool_result 以外を入れられません。テキストを添えるのは、結果の後ろであっても不可です。また、その tool_result の content は文字列か text ブロックだけで、画像やドキュメントのブロックは拒否されます。通常のクライアント側ツール呼び出しにはこの制限はありません。
タイムアウト。 Claude のコードがツール結果を待っている間、保留中の呼び出しは約4分でタイムアウトし、コードの中で TimeoutError になります。Claude は stderr でそれを見て、たいていは呼び直します。防ぐには、レスポンスの expires_at を監視し、ツール側にもタイムアウトを実装し、長い処理は分割します。
{
"type": "code_execution_tool_result",
"tool_use_id": "srvtoolu_abc123",
"content": {
"type": "code_execution_result",
"stdout": "",
"stderr": "TimeoutError: Calling tool ['query_database'] timed out (no response after 270s).",
"return_code": 0,
"content": []
}
}
よくあるエラー。 tool_choice が allowed_callers に "direct" を含まないツールを指名すると HTTP 400 になります。そのツールに "direct" を足すか、tool_choice から外してコードから呼ばせます。ツール自身がエラーを返す場合は、エラー文をそのまま tool_result で返せば、Claude のコード側で受け取って処理できます。
結果の検証。 公式は安全面の注意も挙げています。ツール結果は文字列として返るため、コード片やコマンドを含み得ます。外部由来のデータやユーザー入力を返すツールでは、それが実行環境で解釈される可能性を意識し、コードインジェクションのリスクを踏まえて検証してください。レート制限は通常のツール呼び出しと同じで、コードからの呼び出しも1回ずつ数えられます。
トークン削減の効果と使いどころ
トークンが減る理由は3つです。プログラマティック呼び出しのツール結果はコンテキストに追加されず、最終的なコードの出力だけが載ること。絞り込みや集計といった中間処理がコード側で終わること。1回のコード実行に複数のツール呼び出しをまとめられることです。課金上も、プログラマティック呼び出しのツール結果は入出力トークンに数えられず、最終的なコード実行結果と Claude の応答だけが数えられます。料金体系はコード実行ツールと同じです。
公式が公開している実測値は、効果がワークロードの形に依存することを示しています。
- 75個のツールを持つプロジェクト管理エージェントのベンチマークでは、課金対象の入力トークンが約38%減り、タスクの正確さは変わらなかった
- τ²-bench(航空・小売・通信)のように1ターンで1〜2回の逐次呼び出ししかしない場合、スコアは変わらずコストは約8%増えた。逐次1回だけのワークフローには効かない
- 本番トラフィック全体では、
toolsに10〜49個の定義を持つリクエストで20〜40%の削減が典型的 - エージェント検索系のベンチマーク(BrowseComp・DeepSearchQA)では、基本的な検索ツールに追加して平均11%の性能向上、入力トークンは24%減
つまりこの機能は、コンテナ起動とスクリプト生成という固定の追加コストを払って、ツール結果のトークンとモデルの往復を大きく削る取引です。ツールが多い・呼び出し回数が多い・結果が大きいほど得になり、ツールを1〜2回呼んで終わるだけの処理ではむしろ割高になります。公式ドキュメントの「ツールを10個直接呼ぶと、プログラマティックに呼んで要約を返す場合の約10倍のトークンを使う」という比較が、判断の目安になります。
導入を検討するなら、まずは通常のツール使用で組んだワークフローのうち、同じツールをループで叩いている箇所・大きな結果を受け取ってから捨てている箇所を探すのが近道です。ツール定義そのものが多くてコンテキストを圧迫している場合は、ツール検索ツールとの併用も検討できます。
本記事は Anthropic 公式ドキュメント「Programmatic tool calling」に基づく非公式の日本語解説です。仕様は更新されるため、実装前に公式ドキュメントで最新の内容をご確認ください。