Programmatic Tool Calling(PTC)の何が新しいのか?

AnthropicがClaude(モデル) APIの新機能として「Programmatic Tool Calling」(以下PTC)を パブリックベータとして公開しました。

Introducing advanced tool use on the Claude Developer Platform
Claude can now discover, learn, and execute tools dynamically to enable agents that take action in the real world. Here’s how.

一言で言うと、これは「ClaudeがToolを呼び出す処理をPythonコードとして生成し、 Anthropicが提供するサンドボックス内で実行する」機能です。

従来のTool Useでは、Toolを1つ呼ぶたびにClaudeが次のアクションを判断し、 その結果をすべてコンテキストウィンドウに追加していました。 10個のToolを連鎖して呼び出すと、10回分の推論と、 10回分の生データがコンテキストを圧迫します。

実際、この問題は Claude for Excel で顕著でした。Anthropic 公式記事でも「数千行以上のスプレッドシートを読むだけで、中間データがコンテキストを圧迫し、推論が破綻する」という課題が挙げられています。

Claude for Excel は PTC を利用し、

  • スプレッドシートの巨大データをすべてコード実行側で処理し
  • Claude が受け取るのは 最終的な集計・分析結果だけ

という形にすることで、コンテキスト肥大化を根本的に解決しています。

PTCでは、Claudeは「Tool呼び出しを含むPythonコード」を一度だけ生成し、 あとはサンドボックス内でPythonが逐次実行します。 中間結果はサンドボックスのメモリ内に閉じ、 Claudeのコンテキストに戻るのは print() で出力した最終結果だけです。

興味深いのは、API利用者側の役割は従来の Tool Use と変わらない点です。
レスポンス中に返る tool_use を受け取り、対応するツールを実行して tool_result を返すだけでよく、PTCの内部構造を意識する必要はありません。

また、コード中の await tool_name() が実行トリガーとして機能している点も、PTCの設計として非常に面白い部分です。LLM が自然に書く Python そのままの形でツール呼び出しが表現できるため、Claude のコード生成能力と親和性が高まっています。いかに全体像を図化したものがあります

Anthropic記事に登場するフロー図

しかしPTCの仕組みはアクターとモデルが複雑で非常に分かりづらく、筆者も実際に呼び出しプログラムを書いてみてやっと理解しました。

本記事では、PTCの仕組みと、従来のTool Use方式との違いを整理し、PTCがどのような問題を解決するのかを検証します。

PTCの仕組み

ドキュメントによるとPTCを使うには、3つの設定が必要です。専用のAPIがあるわけではなく、従来のCode execution tool と新パラメータ「allowed_callers」を使った枠組みです。

  1. ベータヘッダー advanced-tool-use-2025-11-20 を指定
  2. ツールリストに code_execution_20250825 を追加
  3. 各Toolに allowed_callers: ["code_execution_20250825"] を指定
tools = [
    {
        "type": "code_execution_20250825",
        "name": "code_execution"
    },
    {
        "name": "count_words",
        "description": "テキストファイルからMeCabで単語頻度を分析する",
        "input_schema": {...},
        "allowed_callers": ["code_execution_20250825"]
    },
    # 他のツールも同様
]

response = client.beta.messages.create(
    model="claude-sonnet-4-5",
    betas=["advanced-tool-use-2025-11-20"],
    max_tokens=8192,
    tools=tools,
    messages=[...]
)

この設定により、ClaudeはTool呼び出しを直接行う代わりに、 Tool呼び出しを含むPythonコードを生成します:

# Claudeが生成するコード(サンドボックス内で実行される)
words = await count_words({"filename": "sample_text.txt"})
filtered = await filter_results({"words": json.loads(words), "min_freq": 10})
await save_summary({"result": json.loads(filtered), "output_filename": "result.txt"})
print(f"頻出単語: {len(json.loads(filtered))}件を保存しました")

words(数千件の単語データ)やfilteredはサンドボックス内の変数に格納され、 Claudeのコンテキストには含まれません。 コンテキストに戻るのは print() の出力だけです。

ここで重要なのは、コードの実行場所です。 上記のPythonコード自体はAnthropicが提供するサンドボックスで実行されますが、 await count_words(...)のように呼び出されるToolは、呼び出し元(開発者が実装した環境)で実行されます。 サンドボックスがToolを呼び出すと、APIがtool_useを返し、 呼び出し元がToolを実行して結果を返すと、サンドボックス内のコードが続行します。

