Agent Tool Builder(エージェントツールビルダー) は何をしますか?
ツールは、AIエージェントが世界と対話するための手段です。適切に設計されたツールは、エージェントが正しく動作するか、幻覚を見たり、サイレント障害を起こしたり、必要なトークンが10倍以上になるかの違いを生み出します。
このスキルでは、スキーマ設計からエラーハンドリングまでをカバーします。JSON Schemaのベストプラクティス、LLMを実際に支援する説明文の書き方、検証、そしてAIツールの共通言語になりつつあるMCP標準について解説します。
重要な洞察:ツールの説明文はツールの実装よりも重要です。LLMはあなたのコードを見ることはなく、スキーマと説明文のみを見ます。
原則
- LLMの精度には、実装品質よりも説明文の品質が重要
- 20個未満のツールを目指す – それ以上は混乱の原因に
- すべてのツールに明示的なエラーハンドリングが必要 – サイレント障害はエージェントを破壊する
- オブジェクトではなく文字列を返す – LLMはテキストを処理する
- 実行前に検証でゲートを設ける – 拒否、修正、エスカレーションを行い、決してサイレントに失敗しない
- LLMでツールをテストする – 単体テストだけでは不十分
機能
- agent-tools
- function-calling
- tool-schema-design
- mcp-tools
- tool-validation
- tool-error-handling
スコープ(関連スキルへのマッピング)
- multi-agent-coordination → multi-agent-orchestration
- agent-memory → agent-memory-systems
- api-design → api-designer
- llm-prompting → prompt-engineering
ツール
標準規格
- JSON Schema - 使用時:すべてのツール定義 注:ツールスキーマのユニバーサルフォーマット
- MCP (Model Context Protocol) - 使用時:再利用可能でクロスプラットフォームなツールを構築する場合 注:Anthropicのオープン標準で、広く採用されている
フレームワーク
- Anthropic SDK - 使用時:Claudeベースのエージェント 注:ベータ版のツールランナーがほとんどの複雑さを処理
- OpenAI Functions - 使用時:OpenAIベースのエージェント 注:スキーマ準拠を保証するためにstrictモードを使用
- Vercel AI SDK - 使用時:マルチプロバイダーのツールハンドリング 注:プロバイダー間の違いを抽象化
- LangChain Tools - 使用時:LangChainベースのエージェント 注:MCPツールをLangChain形式に変換
パターン
ツールスキーマ設計
エージェント用ツールのための明確で曖昧さのないJSON Schemaを作成する
使用するタイミング: エージェントの新しいツールを定義するとき
ツールスキーマのベストプラクティス:
1. 詳細な説明(最も重要)
悪い例 – 曖昧すぎる:
{
"name": "get_stock_price",
"description": "Gets stock price",
"input_schema": {
"type": "object",
"properties": {
"ticker": {"type": "string"}
}
}
}
良い例 – 包括的:
{
"name": "get_stock_price",
"description": "指定されたティッカーシンボルの現在の株価を取得します。\
ティッカーシンボルは、NYSEやNASDAQなどの米国主要証券取引所に上場している\
公開企業の有効なシンボルである必要があります。\
最新の取引価格を米ドルで返します。\
現在または最近の株価についてユーザーが尋ねた場合に使用します。\
履歴データ、企業情報、予測は提供しません。",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "株式ティッカーシンボル。例:AAPL(Apple Inc.)"
}
},
"required": ["ticker"]
}
}
2. パラメータの説明
すべてのパラメータに必要なもの:
- 何であるか
- 期待される形式
- 値の例
- エッジケース/制限
{
"location": {
"type": "string",
"description": "市区町村と都道府県/国名。形式:米国の場合は'City, State'\
(例:'San Francisco, CA')、海外の場合は'City, Country'\
(例:'Tokyo, Japan')。郵便番号や座標は使用しないでください。"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度単位。指定がない場合はユーザーのロケールにデフォルト設定されます。\
米国ユーザーには'fahrenheit'、それ以外には'celsius'を使用します。"
}
}
3. 可能な限りenumを使用する
enumはLLMを有効な値に制約します:
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "タスクの優先度"
}
"action": {
"type": "string",
"enum": ["create", "read", "update", "delete"],
"description": "実行するCRUD操作"
}
4. 必須パラメータとオプションパラメータ
何が必須かを明示的にする:
{
"type": "object",
"properties": {
"query": {...}, // 必須
"limit": {...}, // デフォルト値付きのオプション
"offset": {...} // オプション
},
"required": ["query"],
"additionalProperties": false // strictモード
}
入力例付きツール
LLMによるツール使用をガイドするための例
使用するタイミング: ネストされたオブジェクトや形式に敏感な入力がある複雑なツールの場合
ツール使用例(Anthropicベータ機能):
例は、スキーマでは表現できない具体的なパターンをClaudeに示します。
複雑な操作では精度が72%から90%に向上します。
{
"name": "create_calendar_event",
"description": "オプションの参加者とリマインダー付きでカレンダーイベントを作成します",
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "イベントのタイトル"},
"start_time": {
"type": "string",
"description": "ISO 8601日時、例:2024-03-15T14:00:00Z"
},
"duration_minutes": {"type": "integer", "description": "イベントの期間(分)"},
"attendees": {
"type": "array",
"items": {"type": "string"},
"description": "参加者のメールアドレス"
}
},
"required": ["title", "start_time", "duration_minutes"]
},
"input_examples": [
{
"title": "チームスタンドアップ",
"start_time": "2024-03-15T09:00:00Z",
"duration_minutes": 30,
"attendees": ["[email protected]", "[email protected]"]
},
{
"title": "クイックチャット",
"start_time": "2024-03-15T14:00:00Z",
"duration_minutes": 15
},
{
"title": "プロジェクトレビュー",
"start_time": "2024-03-15T16:00:00-05:00",
"duration_minutes": 60,
"attendees": ["[email protected]"]
}
]
}
例の設計原則:
- プレースホルダーではなく実際のデータを使用する
- 最小限、部分的、完全な指定のパターンを示す
- ツールあたり1~5例に抑える
- 曖昧なケースに焦点を当てる
ツールエラーハンドリング
LLMの回復を助けるエラーを返す
使用するタイミング: 失敗する可能性のあるツール
エラーハンドリングのベストプラクティス:
情報量の多いエラーを返す
悪い例:
{"error": "Failed"}
{"error": true}
良い例:
{
"error": true,
"error_type": "not_found",
"message": "Location 'Atlantis' not found in weather database.\
Please provide a real city name like 'San Francisco, CA'.",
"suggestions": ["San Francisco, CA", "Los Angeles, CA"]
}
Anthropicツール結果とエラー
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Error: Location 'Atlantis' not found in weather database.\
Please provide a real city name like 'San Francisco, CA'.",
"is_error": true
}
処理すべきエラーカテゴリ
1. 入力検証エラー
- 必須パラメータの欠落
- 形式が無効
- 値が範囲外
2. 外部サービスエラー
- APIが利用不可
- レート制限
- タイムアウト
3. ビジネスロジックエラー
- リソースが見つからない
- 権限が拒否された
- 競合/重複
4. 内部エラー
- 予期しない例外
- データ破損
実装パターン
from dataclasses import dataclass
from typing import Union
@dataclass
class ToolResult:
success: bool
content: str
error_type: str = None
suggestions: list[str] = None
def to_response(self) -> dict:
if self.success:
return {"content": self.content}
return {
"content": f"Error ({self.error_type}): {self.content}",
"is_error": True
}
def get_weather(location: str) -> ToolResult:
# 入力を検証
if not location or len(location) < 2:
return ToolResult(
success=False,
content="Location must be at least 2 characters",
error_type="validation_error"
)
try:
data = weather_api.fetch(location)
return ToolResult(
success=True,
content=f"Temperature: {data.temp}°F, Conditions: {data.conditions}"
)
except LocationNotFound:
return ToolResult(
success=False,
content=f"Location '{location}' not found",
error_type="not_found",
suggestions=weather_api.suggest_locations(location)
)
except RateLimitError:
return ToolResult(
success=False,
content="Weather service rate limit exceeded. Try again in 60 seconds.",
error_type="rate_limit"
)
except Exception as e:
return ToolResult(
success=False,
content=f"Unexpected error: {str(e)}",
error_type="internal_error"
)
MCPツールパターン
Model Context Protocolを使用したツールの構築
使用するタイミング: 再利用可能でクロスプラットフォームなツールを作成する場合
MCPツール実装:
MCP(Model Context Protocol)は、AIエージェントを外部システムに接続するためのAnthropicのオープン標準です。
一度構築すれば、どこでも使用できます。
基本的なMCPサーバー(TypeScript)
import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
const server = new Server({
name: "weather-server",
version: "1.0.0"
});
// ツールを定義
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "get_weather",
description: "Get current weather for a location. Returns\
temperature, conditions, and humidity. Use for weather\
queries about specific cities.",
inputSchema: {
type: "object",
properties: {
location: {
type: "string",
description: "City and state, e.g. 'San Francisco, CA'"
},
unit: {
type: "string",
enum: ["celsius", "fahrenheit"],
default: "fahrenheit"
}
},
required: ["location"]
}
}
]
}));
// ツール呼び出しを処理
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_weather") {
try {
const weather = await fetchWeather(args.location, args.unit);
return {
content: [
{
type: "text",
text: JSON.stringify(weather)
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`
}
],
isError: true
};
}
}
throw new Error(`Unknown tool: ${name}`);
});
// サーバーを起動
const transport = new StdioServerTransport();
await server.connect(transport);
MCPの利点
- LLMプロバイダー間でのユニバーサルな互換性
- 再利用可能なツールライブラリ
- ストリーミングおよびSSEトランスポートのサポート
- 組み込みの可観測性
- ツールアクセス制御
ツールランナーパターン
自動処理のためのSDKツールランナーの使用
使用するタイミング: 手動管理なしでツールループを構築する場合
ツールランナー(Anthropic SDKベータ):
ツールランナーはツール呼び出しループを自動的に処理します:
- Claudeがツールを呼び出したときに実行
- 会話の状態を管理
- エラー時のリトライを処理
- ストリーミングサポートを提供
Pythonの例
import anthropic
from anthropic import beta_tool
client = anthropic.Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "fahrenheit") -> str:
'''Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit, either 'celsius' or 'fahrenheit'
'''
# 実装
return json.dumps({"temperature": "72°F", "conditions": "Sunny"})
@beta_tool
def search_web(query: str) -> str:
'''Search the web for information.
Args:
query: The search query
'''
# 実装
return json.dumps({"results": [...]})
# ツールランナーがループを処理
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[get_weather, search_web],
messages=[
{"role": "user", "content": "What's the weather in Paris?"}
]
)
# 各メッセージを処理
for message in runner:
print(message.content[0].text)
# または最終結果だけを取得
final = runner.until_done()
TypeScript(Zod使用)
import { Anthropic } from '@anthropic-ai/sdk';
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
import { z } from 'zod';
const anthropic = new Anthropic();
const getWeatherTool = betaZodTool({
name: 'get_weather',
description: 'Get the current weather in a given location',
inputSchema: z.object({
location: z.string().describe('City and state, e.g. San Francisco, CA'),
unit: z.enum(['celsius', 'fahrenheit']).default('fahrenheit')
}),
run: async (input) => {
// 型安全な入力!
return JSON.stringify({temperature: '72°F'});
}
});
const runner = anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5',
max_tokens: 1024,
tools: [getWeatherTool],
messages: [{ role: 'user', content: "What's the weather in Paris?" }]
});
for await (const message of runner) {
console.log(message.content[0].text);
}
並列ツール実行
複数のツールを同時に実行する
使用するタイミング: 並列実行可能な独立したツール呼び出しの場合
並列ツール実行:
デフォルトでは、Claudeは1つのレスポンスで複数のツールを呼び出すことができます。
これにより、独立した操作のレイテンシが大幅に削減されます。
並列結果の処理
# Claudeは複数のtool_useブロックを返します:
response.content = [
{"type": "text", "text": "両方の場所を確認します..."},
{"type": "tool_use", "id": "toolu_01", "name": "get_weather",
"input": {"location": "San Francisco, CA"}},
{"type": "tool_use", "id": "toolu_02", "name": "get_weather",
"input": {"location": "New York, NY"}},
{"type": "tool_use", "id": "toolu_03", "name": "get_time",
"input": {"timezone": "America/Los_Angeles"}},
{"type": "tool_use", "id": "toolu_04", "name": "get_time",
"input": {"timezone": "America/New_York"}}
]
# 各tool_useブロックには一意のID、ツール名、および入力が含まれます。
# これらは互いに独立しているため、並行して実行できます。
import asyncio
async def execute_tool(tool_use):
results = await tool_implementations[tool_use.name](**tool_use.input)
return {
"type": "tool_result",
"tool_use_id": tool_use.id,
"content": results
}
# すべてのツール呼び出しを並行して実行
results = await asyncio.gather(*[
execute_tool(tool_use) for tool_use in tool_uses
])
MCPでの並列実行
# MCPサーバーはデフォルトで並列呼び出しを処理します
# サーバーが複数のツール呼び出しを同時に受信した場合、自動的に並行して実行します
# クライアント側での並列実行
import asyncio
async def execute_mcp_tools(session, tool_calls):
async def call_tool(tool_call):
result = await session.call_tool(
tool_call.name,
tool_call.input
)
return result
# すべてのツール呼び出しを並行して実行
return await asyncio.gather(*[
call_tool(tc) for tc in tool_calls
])
最適なパフォーマンスのためのヒント
- 依存関係のない独立したツールを特定する
- データ取得は読み取り系ツールで並列化する
- 書き込み系ツール(作成、更新、削除)は順次実行する
- レート制限とリソースの競合に注意する
- asyncio.gather(Python)またはPromise.all(TypeScript)を使用する
- ツールごとにタイムアウトを設定して、1つの遅いツールが全体をブロックしないようにする