← 기사 목록
日本語https://zenn.dev/topics/llm/feed

AIエージェントのツール定義設計原則:スキーマ・命名・レスポンスの実践ガイド

추출된 키워드

25
ツール定義·5JSON Schema·5AIエージェント·5Anthropic·4OpenAI·4Claude·4Tool Search·4input_examples·4トークン消費·3detail_level·3defer_loading·3LLM·3enum制約·3strict mode·3Tool Use·3Function Calling·3MCPサーバー·2Python·2TypeScript·2pydantic·2GitHub·2Slack·2Composio·2ISO 8601·2IATA·2

원문

21,005
AIエージェントのツール定義設計原則:スキーマ・命名・レスポンスの実践ガイド

AIエージェントのツール定義設計原則:スキーマ・命名・レスポンスの実践ガイド

AIエージェントのツール定義設計原則:スキーマ・命名・レスポンスの実践ガイド

この記事でわかること

  • AIエージェントのツール定義(JSON Schema)を設計するための7つの原則
  • 命名・説明文・パラメータ設計の具体的なベストプラクティス
  • レスポンス設計でトークン消費を60%以上削減する手法
  • 大規模ツールセット(30個以上)でも精度を維持するTool Search・動的発見の仕組み
  • Anthropic Claude・OpenAI双方の公式ガイドラインに基づく2026年時点の推奨事項

対象読者

  • 想定読者: AIエージェントを開発・運用する中級以上のエンジニア
  • 必要な前提知識:
    • LLMのFunction Calling / Tool Useの基本概念
    • JSON Schemaの基礎(
      type
      properties
      required
      の意味)
    • Python または TypeScript の基礎文法

結論・成果

ツール定義の品質は、エージェント全体の性能を左右する最大のレバレッジポイントです。Anthropicの公式ガイドラインによると、説明文を3-4文以上に充実させるだけでツール選択の精度が大幅に向上し、

input_examples
の追加により複雑なパラメータの正確な指定率が72%から90%に改善したと報告されています。さらにレスポンス設計の最適化では、詳細/簡潔の切替パラメータ導入でトークン消費を206トークンから72トークンへ約65%削減した事例があります。

本記事では、Anthropic・OpenAI双方の2026年時点の公式ドキュメントとエンジニアリングブログに基づき、ツール定義設計の7つの原則を体系的に解説します。

ツール定義が重要な理由を理解する

AIエージェントがツールを使う流れは、「ツール定義の読み取り → 呼び出し判断 → 引数生成 → 結果の解釈」という4段階で構成されます。このうち最初の「ツール定義の読み取り」がすべての精度を決定します。

Anthropicのエンジニアリングブログ「Writing tools for agents」では、ツール定義の設計について次のように述べられています。「曖昧なプロンプトと弱いツールスキーマは、モデルの品質そのもの以上に本番環境での障害を引き起こす」。つまり、どれほど高性能なLLMを使っても、ツール定義の設計が不十分であればエージェントは正しく動作しません。

以下では、ツール定義を構成する4つの要素(命名・説明文・パラメータ・レスポンス)それぞれの設計原則を解説します。

原則1〜2:命名と説明文を設計する

原則1:意味のある名前空間で命名する

ツール名はLLMが最初に目にする識別子です。名前だけで「何をするツールか」が分かるように設計します。

命名規則:

ルール良い例悪い例
サービス名をプレフィックスに
github_list_prs
list_prs
動詞+対象で構成
slack_send_message
slack_message
正規表現
^[a-zA-Z0-9_-]{1,64}$
に準拠
asana_projects_search
search projects (asana)

複数サービスのツールを提供する場合、名前空間(プレフィックス)の一貫性がツール選択精度に直結します。Anthropicのガイドラインでは「サービスやリソースを跨ぐツールにはプレフィックスを付けよ」と明記されています。

from pydantic import BaseModel, Field

class GitHubListPRs(BaseModel):
    """github_list_prs ツールのパラメータ定義"""
    repo: str = Field(description="リポジトリ名(owner/repo形式、例: anthropics/claude-code)")
    state: str = Field(
        default="open",
        description="PRの状態フィルタ",
        json_schema_extra={"enum": ["open", "closed", "all"]}
    )
    limit: int = Field(
        default=10,
        description="取得件数の上限(1-100)",
        ge=1,
        le=100
    )

