一行要約

Claude にツールを呼ぶ Python コードを書かせて、中間結果を context に載せずに処理させる仕組み。効くのは fan-out 型のワークロードだけ — 逐次的な流れでは効かず、τ²-bench ではむしろ 8% 高くついた

要点

動作

  1. Claude がツールを関数として呼ぶ Python コードを書く(複数呼び出しと前後処理を含みうる)
  2. サンドボックスコンテナで実行する
  3. ツール関数が呼ばれるとコード実行が一時停止し、API が tool_use ブロックを返す
  4. こちらがツール結果を返すとコード実行が続く中間結果は Claude の context window にロードされない
  5. 実行完了後、Claude が最終出力を受け取る

Tools that allow a code execution caller are exposed to Claude’s code as async Python functions, so Claude can run them in parallel with asyncio.gather. Each function takes a single dict of arguments and returns a string: the text of the tool_result you send back. code execution からの呼び出しを許可したツールは、Claude のコードに対して async な Python 関数として公開される。そのため Claude は asyncio.gather で並列に実行できる。各関数は引数の dict をひとつ受け取り、文字列を返す。返すのは、あなたが送り返す tool_result のテキストである。

→ Claude 側のコードは rows = json.loads(await query_database({"sql": "<sql>"})) のように自分で JSON をパースする

allowed_callers

意味
["direct"]直接呼ぶよう誘導(省略時の既定
["code_execution_20260120"]コード実行内からのみ呼ぶよう誘導
["direct", "code_execution_20260120"]どちらも可

Choose either ["direct"] or ["code_execution_20260120"] for each tool rather than enabling both, as this provides clearer guidance to Claude. **各ツールについて ["direct"]["code_execution_20260120"] のどちらかを選べ。両方を有効にするな。**そのほうが Claude への指示が明確になる。

重要な注意:

allowed_callers controls how the tool is presented to Claude and is validated against tool_choice, but it is not a hard API-level block on direct invocation. Your client should still be prepared to handle a direct tool_use for any tool it defines. Do not rely on allowed_callers as a security boundary. allowed_callersツールが Claude にどう提示されるかを制御するもので、tool_choice に対して検証もされるが、直接呼び出しを API レベルで硬く禁じるものではない。クライアントは、自分が定義したどのツールについても直接の tool_use を処理できるよう備えておくべきである。allowed_callers をセキュリティ境界として当てにするな。

レスポンスの caller フィールド

すべての tool_use ブロックが caller を持つ。

{"caller": {"type": "direct"}}
{"caller": {"type": "code_execution_20260120", "tool_id": "srvtoolu_abc123"}}

tool_id は呼び出した code execution の server_tool_useid なので、どの実行が生んだ呼び出しかを対応付けられる

コンテナのライフサイクル(タイムアウトが厳しい)

  • リクエストごとに新しいコンテナ(再利用しない限り)
  • プログラム的ツール呼び出しが結果を待っている間、コンテナ ID は必須(任意ではない)。ないとリクエストが拒否される
  • アイドルのコンテナは約5分で回収される。 作成から30日を超えて再利用はできない
  • 待機中のツール呼び出しは約4分でタイムアウトし、コード内で TimeoutError を投げる

メッセージ書式の制約(実装で必ず踏む)

Tool result only responses: If there are pending programmatic tool calls waiting for results, your response message must contain only tool_result blocks. You cannot include any text content, even after the tool results. tool result のみの応答: 結果待ちの programmatic なツール呼び出しが残っている場合、応答メッセージには tool_result ブロックのみを含めなければならない。テキストの内容を含めることはできない。tool result の後であっても不可である。

Text-only tool result content: The content of each tool_result that answers a programmatic call must be a string or text blocks. Image, document, and other content block types are rejected. tool result の内容はテキストのみ: programmatic な呼び出しに応える各 tool_resultcontent は、文字列または text ブロックでなければならない。画像・文書その他のコンテンツブロック型は拒否される。

この制約はプログラム的な呼び出しへの応答時のみ。 通常のツール呼び出しではテキストを含めてよい。

使えない組み合わせ

  • strict: true の structured outputs
  • tool_choice で特定ツールのプログラム的呼び出しを強制することはできない
  • disable_parallel_tool_use: true
  • MCP connector 提供のツールはプログラム的に呼べない
  • input_schema に再帰的な $ref を含むツールallowed_callers に code execution を入れると 400 invalid_request_errorCircular $ref detected になる(同じスキーマでも直接呼び出しなら受理される

トークン効率の実測(ここが最も重要)

削減の仕組みは3つ — プログラム的呼び出しのツール結果は context に入らない中間処理がコード内で完結する1回のコード実行で複数のツール呼び出し

条件結果
75ツールのプロジェクト管理エージェントのベンチマーク課金入力トークンが約 38% 減り、タスク精度は変わらず
τ²-bench(1ターンに1〜2回の逐次的なツール呼び出し)スコアは変わらず、コストは約 8% 増。逐次的な単発呼び出しのワークフローは恩恵を受けない
本番トラフィック全体(tools10〜49 個典型的に 20〜40% の削減

課金: プログラム的呼び出しのツール結果は入出力トークンに算入されない。最終的なコード実行結果と Claude の応答だけが算入される。

向き / 不向き

向く:

  • 多数の項目への fan-out / 並列操作(50 エンドポイントの確認、20 レコードの照会)
  • context に届く前にフィルタ・集約・要約できる大きなツール結果
  • agentic な検索と取得 — 反復的なクエリと結果フィルタが主体のワークフロー

向かない:

  • 厳密に逐次的なワークフロー — 各呼び出しが前の結果に対する Claude の推論に依存する場合、スクリプトはモデルのラウンドトリップを飛ばせない
  • 少数のツール呼び出しで応答も小さい場合(特に会話の最初のターン)— コンテナとスクリプトのオーバーヘッドが節約を上回りうる
  • 呼び出しの間にユーザーのフィードバックが要るツール

If you are unsure, measure billed input tokens with and without allowed_callers on a representative sample of your traffic before enabling it broadly. 判断がつかないなら、広く有効にする前に、代表的なトラフィックのサンプルで allowed_callers の有無それぞれの課金対象入力トークンを測れ

ツール設計のコツ

  • 出力形式を詳細に文書化するClaude はコード内で結果をデシリアライズするので、JSON の構造とフィールドの型を書く
  • 構造化データを返す
  • 応答は簡潔に

そのまま使える具体例

ツールをプログラム的呼び出しの対象にする:

{
  "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"]
}

バッチ処理(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)
 
# Process results programmatically
top_region = max(results.items(), key=lambda x: x[1])
print(f"Top region: {top_region[0]} with ${top_region[1]:,} in revenue")

早期終了:

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  # Stop early, don't check remaining

条件によるツール選択:

path = "/tmp/example.txt"
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)

データのフィルタ(大量のログから必要な分だけ返す):

server_id = "srv-01"
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:]:  # Only return last 10 errors
    print(error)

応答メッセージの書式(テキストを混ぜると拒否される):

// ❌ 無効
{"role": "user", "content": [
  {"type": "tool_result", "tool_use_id": "toolu_01", "content": "[...]"},
  {"type": "text", "text": "What should I do next?"}
]}
 
// ✅ 有効
{"role": "user", "content": [
  {"type": "tool_result", "tool_use_id": "toolu_01", "content": "[...]"}
]}

原典で言及されている関連文書

未取得の派生リンク