なぜPTCが必要なのか

従来のTool Useでは、ClaudeがToolを呼び出すたびに以下のサイクルが回ります:

  1. Claudeが「次はこのToolを呼ぶ」と判断
  2. APIがtool_useを返す
  3. 開発者がToolを実行し、結果をtool_resultとして返す
  4. Claudeが結果を読み込み、次のアクションを判断
  5. 1に戻る

問題は、各Toolの実行結果がすべてコンテキストに蓄積されていくことです。 Toolを呼ぶほどコンテキストが膨らみ、トークン消費が増えます。

さらに精度低下の問題もあります。 コンテキストウィンドウのトークン数が増えるほど、 モデルがその中から情報を正確に取り出す能力が低下する 「context rot(コンテキスト腐敗)」という現象が知られています。

Anthropicはこの問題に対し、compaction(要約による圧縮)や structured note-taking(構造化メモ)といった手法を提案してきました。 PTCはこれらとは別のアプローチで、 そもそも中間データをコンテキストに入れない、という解決策です。

Effective context engineering for AI agents
Anthropic is an AI safety and research company that’s working to build reliable, interpretable, and steerable AI systems.

検証:3ツール連鎖でのトークン削減

PTCの効果を確認するため、以下の3つのToolを連鎖させるワークフローで検証しました。このワークフローは、ブログ記事の頻出単語を分析し、文章の多様性を高めるための置き換え表現を提案するものです。

  1. count_words - MeCabで形態素解析し、単語頻度を分析
  2. filter_results - 頻出単語(10回以上出現)のみを抽出
  3. save_summary - 結果をファイルに保存

各ツールとスクリプトの実装はGistで公開しています。

従来方式(Non PTC)ではallowed_callers: ["direct"]、 PTC方式ではallowed_callers: ["code_execution_20250825"]を指定します。

# **従来方式(Non-PTC):**
tools = [
    {
        "name": "count_words",
        "description": "テキストファイルから単語頻度を分析する",
        "input_schema": {...},
        "allowed_callers": ["direct"]
    },
    # filter_results, save_summary も同様
]

# **PTC方式:**
tools = [
    {
        "type": "code_execution_20250825",
        "name": "code_execution"
    },
    {
        "name": "count_words",
        "description": "テキストファイルから単語頻度を分析する",
        "input_schema": {...},
        "allowed_callers": ["code_execution_20250825"]
    },
    # filter_results, save_summary も同様
]
## **プロンプト例:**

messages = [
    {
        "role": "user",
        "content": """以下の3つのツールを順次呼び出して、単語頻度分析を実行してください:

1. count_words: ファイル名 "sample_text.txt" から単語頻度を分析
2. filter_results: 結果をフィルタリング(min_freq=10)
3. save_summary: フィルタリング結果をファイルに保存

最後に、以下の情報を報告してください:
- 保存が完了したこと
- 頻出単語トップ5とその出現回数
- 各頻出単語について、文章の多様性を高めるための類義語や置き換え表現の提案"""
    }
]

response = client.beta.messages.create(
    model="claude-sonnet-4-5",
    betas=["advanced-tool-use-2025-11-20"],
    max_tokens=8192,
    tools=tools,
    messages=messages
)
❯ uv run python run_non_ptc.py

ツール呼び出し: count_words
  パラメータ: {"filename": "sample_text.txt"}
  → 1090件の単語を返しました
ツール呼び出し: filter_results
  パラメータ: {"words": "[40 items]", "min_freq": 10}
  → 40件の単語にフィルタリングしました
ツール呼び出し: save_summary
  → ファイルに保存しました
❯ uv run python run_ptc.py

[server_tool_use] name=code_execution
生成されたPythonコード:
  async def main():
      result1 = await count_words(...)
      ...

[tool_use] name=count_words → 1090件
[tool_use] name=filter_results → 40件
[tool_use] name=save_summary → 保存完了

入力には筆者のKiroのブログ記事の下書きを使用しています。 分析の結果、1090件の単語から頻度10以上の40件が抽出され、 頻出単語トップ5は「Kiro(51回)」「カイハツ(35回)」「タスク(33回)」 「モード(28回)」「AI(27回)」でした。