よくある間違い: ツール名を

data
process
のような汎用的な動詞にしてしまうと、複数ツールがある環境でLLMが正しいツールを選択できなくなります。ツール数が20を超えると、この問題は顕著になります。

原則2:説明文は3-4文以上で書く

Anthropicの公式ドキュメントでは、ツール説明文について「これがツール性能における最も重要な要素」と明言しています。説明文には以下の5点を含めます。

  • 何をするか: ツールの機能
  • いつ使うか: 使用すべき場面
  • いつ使わないか: 他のツールを使うべき場面
  • 何を返すか: レスポンスの内容
  • 制約・注意点: 既知の制限事項
{
  "name": "get_stock_price",
  "description": "指定されたティッカーシンボルの現在の株価を取得します。ティッカーシンボルはNYSEまたはNASDAQに上場している企業の有効なシンボルである必要があります。最新の取引価格をUSDで返します。ユーザーが特定の株の現在価格または最新価格について質問した場合に使用してください。株価以外の企業情報(財務諸表、ニュース等)はこのツールでは取得できません。"
}

以下は悪い例です。

{
  "name": "get_stock_price",
  "description": "株価を取得する"
}

この差は実際のエージェント性能に直結します。曖昧な説明文では、LLMが「このツールで企業の決算情報も取れるのでは」と誤解してツールを呼び出し、期待と異なる結果を受け取って無駄なリトライが発生します。

このアプローチでの注意点として、説明文を長くしすぎるとトークン消費が増加します。1ツールあたり100-200トークン程度が目安です。ツール数が多い場合は、後述するTool Searchと組み合わせて必要なツールだけを動的にロードする設計を検討してください。

原則3〜4:パラメータスキーマを設計する

原則3:enum制約とrequired/optionalを厳密に定義する

パラメータ設計では「不正な状態を表現不可能にする」(Make invalid states unrepresentable)という原則が重要です。自由テキストではなくenum制約を使い、必須と任意を明確に分離します。

enum制約の効果:

{
  "name": "search_products",
  "input_schema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "検索キーワード(例: 'ワイヤレスイヤホン')"
      },
      "category": {
        "type": "string",
        "enum": ["electronics", "clothing", "food", "books", "home"],
        "description": "商品カテゴリ。指定しない場合は全カテゴリから検索"
      },
      "sort_by": {
        "type": "string",
        "enum": ["relevance", "price_asc", "price_desc", "rating", "newest"],
        "description": "ソート順。デフォルトはrelevance(関連度順)"
      },
      "max_results": {
        "type": "integer",
        "description": "取得件数の上限(1-50)。デフォルトは10",
        "minimum": 1,
        "maximum": 50
      }
    },
    "required": ["query"]
  }
}

strict modeの活用:

OpenAI・Anthropic双方で、

strict: true
を設定するとLLMの出力がスキーマに厳密に準拠します。strict modeを有効にする場合は以下の要件を満たす必要があります。
要件説明
additionalProperties: false
すべてのオブジェクトに設定
全フィールドを
required
に含める
必須フィールドを明示
任意フィールドは型に
null
を追加
"type": ["string", "null"]
で任意性を表現
{
  "name": "create_calendar_event",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "title": {
        "type": "string",
        "description": "イベントのタイトル"
      },
      "start_time": {
        "type": "string",
        "description": "開始日時(ISO 8601形式、例: 2026-05-26T10:00:00+09:00)"
      },
      "end_time": {
        "type": ["string", "null"],
        "description": "終了日時(ISO 8601形式)。nullの場合は1時間のデフォルト"
      },
      "location": {
        "type": ["string", "null"],
        "description": "場所(オンラインの場合はURL)"
      }
    },
    "required": ["title", "start_time", "end_time", "location"],
    "additionalProperties": false
  }
}

原則4:input_examplesで具体例を示す

JSON Schemaだけでは伝えきれないパターン(任意パラメータの組み合わせ、フォーマットの慣習など)は、

