プログラマティックツール呼び出しの使い方

Claude API API 8分で読めます

ツールを10個も20個も呼ぶワークフローでは、1回のツール呼び出しごとにモデルとの往復が発生し、途中経過の生データがそのままコンテキストに積み上がります。プログラマティックツール呼び出し(Programmatic tool calling)は、この往復をコードにまとめてしまう機能です。Claude が Python のコードを書き、そのコードの中からあなたのツールを関数として呼び、絞り込んだ結果だけをモデルに返します。本記事では公式ドキュメントに基づき、有効化の方法・実際のやり取り・制約・削減効果を順に整理します。

プログラマティックツール呼び出しとは

プログラマティックツール呼び出しは、コード実行ツールのコンテナ内から、あなたが定義したツールを Claude がコードで呼べるようにする機能です。通常のツール使用では、1回のツール呼び出しごとに「モデルが呼ぶ→結果を返す→モデルがまた考える」という往復が必要でした。プログラマティックツール呼び出しでは、その繰り返しを Claude が書いた1本のスクリプトが担います。

公式ドキュメントは、20人分の経費が予算内かを調べる例を挙げています。従来の方法では20回の往復が必要で、その過程で数千行の明細がコンテキストに流れ込みます。プログラマティックツール呼び出しなら、1本のスクリプトが20回の照会を回し、結果を絞り込み、予算を超えた人だけを返します。Claude が読む量は数百キロバイトから数行に縮みます。

処理の流れは次の5段階です。

  1. Claude が、ツールを関数として呼ぶ Python コードを書く(前処理・後処理を含めてよい)
  2. そのコードをコード実行のサンドボックスコンテナで走らせる
  3. ツール関数が呼ばれた時点でコード実行が一時停止し、API が tool_use ブロックを返す
  4. あなたがツール結果を返すとコード実行が再開する(途中の結果はコンテキストに載らない
  5. コード実行が終わると、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種類です。

公式はどちらか一方に決めることを推奨しています。両方を許すと、そのツールをどう使ってほしいのかという指示が曖昧になるためです。なお "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点を明示しています。

結果を返すとコードが再開し、次のツール呼び出しで再び止まるか、コード実行が完了して 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_resultcontent文字列か 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_choiceallowed_callers"direct" を含まないツールを指名すると HTTP 400 になります。そのツールに "direct" を足すか、tool_choice から外してコードから呼ばせます。ツール自身がエラーを返す場合は、エラー文をそのまま tool_result で返せば、Claude のコード側で受け取って処理できます。

結果の検証。 公式は安全面の注意も挙げています。ツール結果は文字列として返るため、コード片やコマンドを含み得ます。外部由来のデータやユーザー入力を返すツールでは、それが実行環境で解釈される可能性を意識し、コードインジェクションのリスクを踏まえて検証してください。レート制限は通常のツール呼び出しと同じで、コードからの呼び出しも1回ずつ数えられます。

トークン削減の効果と使いどころ

トークンが減る理由は3つです。プログラマティック呼び出しのツール結果はコンテキストに追加されず、最終的なコードの出力だけが載ること。絞り込みや集計といった中間処理がコード側で終わること。1回のコード実行に複数のツール呼び出しをまとめられることです。課金上も、プログラマティック呼び出しのツール結果は入出力トークンに数えられず、最終的なコード実行結果と Claude の応答だけが数えられます。料金体系はコード実行ツールと同じです。

公式が公開している実測値は、効果がワークロードの形に依存することを示しています。

つまりこの機能は、コンテナ起動とスクリプト生成という固定の追加コストを払って、ツール結果のトークンとモデルの往復を大きく削る取引です。ツールが多い・呼び出し回数が多い・結果が大きいほど得になり、ツールを1〜2回呼んで終わるだけの処理ではむしろ割高になります。公式ドキュメントの「ツールを10個直接呼ぶと、プログラマティックに呼んで要約を返す場合の約10倍のトークンを使う」という比較が、判断の目安になります。

導入を検討するなら、まずは通常のツール使用で組んだワークフローのうち、同じツールをループで叩いている箇所・大きな結果を受け取ってから捨てている箇所を探すのが近道です。ツール定義そのものが多くてコンテキストを圧迫している場合は、ツール検索ツールとの併用も検討できます。

本記事は Anthropic 公式ドキュメント「Programmatic tool calling」に基づく非公式の日本語解説です。仕様は更新されるため、実装前に公式ドキュメントで最新の内容をご確認ください。