PTCモードでは、Claudeは以下のようなPythonコードを生成しました:

async def main():
    # 1. count_words: ファイルから単語頻度を分析
    result1 = await count_words({"filename": "sample_text.txt"})
    words_data = json.loads(result1)
    print(f"総単語数: {len(words_data)}")
    
    # 2. filter_results: 最小頻度10でフィルタリング
    result2 = await filter_results({
        "words": words_data,
        "min_freq": 10
    })
    filtered_data = json.loads(result2)
    print(f"フィルタリング後: {len(filtered_data)}件")
    
    # 3. save_summary: 結果を保存
    result3 = await save_summary({
        "result": filtered_data,
        "output_filename": "result-ptc.txt"
    })
    print(f"保存完了: {result3}")

words_data(1090件の単語データ)はサンドボックス内のPython変数として保持され、 Claudeのコンテキストには含まれません。

結果:

項目 Non-PTC PTC 削減率
入力トークン 20,853 5,092 75.6%
出力トークン 710 522 26.5%
合計 21,563 5,614 74.0%
実行時間 36.51秒 27.84秒 23.7%

約74%のトークン削減を達成しました。 これは、count_wordsの結果(1090件の単語データ)が コンテキストに含まれなくなったためです。また推論のステップ数が減り、実行時間も短縮されるのも期待通りです。

Claude CodeやCursorユーザーへの影響

読者の多くはClaude CodeやCursorといったエディタを使っていると思います。
PTCはClaude APIの機能なので、エディタがPTCを採用するかは開発元次第です。

一般の開発者からすると、PTCの恩恵は間接的なものになります。
エディタがPTCを採用すれば、Toolをたくさん設定してもコンテキスト肥大化によるタスク失敗が起きにくくなる、という形です。

一方、エージェント開発をしている人にとっては、コンテキストエンジニアリングのコアな問題解決技術として直接関係します。

余談:Cloudflare Code Modeとの比較

PTCと似たアプローチとして、CloudflareのCode Modeがあります。 両者は「LLMがコードを書き、コードがToolを動かす」という発想を共有しています。

https://blog.cloudflare.com/code-mode/

ただし、目的とアーキテクチャは大きく異なります。

Cloudflare Code Modeの目的: LLMはTool Callよりもコード生成の方が得意である、という観察に基づいています。 Cloudflareはこれをデータ分布の違いで説明しています。 Tool Callで使われる特殊トークンは人工的なフォーマットであり、 LLMはモデル開発者が用意した合成データでしか学習していません。 一方、TypeScriptは数百万のオープンソースプロジェクトという実世界のデータで大量に学習しています。

PTCの目的: 筆者の推測では、Anthropicはcontext rotを含む長文コンテキスト問題の回避をメインに意図していると考えられます。 中間データをコンテキストに入れないことで、トークン消費を削減し、精度低下を防ぎます。

興味深いのは、両者が異なる目的から出発しながら、 同じ構造に収束している点です。 PTCは「コンテキスト肥大化の回避」、Code Modeは「LLMのコード生成能力の活用」という 別々の課題を解決しようとした結果、 どちらも「中間結果をLLMに戻さず、コード内に閉じ込める」という設計に至りました。 Cloudflareはこれを「各Tool呼び出しの出力がニューラルネットワークを経由して 次の呼び出しの入力にコピーされる無駄を省ける」と表現しています。

Q. TypeScript SDK でも Programmatic Tool Calling (PTC) は使えますか?

A. はい、使えます。ただしCode execution toolで生成・実行されるコードは Python のみであり、TypeScript コードをサンドボックス内で実行するわけではありません。呼び出し側は今まで通りにTypeScript SDKで記述できます。

終わりに

PTCは、従来のTool Useが抱えていた長文コンテキスト問題に対する Anthropicのアプローチだと筆者は考えています。 中間データをサンドボックス内に閉じ込めることで、 トークン消費を削減し、精度低下を防ぎます。

最近は、モデル自体がエージェントを実現するパーツを提供する形へとサービス化が進んでいます。 Claude SkillsやCloudflare Code Modeもその流れにあります。 動的に生成したコードをサンドボックスで実行するというモデルへの変化は、その過渡にあります。 AnthropicのPTCは、その先行事例として非常に興味深いですね。

Programmatic tool calling
Claude API Documentation

Subscribe to laiso

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe