ai-patterns-tool-use-patterns について
LLMのツール呼び出しやエージェントループを実装する開発者向けのスキルです。スキーマ設計、実行回数の制限、入力検証、エラー処理、ツール選択を扱います。
主な機能
- 01JSON Schemaによるツール定義
- 02上限付きツール呼び出しループ
- 03入力検証と構造化エラー
- 04ツール選択の制御
- 05並列呼び出しと承認ゲート
- 06TypeScriptの型安全な実装パターン
活用シーン
- 01プロバイダー横断のツール呼び出し実装
- 02複数段階のエージェントワークフロー構築
- 03並列ツール呼び出しの処理
- 04リスクの高い操作への承認追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill ai-patterns-tool-use-patternsインストール後、Claude Code で/ai-patterns-tool-use-patternsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/advanced.mdcore.md詳細
ツール使用パターン
クイックガイド: ツール使用(関数呼び出し)では、LLM が外部関数を呼び出せます。基本的なパターンは、ツールのスキーマを定義する(パラメーターには JSON Schema を使用)→ ツールとメッセージを LLM に送信する → 応答内の tool_use を検出する → ローカルで実行する → 結果を LLM に返す → モデルがテキストで応答するまで繰り返す、という流れです。無限ループや API 呼び出しの暴走によるコスト増大を防ぐため、すべてのループに最大ステップ数を設定し、実行前にすべてのツール入力を検証し、モデルが復旧できるよう構造化されたエラーを返してください。ツール選択制御(
auto、required、none、特定のツール)を使って、モデルの動作を誘導します。
<critical_requirements>
重要:このスキルを使用する前に
すべてのコードは、CLAUDE.md のプロジェクト規約(kebab-case、名前付きエクスポート、インポート順序、
import type、名前付き定数)に従う必要があります。
(すべてのツールループには最大ステップ数を必ず設定してください。無制限のループは API 呼び出しの無限化やコストの暴走につながります。)
(実行前に、すべてのツール入力引数を必ず検証してください。LLM が生成した引数は信頼できない入力です。)
(ツールの実行に失敗した場合は、必ず構造化されたエラーメッセージをモデルに返してください。エラーを黙って無視したり、空の結果を返したりしてはいけません。)
(ツールのパラメーター定義には必ず JSON Schema を使用してください。主要なプロバイダーはすべてこの形式を必要とします。)
(ツール定義はトークンコストとして扱ってください。すべてのツールスキーマは API 呼び出しのたびに送信されるため、説明は簡潔かつ正確にしてください。)
</critical_requirements>
自動検出: ツール使用、関数呼び出し、tool_calls、tool_use、ツール呼び出しループ、エージェントループ、ツール定義、ツールスキーマ、toolChoice、tool_choice、並列ツール呼び出し、人間参加型、ツール承認、エージェント型ワークフロー、マルチステップエージェント、ツール結果、ツールエラー
使用する場面:
- あらゆるプロバイダーで LLM のツール呼び出し/関数呼び出しを実装する場合
- タスクが完了するまでツールを反復的に呼び出すエージェントループを構築する場合
- 並列ツール呼び出し(1 回の応答内に複数のツール呼び出しがある場合)を処理する場合
- モデルが復旧できるよう、ツールエラーをモデルに報告する場合
- ツール選択(自動、必須、なし、特定のツールを強制)を制御する場合
- 危険なツールの実行前に、人による承認ゲートを追加する場合
- ツール呼び出しを含む応答をストリーミングする場合
対象となる主なパターン:
- ツール定義スキーマ(パラメーターと説明の JSON Schema)
- ツール呼び出しの基本ループ(送信 → 検出 → 実行 → 返却 → 再送信)
- 並列ツール呼び出し(1 回の応答内に複数の呼び出しがある場合の処理)
- エラー処理(ツールの実行失敗をモデルに報告)
- ツール選択制御(自動、必須、なし、特定のツール)
- 会話状態を用いたマルチステップのエージェントワークフロー
- 人間参加型の承認パターン
- TypeScript での型安全なツール定義
- セキュリティ(入力検証、サンドボックス化、最小権限)
- ツール呼び出しを伴うストリーミング
使用しない場面:
- ツール呼び出しを伴わない単純なテキスト生成 — ツールは必要ありません
- 構造化出力/JSON 抽出 — 代わりに、使用するプロバイダーの構造化出力機能を使用してください
- プロバイダー固有の SDK パターン — SDK 固有の API には、使用するプロバイダーの SDK スキルを使用してください
詳細リソース:
- examples/core.md -- ツール定義、ツール呼び出しループ、エラー処理、型安全なツール
- examples/advanced.md -- 並列ツール呼び出し、マルチステップエージェント、人間参加型、ストリーミング、セキュリティ
- reference.md -- 判断フレームワーク、プロバイダー比較、アンチパターンのチェックリスト
## 哲学
ツールの使用は、LLM をテキスト生成器からエージェントへと変える仕組みです。モデルはコードを実行したり、データベースを照会したり、API を呼び出したりすることはできません。構造化されたツール呼び出しを出力して、コードに実行を 要求 することしかできません。実行するのはコードであり、計画するのはモデルです。
基本原則:
- モデルが計画し、コードが実行する -- LLM は、構造化された引数を含むツール呼び出しのリクエストを出力します。コードはそれを検証、実行し、結果を返します。モデルに任意のコードを直接実行させてはいけません。
- エージェントはループで動作する -- シンプルな天気予報ボットから複雑なコーディングアシスタントまで、すべてのエージェントは同じループに従います。LLM が判断する -> システムが実行する -> 結果をフィードバックする -> 繰り返す。複雑さを生み出すのはツールと状態であり、ループ自体ではありません。
- ツールはスキーマである -- ツール定義は JSON Schema であり、どの関数が存在するか、どのようなパラメーターを受け取るか、いつ使用するかをモデルに伝えます。説明が優れているほど、ツールの選択と引数の品質が向上します。
- エラーは情報である -- ツールが失敗したときは、構造化されたエラーメッセージをモデルに返します。モデルは多くの場合、異なる引数で再試行したり、別のツールを選んだり、ユーザーに失敗を説明したりして復旧できます。
- 多層防御 -- LLM が生成した引数は信頼できない入力です。スキーマを検証し、型を強制し、引数の範囲を制限し、実行をサンドボックス化し、危険な操作には承認を必須にしてください。
ツール呼び出しを使用する場面:
- モデルが持っていない現実世界のデータ(天気、データベース、API)が必要なタスク
- 副作用(メールの送信、レコードの作成、ファイル操作)が必要なタスク
- 中間データの検索を伴う複数段階の推論が必要なタスク
- モデルが確実には実行できない計算(数学、コードの実行)が必要なタスク
ツール呼び出しを使用しない場面:
- モデルが学習データだけで回答できる場合
- 構造化された JSON 出力だけが必要な場合(代わりに構造化出力機能を使用してください)
- 「ツール」が関数を装った単なるプロンプトエンジニアリングである場合
- 決定論的な動作が必要な場合(ツール呼び出しではモデルの判断による非決定性が加わります)
基本パターン
パターン 1:ツール定義スキーマ
すべてのツール定義には、名前、説明、パラメータースキーマの 3 つの要素があります。説明が最も重要です。ツールを呼び出すかどうか、また引数をどのように構成するかというモデルの判断を導きます。
// Provider-agnostic tool definition shape
interface ToolDefinition {
name: string; // ^[a-zA-Z0-9_-]{1,64}$
description: string; // When and why to use this tool
parameters: JsonSchema; // JSON Schema for input arguments
}
良いツール定義
// Good: precise description, constrained parameters, .describe() on each property
const VALID_UNITS = ["celsius", "fahrenheit"] as const;
const getWeatherTool: ToolDefinition = {
name: "get_current_weather",
description:
"Get the current weather conditions for a specific city. " +
"Returns temperature, humidity, and conditions. " +
"Use this when the user asks about current weather, NOT forecasts.",
parameters: {
type: "object",
properties: {
location: {
type: "string",
description: "City name and optional country code, e.g. 'London, UK'",
},
unit: {
type: "string",
enum: VALID_UNITS,
description: "Temperature unit. Defaults to celsius if not specified.",
},
},
required: ["location"],
},
};
良い理由: 説明では、ツールが何を返すか、いつ使用するか、いつ使用しないかを説明しています。パラメーターには説明と制約(列挙型)があります。有効な値には名前付き定数を使用しています。
悪いツール定義
// Bad: vague description, no parameter descriptions, no constraints
const weatherTool: ToolDefinition = {
name: "weather",
description: "Gets weather", // BAD: too vague
parameters: {
type: "object",
properties: {
loc: { type: "string" }, // BAD: no description, cryptic name
u: { type: "string" }, // BAD: no enum constraint
},
},
};
悪い理由: 曖昧な説明によりツールの選択を誤りやすくなり、パラメーターの説明がないと不正な引数が生成されやすくなります。また、列挙型による制約がないと無効な値が使われやすくなり、分かりにくいパラメーター名はモデルを混乱させます。
パターン 2:基本的なツール呼び出しループ
ツールを使用するための基本パターンです。ツール定義を含むメッセージを送信し、応答にツール呼び出しが含まれているか確認して実行し、結果を返して、モデルに最終応答を生成させます。完全な実装については、examples/core.md を参照してください。
const MAX_TOOL_STEPS = 10;
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
const response = await callLLM({ messages, tools });
if (!response.toolCalls?.length) return response.text;
messages.push({ role: "assistant", toolCalls: response.toolCalls });
for (const tc of response.toolCalls) {
messages.push({
role: "tool",
toolCallId: tc.id,
content: JSON.stringify(await executeTool(tc)),
});
}
}
重要なポイント: 名前付き定数(MAX_TOOL_STEPS)を使用した上限付きの for ループを必ず使い、while (true) は決して使わないでください。ツールの結果より前に、必ずアシスタントメッセージ(ツール呼び出しを含む)を追加してください。ステップ数の上限に達した場合は、適切に終了する処理を含めてください。
ツールが失敗した場合、クラッシュさせるのではなく、構造化されたエラーをモデルに返します。モデルは、再試行したり、別の方法を選択したり、失敗について説明したりすることで、復旧できることがよくあります。完全な検証パイプラインについては、examples/core.md を参照してください。
// Four-step validation: tool exists -> JSON parses -> schema validates -> execution succeeds
async function executeTool(toolCall: ToolCall): Promise<ToolResult> {
const handler = toolRegistry[toolCall.name];
if (!handler)
return { success: false, error: `Unknown tool: "${toolCall.name}"` };
const validated = handler.schema.safeParse(toolCall.arguments);
if (!validated.success)
return { success: false, error: `Invalid args: ${validated.error}` };
try {
return { success: true, data: await handler.execute(validated.data) };
} catch (error) {
return {
success: false,
error: `Tool failed: ${error instanceof Error ? error.message : "Unknown"}`,
};
}
}
重要なポイント: ツールの存在を必ず確認し、JSON 引数を解析してスキーマに照らして検証し、実行時のエラーを捕捉します。モデルが復旧できるよう、構造化された { success, data?, error? } の結果を返します。ツールのエラーでループを決してクラッシュさせないでください。
パターン 4:ツール選択の制御
モデルがツールを使用するかどうか、またどのように使用するかを制御します。主要なプロバイダーはすべて、4 つのモードに対応しています。
type ToolChoice =
| "auto" // Model decides whether to call tools (default)
| "required" // Model MUST call at least one tool
| "none" // Model MUST NOT call any tools
| { name: string }; // Model MUST call this specific tool
各モードを使用するタイミング
// auto (default) -- let the model decide
const response = await callLLM({
messages,
tools,
toolChoice: "auto",
});
// required -- force tool use (e.g., first step of an agent must act)
const response = await callLLM({
messages,
tools,
toolChoice: "required",
});
// none -- disable tools (e.g., final response must be text only)
const response = await callLLM({
messages,
tools,
toolChoice: "none",
});
// specific tool -- force a particular tool (e.g., classification step)
const response = await callLLM({
messages,
tools,
toolChoice: { name: "classify_intent" },
});
判断の目安:
| モード | 使用する場面 |
|---|---|
auto | 汎用エージェント――モデルがコンテキストに基づいて判断する |
required | エージェントの最初のステップで必ずツールを呼び出す必要がある場合(例:データ検索) |
none | 最終応答の生成時――以降のツール呼び出しを許可しない場合 |
{ name } | 特定のツールを使う必要があるパイプラインのステップ(例:抽出、分類) |
使用するタイミング: ワークフローの特定の段階で、ツールの動作を制限または保証する必要がある場合
パターン 5:TypeScript での型安全なツール定義
Zod スキーマを使った型付きレジストリパターンにより、ツール定義のコンパイル時の安全性と、実行時の検証を実現します。ToolRegistry クラスの完全な実装については、examples/core.md を参照してください。
import { z } from "zod";
// Define tool with schema + execute function -- TypeScript infers argument types
function defineTool<T extends z.ZodType>(config: {
name: string;
description: string;
schema: T;
execute: (args: z.infer<T>) => Promise<unknown>;
}) {
return config;
}
使用するタイミング: ツール呼び出しを実装するあらゆる TypeScript プロジェクト――Zod は実行時に検証を行い、TypeScript はスキーマとハンドラーの不一致をコンパイル時に検出します
<decision_framework>
判断フレームワーク
ツール呼び出しは必要ですか?
Does the task require information the model doesn't have?
+-- YES -> Tool calling (fetch data from APIs, databases, files)
+-- NO -> Does the task require side effects?
+-- YES -> Tool calling (send email, create record, execute code)
+-- NO -> Do you need structured JSON output?
+-- YES -> Use structured output features (NOT tool calling)
+-- NO -> Plain text generation, no tools needed
どのループパターンを使いますか?
How many tools might the model call?
+-- Single tool call per request
| +-- Simple request-response with one tool execution
| +-- No loop needed, just one round-trip
+-- Multiple sequential tool calls
| +-- Use the bounded tool call loop (Pattern 2)
| +-- Set MAX_TOOL_STEPS based on task complexity
+-- Multiple parallel tool calls in one response
| +-- Execute all tool calls concurrently (Promise.all)
| +-- Then return all results and loop
+-- Complex multi-step agent
+-- Use the bounded loop with conversation state
+-- Add human-in-the-loop for dangerous operations
+-- Consider per-step tool filtering
ツールのエラーをどのように処理しますか?
Tool execution failed. What to do?
+-- Return structured error to the model
| +-- Include error message and context
| +-- Model can retry, choose alternative, or explain failure
+-- NEVER: silently return empty result
+-- NEVER: crash the loop
+-- NEVER: retry automatically without telling the model
(the model should decide whether to retry)
</decision_framework>
<red_flags>
危険信号
優先度の高い問題:
- ステップカウンターのない無制限のツールループ(
while (true))――無限に API を呼び出し続け、コストが際限なく増大するリスクがあります - 検証せずにツール引数を実行すること――LLM が生成した引数は信頼できない入力です。ユーザー入力と同様に扱ってください
- ツールのエラーを黙って握りつぶすこと(
nullや{}を返すなど)――モデルは、知らされていないエラーから復旧できません - サンドボックス化せずに、ツール引数から任意のコードを実行できるようにすること――プロンプトインジェクションによってコード実行にエスカレートするおそれがあります
- 「データを取得します」のようなツールの説明――説明が曖昧だと、誤ったツール選択や不正な引数につながります
優先度が中程度の問題:
- 関連するツールが一部だけなのに、すべての API 呼び出しで全ツールを送信すること――トークンを浪費し、モデルを混乱させます
- ツールの結果より前に、ツール呼び出しを含むアシスタントメッセージを会話履歴に含めないこと――メッセージの順序が崩れます
- ツールを使うのが適切でない場合の代替策なしに
tool_choice: "required"を使用すること――意味のないツール呼び出しを強制します - 生のデータベース行や API の完全な応答をツールの結果として返すこと――無関係なデータでコンテキストを圧迫します。要約するか、切り詰めてください
- ツールの呼び出しと結果を記録しないこと――本番環境でエージェントの動作をデバッグできなくなります
よくある間違い:
- ツール呼び出しの引数は、解析済みのオブジェクトではなく JSON 文字列として届くことを忘れる――使用前に必ず
JSON.parse()してください - ツールを利用できる状態なら、モデルが必ずツールを呼び出すと思い込む――
autoモードでは、テキストで直接応答することがあります - ツール呼び出しを構造化出力として扱う――両者は異なる問題を解決します(アクションとデータ抽出)
- ツールの実装ではなく、ツールの説明にビジネスロジックを記述すること――説明は選択を導くものであり、実行を担うものではありません
注意点とエッジケース:
- ツール定義は API 呼び出しのたびにトークンを消費します――詳細なスキーマを持つツールが 10 個あると、リクエストごとに 1000 以上のトークンを使うことがあります
- 並列のツール呼び出しは任意の順序で届く可能性があります――実行順序が定義順と一致すると決めつけないでください
- 定義にないツール名や引数を幻覚するモデルもあります――ツール名がレジストリに存在することを必ず検証してください
- ツール呼び出しを含むストリーミング応答では、解析する前に部分的な JSON チャンクを蓄積する必要があります――引数は一度に届くのではなく、段階的に届きます
- 非常に大きなツール結果(4000 トークン超)を返すと、会話がコンテキスト上限を超える可能性があります――大きな結果は切り詰めるか、要約してください
- モデルは、すべてのツール呼び出しと結果を含む会話の全コンテキストを保持します――エージェントの実行時間が長くなると、トークン使用量が大幅に増加します
- プロバイダーによって、ツールの結果に異なるメッセージ形式(
role: "tool"とコンテンツブロックなど)が使われます――これをcallLLMラッパーで抽象化してください
</red_flags>
<critical_reminders>
重要な注意事項
すべてのコードは CLAUDE.md のプロジェクト規約(ケバブケース、名前付きエクスポート、インポート順序、
import type、名前付き定数)に従う必要があります
(すべてのツールループに最大ステップ数の上限を必ず設けてください――無制限のループは API の無限呼び出しやコストの際限ない増大につながります)
(実行前にすべてのツール入力引数を必ず検証してください――LLM が生成した引数は信頼できない入力です)
(ツールの実行に失敗した場合は、構造化されたエラーメッセージを必ずモデルに返してください――エラーを黙って握りつぶしたり、空の結果を返したりしてはいけません)
(ツールパラメーターの定義には必ず JSON Schema を使用してください――主要なプロバイダーはすべてこの形式を要求します)
(ツール定義はトークンコストとして必ず考慮してください――すべての API 呼び出しで各ツールのスキーマが送信されるため、説明は簡潔かつ正確にしてください)
これらのルールに従わないと、無限ループで API コストが増大したり、検証されていない入力を実行したり、モデルが復旧できないまま黙って失敗したりするエージェントが生まれます。
</critical_reminders>