Claude SDK Tool Runner とは
Claude API でツールを使ったアプリケーションを構築する際、従来は「Claude にリクエスト → ツール呼び出しを検出 → ツールを実行 → 結果を Claude に返す」というループを自分で実装する必要がありましました。この繰り返し処理は定型的ですが、エラー処理やコンテキスト管理を含めると複雑になりがちです。
Tool Runner は、このツール呼び出しループを SDK 側で自動管理してくれるベータ機能です。Python・TypeScript・Ruby の各 SDK で利用でき、開発者はツールの定義と実装に集中できます。
Tool Runner が提供する主な機能は以下の通りです。
- 自動ループ処理: Claude がツールを呼び出すたびに、結果を自動的にフィードバックして次のレスポンスを取得
- 型安全な入力バリデーション: Tool Helpers と組み合わせて、ツール入力のスキーマ検証を自動化
- 自動コンパクション: トークン使用量が閾値を超えると、会話の要約を自動生成してコンテキストを管理
- ストリーミング対応: リアルタイムでレスポンスを受け取りながらループを実行
従来のツール呼び出しループとの違い
まず、Tool Runner を使わない従来のパターンを確認しましょう。
import anthropic
client = anthropic.Anthropic()
# ツール定義
tools = [
{
"name": "get_weather",
"description": "指定した都市の天気を取得",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"}
},
"required": ["city"]
}
}
]
def get_weather(city: str) -> str:
# 実際の天気API呼び出し(ここではモック)
return f"{city}の天気: 晴れ、気温22°C"
# 手動ループ
messages = [{"role": "user", "content": "東京と大阪の天気を教えてください"}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
# stop_reason が "end_turn" ならループ終了
if response.stop_reason == "end_turn":
break
# ツール呼び出しを処理
for block in response.content:
if block.type == "tool_use":
result = get_weather(block.input["city"])
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
}]
})
# 最終レスポンスからテキストを抽出
final_text = next(b.text for b in response.content if hasattr(b, "text"))
print(final_text)このコードでも動作しますが、ループ管理・メッセージの追加・エラー処理がすべて開発者の責任です。Tool Runner を使うとこれが大幅に簡潔になります。
Tool Helpers でツールを定義する
Tool Runner を使う前に、Tool Helpers でツールを型安全に定義する方法を見ていきます。
Python(Pydantic ベース)
import anthropic
from pydantic import BaseModel, Field
client = anthropic.Anthropic()
# Pydantic モデルでツール入力を定義
class GetWeatherInput(BaseModel):
city: str = Field(description="天気を取得する都市名")
unit: str = Field(default="celsius", description="温度の単位(celsius/fahrenheit)")
# @tool デコレータでツール関数を定義
@anthropic.tool(name="get_weather", description="指定した都市の現在の天気を取得します")
def get_weather(input: GetWeatherInput) -> str:
"""天気情報を返すツール"""
return f"{input.city}の天気: 晴れ、気温22°C({input.unit})"
# ツール定義から自動的にJSON Schemaが生成される
print(get_weather.to_params())
# 出力例:
# {
# "name": "get_weather",
# "description": "指定した都市の現在の天気を取得します",
# "input_schema": {
# "type": "object",
# "properties": {
# "city": {"type": "string", "description": "天気を取得する都市名"},
# "unit": {"type": "string", "default": "celsius", ...}
# },
# "required": ["city"]
# }
# }TypeScript(Zod ベース)
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
const client = new Anthropic();
// Zod スキーマでツール入力を定義
const GetWeatherInput = z.object({
city: z.string().describe("天気を取得する都市名"),
unit: z.enum(["celsius", "fahrenheit"]).default("celsius").describe("温度の単位"),
});
// betaZodTool でツールを作成
const weatherTool = client.betaZodTool({
name: "get_weather",
description: "指定した都市の現在の天気を取得します",
schema: GetWeatherInput,
execute: async (input) => {
// input は自動的に型推論される
return `${input.city}の天気: 晴れ、気温22°C(${input.unit})`;
},
});Tool Helpers を使うことで、入力値のバリデーションが自動で行われ、不正な型の入力が渡されるとエラーが発生します。
Tool Runner でエージェントループを構築する
Python での実装
import anthropic
from pydantic import BaseModel, Field
client = anthropic.Anthropic()
class SearchInput(BaseModel):
query: str = Field(description="検索クエリ")
class CalculateInput(BaseModel):
expression: str = Field(description="計算式")
@anthropic.tool(name="web_search", description="Webを検索して情報を取得します")
def web_search(input: SearchInput) -> str:
return f"検索結果: '{input.query}' に関する最新情報..."
@anthropic.tool(name="calculate", description="数式を計算します")
def calculate(input: CalculateInput) -> str:
try:
result = eval(input.expression) # 本番ではsafe_evalを使用
return f"計算結果: {input.expression} = {result}"
except Exception as e:
return f"計算エラー: {e}"
# Tool Runner でエージェントループを実行
result = client.messages.tool_runner(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=[web_search, calculate],
messages=[{
"role": "user",
"content": "日本の人口は約何人ですか?また、世界人口の何%にあたるか計算してください。"
}],
)
# result はイテラブル — 各メッセージを順に処理
for message in result:
print(f"[{message.role}]")
for block in message.content:
if hasattr(block, "text"):
print(block.text)
elif block.type == "tool_use":
print(f" → ツール呼び出し: {block.name}({block.input})")TypeScript での実装
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";
const client = new Anthropic();
const searchTool = client.betaZodTool({
name: "web_search",
description: "Webを検索して情報を取得します",
schema: z.object({ query: z.string().describe("検索クエリ") }),
execute: async (input) => {
return `検索結果: '${input.query}' に関する最新情報...`;
},
});
const calcTool = client.betaZodTool({
name: "calculate",
description: "数式を計算します",
schema: z.object({ expression: z.string().describe("計算式") }),
execute: async (input) => {
try {
// 本番ではmathjs等の安全なパーサーを使用
const result = Function(`"use strict"; return (${input.expression})`)();
return `計算結果: ${input.expression} = ${result}`;
} catch (e) {
return `計算エラー: ${e}`;
}
},
});
// Tool Runner でエージェントループを実行
const runner = client.messages.toolRunner({
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools: [searchTool, calcTool],
messages: [{
role: "user",
content: "日本の人口は約何人ですか?また、世界人口の何%にあたるか計算してください。",
}],
});
// ストリーミングで各メッセージを処理
for await (const message of runner) {
console.log(`[${message.role}]`);
for (const block of message.content) {
if ("text" in block) {
console.log(block.text);
} else if (block.type === "tool_use") {
console.log(` → ツール呼び出し: ${block.name}(${JSON.stringify(block.input)})`);
}
}
}自動コンパクションでコンテキストを管理する
長時間のエージェントセッションでは、ツール呼び出しの結果が蓄積してトークン数が膨大になります。Tool Runner の自動コンパクション機能は、トークン使用量が閾値を超えた際に会話の要約を自動生成し、コンテキストウィンドウ内に収めます。
# Python: 自動コンパクション付き Tool Runner
result = client.messages.tool_runner(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=[web_search, calculate],
messages=messages,
# コンパクション設定
compaction={
"enabled": True,
"threshold_tokens": 50000, # この値を超えたら要約を生成
},
)// TypeScript: 自動コンパクション付き Tool Runner
const runner = client.messages.toolRunner({
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools: [searchTool, calcTool],
messages,
compaction: {
enabled: true,
thresholdTokens: 50000,
},
});コンパクションが発生すると、古いメッセージが要約に置き換えられ、重要な情報は保持されたまま会話を続行できます。
strict: true で入力スキーマを厳密に検証する
Structured Outputs の strict: true オプションをツール定義に追加すると、Claude が生成するツール呼び出しの入力が必ず指定したスキーマに一致することが保証されます。
tools = [
{
"name": "create_user",
"description": "新しいユーザーを作成します",
"strict": True, # スキーマの厳密な検証を有効化
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0}
},
"required": ["name", "email", "age"],
"additionalProperties": False # strict: true 時は必須
}
}
]strict: true を使用すると、型の不一致やフィールドの欠落がなくなるため、ツール実行時のランタイムエラーを大幅に削減できます。
全体を振り返って
Claude SDK の Tool Runner と Tool Helpers を使えば、ツール呼び出しの自動ループ構築が大幅に簡略化されます。従来は手動で管理していたメッセージのやり取り・エラー処理・コンテキスト管理が SDK 側に委譲され、開発者はビジネスロジックに集中できます。
ツール定義の方法については Claude API ツール活用の完全ガイド を、TypeScript SDK の型安全な使い方は TypeScript SDK 型安全プロダクションガイド をご覧ください。API のコスト管理に関しては Claude API トークンカウントとコスト最適化ガイド も参考になります。