Tool Use(関数呼び出し)
LLM に「外部関数」を提供して、自律的に呼ばせる仕組み。 エージェントの中核で、検索・DB アクセス・コード実行などを LLM 任せにできる。
仕組み
- API リクエストに tools 定義を渡す
- モデルが「ツール呼び出すべき」と判断したら
tool_useブロックを返す - こちら側で関数を実行
- 結果を
tool_resultブロックとして履歴に追加 - 再度モデル呼び出し → 最終応答(または別ツール)
User: "東京の天気は?"
↓
LLM: "tool_use: get_weather(city='Tokyo')"
↓ (こちらで実行)
Tool: { temp: 18, condition: "晴れ" }
↓
LLM: "東京は晴れで気温 18°C です。"
ツールの定義
JSON Schema で引数を定義する。name / description / input_schema が必須。
const tools = [
{
name: "get_weather",
description: "指定都市の現在天気を取得します",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "都市名(英語)" },
unit: { type: "string", enum: ["celsius", "fahrenheit"], default: "celsius" }
},
required: ["city"]
}
}
]
モデルは description を読んで「いつ呼ぶか」を判断する。
関数名だけでは曖昧な場合は具体例や使用条件まで書く。
ツール付きリクエスト
const res = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages: [{ role: "user", content: "東京の天気は?" }]
})
console.log(res.stop_reason) // "tool_use"
console.log(res.content)
// [
// { type: "text", text: "天気を調べます。" },
// { type: "tool_use", id: "toolu_...", name: "get_weather", input: { city: "Tokyo" } }
// ]
結果を返す
const toolUse = res.content.find(c => c.type === "tool_use")
const toolResult = await getWeather(toolUse.input.city) // 実関数を呼ぶ
const final = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
tools,
messages: [
{ role: "user", content: "東京の天気は?" },
{ role: "assistant", content: res.content },
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(toolResult)
}
]
}
]
})
エージェントループ
ツールが連鎖するときはwhile で stop_reason を見る。
async function runAgent(userInput: string) {
const messages = [{ role: "user", content: userInput }]
while (true) {
const res = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools,
messages
})
messages.push({ role: "assistant", content: res.content })
if (res.stop_reason !== "tool_use") {
// ツール呼び出しが終わったので最終応答
return res.content.find(c => c.type === "text")?.text
}
// ツール群を実行(並列可)
const toolUses = res.content.filter(c => c.type === "tool_use")
const toolResults = await Promise.all(
toolUses.map(async (t) => ({
type: "tool_result",
tool_use_id: t.id,
content: JSON.stringify(await dispatch(t.name, t.input))
}))
)
messages.push({ role: "user", content: toolResults })
}
}
並列ツール呼び出し
Claude は1 ターンで複数 tool_use を返すことができる。 independent ならPromise.all で並列実行すると速い。
tool_choice の制御
| tool_choice | 意味 |
|---|---|
{ "type": "auto" } | モデルが判断(デフォルト) |
{ "type": "any" } | 必ず何かツールを呼ぶ |
{ "type": "tool", "name": "x" } | 必ず x を呼ぶ |
{ "type": "none" } | ツール禁止 |
JSON 出力したいときの定石
ダミーの extract ツールを定義し tool_choice: tool で強制呼出 → 構造化出力が確定する。
tool_result でエラーを返す
{
type: "tool_result",
tool_use_id: "...",
content: "DB connection failed",
is_error: true
}
エラーを返すとモデルがリトライや別ツールへ切替を試みる。
Anthropic 提供の組み込みツール
- computer_use: 画面を撮影 → クリック / タイプ で OS を操作
- text_editor: ファイル編集(Claude Code 内部で使用)
- bash: シェルコマンド実行
- web_search: Web 検索
Computer Use は仮想マシン内で実行する前提。直接ホスト OS を触らせるのは危険。
MCP(Model Context Protocol)
Anthropic が提唱するツール提供の標準。 MCP サーバー(GitHub / DB / Slack...)を立てれば、Claude / 各種クライアントから共通プロトコルで接続できる。
- JSON-RPC over stdio / SSE / HTTP
- Resources / Tools / Prompts の 3 概念
- Claude Desktop / Claude Code / Cursor などで対応
セキュリティと権限
- ツールは最小権限で(DB は read-only / 削除系は別エンドポイント)
- 金額・通信先などは事前バリデーション
- ユーザー確認が要る操作(送金・削除)は承認フローを挟む
- 引数をZod や JSON Schema で再検証してから実行
- シェル実行系はサンドボックス(Docker / Firecracker)
失敗パターン
| 症状 | 対処 |
|---|---|
| 呼ばれるべきツールが呼ばれない | description を具体化、Few-shot で例示 |
| 引数が間違っている | required / enum / default を明示 |
| 無限ループ | 最大ステップ数を制限、進捗を観測 |
| 関係ないツールも呼ぶ | tools 数を絞る、tool_choice で制限 |
| tool_result の中身を読まない | 大きすぎる結果を要約、構造化 |
ツール多すぎ問題
ツールが 30 個を超えると選択精度が落ちる。
- カテゴリで絞り込んで動的に渡す
- 2 段階構成(コーディネータ → 専門エージェント)
- RAG で「このユーザー入力に関連するツール」を検索して投入
具体例: 簡易リサーチエージェント
const tools = [
{
name: "search",
description: "Web を検索してタイトルと URL を返す",
input_schema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"]
}
},
{
name: "fetch_url",
description: "URL を取得して本文を返す",
input_schema: {
type: "object",
properties: { url: { type: "string" } },
required: ["url"]
}
}
]
// このループで Claude は
// search → fetch_url を複数回 → まとめ
// と自律的に動く
テストとデバッグ
- 個別ツールのユニットテスト(LLM を呼ばずに)
- 固定入力でのループのスナップショットテスト
- OpenTelemetry / LangSmith / Helicone で全 step を可視化
- LLM レスポンスを録画して再生(VCR 的な仕組み)
ツールは細かく作りすぎず、汎用的に。Unix のコマンドと同じで、組み合わせで力を発揮する。 1 ツール 1 責務、副作用は明示、引数はシンプル。