9月25日、Shittu Olumideが「Tool Calling vs. Code Execution for AI Agents: Choosing the Right Action Primitive」と題した記事を公開した。AIエージェントにおける2つのアクション基本単位「ツール呼び出し(Tool Calling)」と「コード実行(Code Execution)」の仕組みの違いと、設計上の使い分け基準について詳しく解説している。
「20人分の経費を集計せよ」── ツール呼び出しの見えないコスト
AIエージェントの設計でほとんどのチュートリアルが触れない問題がある。「エージェントはどうやって実際にアクションを起こすか」だ。
具体的なシナリオで考える。「20人の従業員のうち、Q3の出張予算を超過したのは誰か」をエージェントに問うとする。素直に実装すれば、モデルは1人ずつツールを呼び出す。20回の個別呼び出し、それぞれが50〜100行の明細を返し、合計2,000件以上の行データと50KB超の生データがモデルのコンテキストを通過する。モデルが本当に必要だったのは「合計値」だけなのに。
これがツール呼び出しとコード実行の選択に潜む、コスト・レイテンシ・精度への実際的な影響だ。
2つのアクション基本単位の仕組み
ツール呼び出し(Tool Calling)
ツール呼び出しの内部では、モデルは通常のテキストではなく、「これはツール呼び出しである」を示す特殊トークンのペアと、その間にツール名・引数を記述したJSONペイロードを生成する。ホストアプリケーションはそのトークンを検出して生成を停止し、JSONをスキーマに照合してツールを実行し、結果を会話に差し込む。
1ステップずつ実行され、全ての結果がモデルのコンテキストに入る。ログが取りやすく、監査に強い。モデルは各結果を自然言語で「見て」から次の判断を下す。
ここで問題になるのがコンテキスト汚染(context pollution)だ。これは、ツール呼び出しの中間結果がすべてモデルのコンテキストウィンドウに蓄積され、本来の推論に不要な大量のデータがトークンを圧迫する現象を指す。呼び出し回数が増えるほど、モデルが「見なければならない」情報が膨れ上がり、レイテンシの増大・コストの上昇・精度の低下につながる。
コード実行(Code Execution)──Programmatic Tool Calling
Anthropicが提供する**Programmatic Tool Calling**は、別の出発点に立つ。モデルにJSONで1件ずつアクションを記述させるのではなく、実際のPythonスクリプトを書かせ、サンドボックス環境で実行する。
仕組みの核心はツール定義に allowed_callers フィールドを追加し、リクエストに code_execution ツールを加えること。モデルはループ・条件分岐・エラー処理を含む完全なスクリプトを書き、そのスクリプトが内部でツールを呼び出す。個々のツール呼び出し結果はスクリプトが処理し、モデルのコンテキストには戻らない。スクリプトの最終出力だけが返ってくる。
一言で言えば:ツール呼び出しはすべての中間結果をモデルの前に置く。コード実行は、モデルが書いたコードを通じて、何を返すかをモデル自身が決める。
なお、Programmatic Tool Callingの詳細な仕様や利用条件についてはAnthropicの公式ドキュメントを参照されたい。
実装例:同じツールで2つのアプローチを比較
記事では、Open-Meteo(APIキー不要の無料気象API)を使った get_weather 関数を共通ツールとして、両アプローチを実際のコードで比較している。
ツール呼び出し:「ロンドンの天気は?」
1回の検索に1回の呼び出し、結果は小さくモデルに直接返す。この用途ではツール呼び出しが最適だ。
import json
import requests
from anthropic import Anthropic
client = Anthropic() # ANTHROPIC_API_KEY を環境変数から読み込む
def get_weather(city: str) -> dict:
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1},
).json()
if not geo.get("results"):
return {"error": f"Could not find a location named '{city}'"}
lat = geo["results"][0]["latitude"]
lon = geo["results"][0]["longitude"]
forecast = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": lat, "longitude": lon,
"current": "temperature_2m",
"daily": "temperature_2m_max",
"timezone": "auto",
},
).json()
return {
"city": city,
"current_temp_c": forecast["current"]["temperature_2m"],
"week_high_temps_c": forecast["daily"]["temperature_2m_max"],
"unit": "celsius",
}
weather_tool = {
"name": "get_weather",
"description": (
"Get the current temperature and this week's daily high "
"temperatures for a city."
),
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, e.g. 'Lagos'"}
},
"required": ["city"],
},
}
messages = [{"role": "user", "content": "What's the weather like in London right now?"}]
response = client.messages.create(
model="claude-sonnet-5", max_tokens=1024,
tools=[weather_tool], messages=messages,
)
while response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use" and block.name == "get_weather":
result = get_weather(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result),
})
messages.append({"role": "user", "content": tool_results})
response = client.messages.create(
model="claude-sonnet-5", max_tokens=1024,
tools=[weather_tool], messages=messages,
)
for block in response.content:
if block.type == "text":
print(block.text)
while response.stop_reason == "tool_use" のループがツール呼び出しの機械的な核心だ。Claudeがツールを要求するたびに、アプリ側で実行・ラップして会話に追加し、APIを再度呼ぶ。1回の検索ならこれで十分だが、15都市に拡張すると15回のループ、15個のJSONペイロードがコンテキストを埋める。
コード実行:「15都市で最も寒い都市と平均最高気温は?」
同じ get_weather 関数を使いながら、Programmatic Tool Callingに切り替える場合、ツール定義に allowed_callers フィールドを追加し、リクエストに code_execution ツールを加える。こうすることで、モデルはforループを含む完全なスクリプトを生成し、15都市分のデータ取得・比較・集計をスクリプト内で完結させる。モデルのコンテキストには最終結果だけが届き、中間の生データは一切蓄積されない。コンテキスト汚染を構造的に回避できる点が、このアプローチの最大の利点だ。
どちらを選ぶか:判断フレームワーク
記事が提示する選択基準は以下の5軸だ。
| 軸 | ツール呼び出しが適する | コード実行が適する |
|---|---|---|
| 呼び出し回数 | 1〜2回 | 多数(fan-out) |
| データ感度 | 中間結果をモデルに見せてよい | 生データをコンテキストに入れたくない |
| レイテンシ | 許容できる | 並列実行で短縮したい |
| インフラ | サンドボックス不要 | サンドボックス環境が必要 |
| 監査性 | ステップごとにログ取得しやすい | スクリプト単位での監査 |
呼び出しが1〜2件で自然言語の応答が必要なら、ツール呼び出しが正解。呼び出しが多数になりfan-out・集計が発生するなら、コード実行(Programmatic Tool Calling)を検討すべきだ。モデルに算術をさせるより、forループに任せた方が速く、安く、正確である。
2つのアプローチは対立するものではなく、タスクの性質に応じて使い分けることが設計の要点だ。記事はどちらが優れているかという議論ではなく、それぞれが有効に機能する条件を正確に見極めることを強調している。
詳細はTool Calling vs. Code Execution for AI Agents: Choosing the Right Action Primitiveを参照していただきたい。