input_examples
で補完します。Anthropicの報告では、input_examplesの追加により複雑なパラメータの正確な指定率が72%から90%に改善しています。
{
  "name": "search_flights",
  "description": "指定された条件でフライトを検索します。出発地・目的地は空港コード(IATA)で指定してください。片道・往復の両方に対応しています。",
  "input_schema": {
    "type": "object",
    "properties": {
      "origin": {
        "type": "string",
        "description": "出発空港コード(IATA、例: NRT)"
      },
      "destination": {
        "type": "string",
        "description": "到着空港コード(IATA、例: SFO)"
      },
      "departure_date": {
        "type": "string",
        "description": "出発日(YYYY-MM-DD形式)"
      },
      "return_date": {
        "type": "string",
        "description": "復路出発日(YYYY-MM-DD形式)。片道の場合は省略"
      },
      "passengers": {
        "type": "integer",
        "description": "搭乗者数(1-9)",
        "minimum": 1,
        "maximum": 9
      }
    },
    "required": ["origin", "destination", "departure_date"]
  },
  "input_examples": [
    {
      "origin": "NRT",
      "destination": "SFO",
      "departure_date": "2026-07-15",
      "return_date": "2026-07-22",
      "passengers": 2
    },
    {
      "origin": "HND",
      "destination": "ICN",
      "departure_date": "2026-08-01"
    }
  ]
}

2つ目の例では任意パラメータ(

return_date
passengers
)を省略しており、片道検索のパターンをLLMに学習させています。

注意点: input_examplesは1つあたり20-50トークン(単純な場合)〜100-200トークン(複雑なネストがある場合)を消費します。3-5個の例が推奨されており、過剰に追加するとコンテキストウィンドウを圧迫します。

原則5:レスポンスを設計する

高信号情報のみを返す

ツールのレスポンス設計は、エージェントの推論精度とトークン効率の両方に影響します。Anthropicの「Writing tools for agents」では、「意味のあるコンテキスト情報を優先し、LLMが次のステップを判断するために必要なフィールドのみを返す」ことが推奨されています。

設計のポイント:

  • 安定した識別子を返す: 内部IDではなく、スラッグやUUIDなどの意味のある識別子
  • 不要なフィールドを除外する: 生のAPIレスポンスをそのまま返さない
  • ページネーションとフィルタリング: 結果が多い場合は件数制限とページネーション
  • 詳細度の切替:
    detail_level
    パラメータで詳細/簡潔を選択可能にする
from typing import Literal
from pydantic import BaseModel, Field

class SlackSearchParams(BaseModel):
    query: str = Field(description="検索キーワード")
    channel: str | None = Field(
        default=None,
        description="検索対象チャンネル名(例: #general)"
    )
    detail_level: Literal["concise", "detailed"] = Field(
        default="concise",
        description="concise: メッセージ本文のみ(推奨)、detailed: 送信者・リアクション・スレッド情報を含む"
    )
    max_results: int = Field(
        default=5,
        description="取得件数の上限(1-20)",
        ge=1,
        le=20
    )

Anthropicのエンジニアリングブログでは、Slackツールの事例として

detail_level
パラメータの導入により、1メッセージあたりのトークン消費が206トークンから72トークンへ約65%削減されたと報告されています。エージェントが最初に
concise
で概要を把握し、必要に応じて
detailed
で深掘りするという2段階アプローチが有効です。

よくある間違い: APIの生レスポンスをそのままツール結果として返してしまうパターンです。たとえばGitHub APIのPR情報には数十のフィールドが含まれますが、エージェントが次のアクションを判断するのに必要なのは

title
state
author
url
程度です。
def format_pr_response_concise(pr: dict) -> dict:
    """エージェント向けに高信号フィールドのみ抽出"""
    return {
        "title": pr["title"],
        "state": pr["state"],
        "author": pr["user"]["login"],
        "url": pr["html_url"],
        "created_at": pr["created_at"],
        "labels": [l["name"] for l in pr["labels"]],
    }

レスポンスが大きすぎる場合の対処として、切り詰め時にはガイダンスメッセージを含めることが推奨されています。例:

"結果が多すぎます(全200件中上位10件を表示)。より具体的なキーワードで再検索してください。"

原則6:ツールを統合する

関連操作を1つのツールにまとめる

Anthropicの公式ドキュメントでは、「個別の操作ごとに別々のツールを作成するのではなく、戦略的に統合せよ」と推奨されています。具体的には、

create_pr
review_pr
merge_pr
を3つの別ツールとして提供するのではなく、
action
パラメータを持つ1つの
github_pr
ツールに統合するアプローチです。

統合前:

ツール数: 3
- create_pr(repo, title, body, base, head)
- review_pr(repo, pr_number, action, body)
- merge_pr(repo, pr_number, method)

統合後:

{
  "name": "github_pr",
  "description": "GitHub Pull Requestの作成・レビュー・マージを行います。actionパラメータで操作を指定してください。createの場合はtitle/body/base/headが必須、reviewの場合はpr_number/review_actionが必須、mergeの場合はpr_numberが必須です。",
  "input_schema": {
    "type": "object",
    "properties": {
      "repo": {
        "type": "string",
        "description": "リポジトリ名(owner/repo形式)"
      },
      "action": {
        "type": "string",
        "enum": ["create", "review", "merge"],
        "description": "実行する操作"
      },
      "pr_number": {
        "type": "integer",
        "description": "PR番号(review/merge時に必須)"
      },
      "title": {
        "type": "string",
        "description": "PRタイトル(create時に必須)"
      },
      "body": {
        "type": "string",
        "description": "PR本文またはレビューコメント"
      },
      "base": {
        "type": "string",
        "description": "マージ先ブランチ(create時に必須、例: main)"
      },
      "head": {
        "type": "string",
        "description": "マージ元ブランチ(create時に必須)"
      },
      "review_action": {
        "type": "string",
        "enum": ["approve", "request_changes", "comment"],
        "description": "レビューアクション(review時に必須)"
      },
      "merge_method": {
        "type": "string",
        "enum": ["merge", "squash", "rebase"],
        "description": "マージ方法(merge時、デフォルト: squash)"
      }
    },
    "required": ["repo", "action"]
  }
}

統合のメリット:

  • LLMが選択すべきツール数が減り、選択精度が向上する
  • 関連操作の文脈をLLMが一箇所で把握できる
  • ツール定義全体のトークン消費が削減される

統合すべきでない場合:

  • 操作の性質がまったく異なる場合(例: メール送信と天気取得)
  • 統合によりパラメータが複雑になりすぎる場合
  • 権限レベルが異なる操作を混在させるとセキュリティリスクが生じる場合

ツール統合の判断基準として、「同じリソースに対する異なる操作」であれば統合を検討し、「異なるリソースに対する操作」であれば分離を維持するのが目安です。

原則7:大規模ツールセットを管理する

Tool Searchによる動的発見

ツール数が増えるほど、LLMの選択精度は低下します。OpenAIのガイドラインでは「20ツール以下で初期精度を確保」、Composioのテストでは「30ツール超で精度が79.5%まで低下」と報告されています。

この問題に対するAnthropicの解決策がTool Search

defer_loading: true
)です。ツール定義を事前にすべてロードするのではなく、LLMが必要に応じて検索・発見する仕組みです。

効果:

指標全ロードTool Search
初期トークン消費約72,000トークン約500トークン
選択精度(30+ツール)79.5%88.1%
トークン削減率-約85%

Anthropicの公式ガイドラインに基づくと、Tool Searchの導入は以下の条件で特に効果的です。

  • ツール定義全体が10,000トークンを超える場合
  • ツール選択の精度に問題がある場合
  • MCPサーバーを複数接続している場合

実装例(Claude API):

import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "github_list_prs",
        "description": "GitHub PRの一覧を取得する。リポジトリ名とステータスでフィルタ可能。",
        "defer_loading": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "repo": {"type": "string", "description": "owner/repo形式のリポジトリ名"},
                "state": {"type": "string", "enum": ["open", "closed", "all"]}
            },
            "required": ["repo"]
        }
    },
    {
        "name": "slack_send_message",
        "description": "Slackチャンネルにメッセージを送信する。チャンネル名とメッセージ本文を指定。",
        "defer_loading": True,
        "input_schema": {
            "type": "object",
            "properties": {
                "channel": {"type": "string", "description": "#を含むチャンネル名"},
                "text": {"type": "string", "description": "送信するメッセージ"}
            },
            "required": ["channel", "text"]
        }
    }
]

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "mainブランチへのオープンPRを確認して"}]
)

MCP(Model Context Protocol)によるツール標準化

2026年現在、ツール接続の標準化としてAnthropicが策定した**MCP(Model Context Protocol)**の普及が進んでいます。MCPは3つのプリミティブで構成されます。

プリミティブ役割
Tools LLMが呼び出す関数DBクエリ、メール送信
Resources 読み取り専用コンテキストファイル内容、設定値
Prompts 再利用可能なテンプレートコードレビュー手順

MCPを採用することで、ツール定義を1つのMCPサーバーとして実装すれば、Claude Code、Cursor、VS Codeなど複数のAIクライアントから同じツールを利用できます。通信にはJSON-RPC 2.0を使用し、ローカルプロセス(stdio)とリモートサーバー(HTTP/SSE)の両方に対応しています。

2026年7月28日にリリース候補が公開される最新仕様では、ステートレスなHTTPベースの設計、サーバーレンダリングUIの提供(MCP Apps拡張)、OAuth/OpenID Connectに準拠した認可など、本番運用を見据えた機能が追加されています。

制約: MCPはまだ仕様策定の途上にあり、2026年7月の最終仕様公開まではAPIの破壊的変更が発生する可能性があります。プロダクション導入を検討する場合は、仕様のバージョンを固定して運用することを推奨します。

よくある問題と解決方法

ツール設計でよく遭遇する問題と、その解決策をまとめます。

問題原因解決方法
LLMが間違ったツールを選択する説明文が曖昧、ツール間の境界が不明確「いつ使うか」「いつ使わないか」を説明文に明記する
パラメータの形式が間違っているフォーマットの例示不足
input_examples
を追加し、具体的な入力パターンを示す
レスポンスが大きすぎてコンテキスト溢れAPIの生レスポンスをそのまま返している高信号フィールドのみ抽出し、
detail_level
パラメータを導入
ツール数増加で精度低下全ツール定義を毎回ロードしている
defer_loading: true
でTool Search導入
任意パラメータにLLMが不要な値を入れる必須/任意の区別が不明確strict mode +
"type": ["string", "null"]
で明示
ツール呼び出しの無限ループエラーメッセージが不親切アクション可能なエラーメッセージを返す

エラーメッセージの設計

ツールの実行が失敗した場合、LLMが次のアクションを判断できるエラーメッセージを返すことが重要です。

def handle_tool_error(error: Exception, tool_name: str) -> dict:
    """エージェント向けのアクション可能なエラーレスポンス"""
    if isinstance(error, ValueError):
        return {
            "error": True,
            "message": f"パラメータが不正です: {error}",
            "suggestion": "パラメータの形式を確認してください。日付はYYYY-MM-DD形式です。"
        }
    if isinstance(error, PermissionError):
        return {
            "error": True,
            "message": "このリソースへのアクセス権限がありません",
            "suggestion": "別のリソースを指定するか、権限のあるツールを使用してください。"
        }
    return {
        "error": True,
        "message": f"{tool_name}の実行中にエラーが発生しました",
        "suggestion": "パラメータを変更して再試行してください。"
    }

まとめと次のステップ

まとめ:

  • 原則1: 命名— サービス名プレフィックス + 動詞+対象で一意に識別できる名前を付ける
  • 原則2: 説明文— 3-4文以上で「何を」「いつ」「いつ使わないか」「何を返すか」「制約」を記述する
  • 原則3: パラメータ— enum制約・required/optional明示・strict modeで不正な状態を排除する
  • 原則4: 具体例— input_examplesで任意パラメータの組み合わせパターンをLLMに学習させる
  • 原則5: レスポンス— 高信号フィールドのみ返し、detail_levelパラメータでトークン消費を制御する
  • 原則6: 統合— 同一リソースへの操作はactionパラメータで1ツールに統合する
  • 原則7: スケール— 20ツール超ではTool Search(defer_loading)で動的発見に切り替える

次にやるべきこと:

  • 既存のツール定義を本記事の7原則に照らして監査し、説明文の充実度を確認する
  • strict modeを有効にして、パラメータのバリデーション精度を検証する
  • ツール数が10を超える場合は、Tool SearchまたはMCPサーバーの導入を計画する

参考

GitHubで編集を提案