AIエージェントの「ツール多すぎ問題」解決法|Claude Code・ChatGPT APIでの最適設計【2026年版】
AIエージェントが直面する「ツール多すぎ問題」とは
Claude CodeやChatGPT APIを使ってAIエージェント(会話型の自動処理システム)を構築する際、開発者が陥りやすいのが「ツール数の肥大化」です。プロジェクトが進むにつれて、利用可能な機能をどんどんツールとしてAPIに登録していくと、やがてエージェントの精度が低下し、応答が遅くなります。
このような問題が起きる理由は、LangChainなどのフレームワークを使う際、ツール定義の管理とツール選択のプロセスが分離しているためです。エージェントが多数のツール定義を受け取ると、目的に合った正しいツールを選択するまでに時間がかかり、場合によっては不適切なツールを呼び出してしまいます。
本記事では、実務開発者が遭遇しやすいこの問題の原因と、Claude Code・ChatGPT APIの両方で有効な解決策を実装レベルで解説します。
問題が起きる仕組み:なぜツール数増加で精度が下がるのか
LangChainでのツール管理の課題
LangChainでFunction Callingを実装する場合、一般的なパターンは以下の通りです:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool
# ツールを定義
@tool
def search_database(query: str) -> str:
"""データベースを検索"""
return f"検索結果: {query}"
@tool
def send_email(recipient: str, body: str) -> str:
"""メール送信"""
return f"メール送信完了: {recipient}"
@tool
def fetch_weather(location: str) -> str:
"""天気情報取得"""
return f"{location}の天気: 晴れ"
@tool
def calculate_revenue(year: int) -> str:
"""売上計算"""
return f"{year}年の売上: 100万円"
# すべてのツールをエージェントに渡す
tools = [search_database, send_email, fetch_weather, calculate_revenue]
agent = create_openai_tools_agent(
llm=ChatOpenAI(model="gpt-4"),
tools=tools,
prompt=prompt
)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
このアプローチでは、エージェントが4つのツール定義すべてをコンテキスト(会話の文脈情報)に含める必要があります。GPT-4は優秀ですが、ツール数が増えると、選択肢が多すぎるため、意思決定に時間がかかります。これはトークン数の増加にも直結し、API呼び出しのコストが上昇します。
根本原因:ツール選択の複雑性
エージェントがツールを選択する際、内部的には以下のプロセスが動きます:
- ユーザーの質問を受け取る
- 登録されたすべてのツール定義を読む
- どのツールが最適かを判断する
- 選択したツールを実行
- 結果を解釈して応答する
ツール数が増えると、ステップ2~3の負荷が指数関数的に増加します。特に似たような名前や機能のツールが複数存在する場合、エージェントが誤ったツールを選択する確率が高まります。
Claude Codeとの統合時に特に重要な理由
Claude CodeはAnthropicのツール使用機能(Tool Use)を活用しており、ChatGPT APIとは微妙に異なるツール呼び出しプロトコルを採用しています。Claude Codeでは、コードの作成・実行・修正が自動的に行われるため、エージェントが「どのツールを使うか」の判断をより正確に行う必要があります。
不適切なツール選択は、生成されたコードの品質低下につながり、デバッグに余分な時間が費やされることになります。
解決策1:機能別にツールを分類し、階層的に設計する
最も効果的な対策は、ツールの数を意図的に制限し、分類ごとに異なるエージェントを構築することです。
参考:LangChainでの実装例
LangChainのバージョンによって、モジュール構成が異なります。バージョン0.1以降では langchain_community パッケージを別途インストールする必要があります:
pip install langchain langchain-openai langchain-community
機能別分類の実装例を示します:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from langchain.prompts import ChatPromptTemplate
# グループA:データ取得関連ツール
@tool
def search_database(query: str) -> str:
"""データベースを検索する"""
return f"検索結果: {query}"
@tool
def fetch_api(endpoint: str) -> str:
"""外部APIからデータを取得"""
return f"API結果: {endpoint}"
# グループB:データ処理関連ツール
@tool
def calculate_stats(data: str) -> str:
"""統計情報を計算"""
return f"統計結果: {data}"
@tool
def format_report(content: str) -> str:
"""レポート形式に変換"""
return f"レポート:\n{content}"
# グループAのエージェント
llm = ChatOpenAI(model="gpt-4")
data_retrieval_tools = [search_database, fetch_api]
data_retrieval_agent = create_openai_tools_agent(
llm=llm,
tools=data_retrieval_tools,
prompt=ChatPromptTemplate.from_messages([
("system", "あなたはデータ取得の専門家です。必要なデータを取得してください。"),
("human", "{input}")
])
)
data_retrieval_executor = AgentExecutor(
agent=data_retrieval_agent,
tools=data_retrieval_tools,
verbose=True
)
# グループBのエージェント
processing_tools = [calculate_stats, format_report]
processing_agent = create_openai_tools_agent(
llm=llm,
tools=processing_tools,
prompt=ChatPromptTemplate.from_messages([
("system", "あなたはデータ処理の専門家です。データを加工して整形してください。"),
("human", "{input}")
])
)
processing_executor = AgentExecutor(
agent=processing_agent,
tools=processing_tools,
verbose=True
)
# 使用例
print("=== ステップ1:データ取得 ===")
retrieval_result = data_retrieval_executor.invoke({
"input": "営業ログデータを取得してください"
})
print("\n=== ステップ2:データ処理 ===")
processing_result = processing_executor.invoke({
"input": f"以下のデータを統計処理してレポートにしてください\n{retrieval_result}"
})
このアプローチでは、各エージェントが2~3個のツールのみ見ることになり、選択の正確性が大幅に向上します。
解決策2:ツール選択前に「意図分類」ステップを挟む
別の効果的な方法は、ユーザーの質問に対して、まず「この質問は何の目的か」を分類してから、該当するツールセットだけをエージェントに渡すものです:
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate
# ステップ1:意図分類
llm = ChatOpenAI(model="gpt-4")
classification_prompt = ChatPromptTemplate.from_template("""
ユーザーの質問から意図を分類してください。以下のいずれかで答えてください:
- "データ取得"(情報を探すため)
- "データ処理"(情報を加工するため)
- "通知"(誰かに連絡するため)
ユーザー質問:{user_input}
答え:
""")
classifier = LLMChain(llm=llm, prompt=classification_prompt)
def dispatch_to_agent(user_input: str):
# 意図を分類
intent = classifier.run(user_input=user_input).strip()
# 意図に応じてツールセットを選択
if "データ取得" in intent:
tools = [search_database, fetch_api]
system_msg = "データ取得に特化したエージェントです"
elif "データ処理" in intent:
tools = [calculate_stats, format_report]
system_msg = "データ処理に特化したエージェントです"
elif "通知" in intent:
tools = [send_email, send_slack_message]
system_msg = "通知送信に特化したエージェントです"
else:
tools = []
system_msg = "一般的な会話モードです"
# 選択されたツールセットでエージェント実行
if tools:
agent = create_openai_tools_agent(
llm=llm,
tools=tools,
prompt=ChatPromptTemplate.from_messages([
("system", system_msg),
("human", "{input}")
])
)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
return executor.invoke({"input": user_input})
else:
return "その質問にはツールは不要です。通常の回答を提供します。"
# 使用例
result = dispatch_to_agent("来月の売上見込みを計算してください")
このパターンは、エージェントが最初に「これは何の質問か」を判断して必要なツールだけを選択するため、トークン消費が少なく、応答が速くなります。
解決策3:Claude Code固有の最適化
Claude Codeを使用する場合は、Anthropicの Tool Use specification に沿ったツール定義を心がけることが重要です。Claude CodeはFunction Callingにおいて、説明文の詳細度に敏感です。
# Claude Code最適化版
def define_tools_for_claude():
"""Claude Code向けツール定義の例"""
tools = [
{
"name": "read_file",
"description": "プロジェクト内のファイルを読み込む。パスは相対パスで指定。",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "ファイルパス(例:src/main.py)"
}
},
"required": ["path"]
}
},
{
"name": "execute_python",
"description": "Pythonコードを実行。スクリプトデバッグやテスト実行に使用。",
"input_schema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "実行するPythonコード(複数行可)"
}
},
"required": ["code"]
}
},
{
"name": "write_file",
"description": "ファイルを作成または上書き。新規ファイル作成とコード生成に使用。",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "保存先パス"
},
"content": {
"type": "string",
"description": "ファイルの内容"
}
},
"required": ["path", "content"]
}
}
]
return tools
# Claude Codeでツール使用を最適化するコツ:
# 1. description は日本語で、具体的な用途を書く(抽象的な説明は避ける)
# 2. input_schema は必ずtype指定を明確にする
# 3. required フィールドは必須パラメータのみ列挙
Claude Codeではコード生成とファイル操作が主な目的になるため、その目的に専門化したツールセットを構築することで、生成コードの品質が大幅に向上します。
解決策4:ツール使用頻度の監視とリファクタリング
実装後、実際のログを分析して「どのツールがよく使われ、どのツールは使われていないか」を把握することも重要です:
import json
from datetime import datetime
from collections import Counter
# ツール実行ログを記録
class ToolUsageLogger:
def __init__(self):
self.logs = []
def log_tool_call(self, tool_name: str, user_input: str, success: bool):
"""ツール呼び出しをログに記録"""
self.logs.append({
"timestamp": datetime.now().isoformat(),
"tool": tool_name,
"user_input": user_input,
"success": success
})
def analyze(self):
"""ツール使用統計を分析"""
if not self.logs:
return "ログがありません"
tool_counts = Counter(log["tool"] for log in self.logs)
success_rate = {}
for tool in tool_counts:
calls = [log for log in self.logs if log["tool"] == tool]
successful = sum(1 for call in calls if call["success"])
success_rate[tool] = {
"total_calls": len(calls),
"success": successful,
"failure": len(calls) - successful,
"success_rate": f"{100 * successful / len(calls):.1f}%"
}
return success_rate
# 使用例
logger = ToolUsageLogger()
# エージェント実行後、ツール呼び出しをログ
logger.log_tool_call("search_database", "営業ログを検索", True)
logger.log_tool_call("fetch_weather", "天気を取得", False)
logger.log_tool_call("send_email", "メール送信", True)
# 統計分析
stats = logger.analyze()
print(json.dumps(stats, indent=2, ensure_ascii=False))
# 分析結果から不要なツール(呼び出し0回)や低精度ツール(成功率50%以下)を削除候補に
この分析により、実際に使われていないツール定義を削除したり、成功率が低いツールを改善したりできます。
実務的なベストプラクティス:ツール設計の7つの原則
多くの実案件で有効な原則を以下にまとめます:
原則1:ツール数は最大5個に制限
エージェントが正確に動作するには、見えるツールの数を5個以下に保つことが推奨されます。これを超える機能が必要な場合は、複数のエージェントに分割するか、意図分類ステップを導入してください。
原則2:ツール名と説明は明確に
# 悪い例
@tool
def process(input: str) -> str:
"""入力を処理する"""
pass
# 良い例
@tool
def extract_customer_name_from_invoice(invoice_text: str) -> str:
"""請求書テキストから顧客名を抽出する。請求書の「Bill To」セクションを解析"""
pass
原則3:1つのツール=1つの責任
複数の機能を1つのツールに詰め込まない:
# 悪い例:1つのツールで多すぎる
@tool
def database_operation(operation: str, query: str) -> str:
"""データベース操作(検索・作成・更新・削除に対応)"""
pass
# 良い例:責任を分割
@tool
def search_customer_records(query: str) -> str:
"""顧客レコードを検索"""
pass
@tool
def create_customer_record(name: str, email: str) -> str:
"""新規顧客レコードを作成"""
pass
原則4:エラーハンドリングはツール内で完結
ツールが失敗したときの動作をツール内で定義することで、エージェントの判断負荷を減らします:
@tool
def safe_database_query(query: str) -> str:
"""安全なデータベースクエリ。エラーメッセージを返す"""
try:
result = execute_query(query)
return f"成功: {result}"
except ValueError as e:
return f"エラー: クエリ形式が不正です。{str(e)}"
except ConnectionError:
return "エラー: データベースに接続できません。後で再試行してください。"
except Exception as e:
return f"エラー: 予期しないエラーが発生しました。{str(e)}"
原則5:出力形式を統一
すべてのツールが統一された形式で結果を返すことで、エージェントが解釈しやすくなります:
import json
def format_tool_result(success: bool, data: any, message: str = ""):
"""ツール出力を統一フォーマットで返す"""
return json.dumps({
"success": success,
"data": data,
"message": message
}, ensure_ascii=False)
@tool
def search_with_format(query: str) -> str:
"""フォーマット済みの検索結果を返す"""
try:
result = search_database(query)
return format_tool_result(True, result, "検索に成功しました")
except Exception as e:
return format_tool_result(False, None, str(e))
原則6:テストと検証を組み込む
ツール定義後、実際にエージェントが正しく選択・実行するか検証します:
def test_agent_tool_selection(agent, test_cases):
"""エージェントのツール選択をテスト"""
for test_input, expected_tool in test_cases:
result = agent.invoke({"input": test_input})
# ログから実際に使用されたツールを確認
used_tool = extract_tool_name_from_log(result)
if used_tool == expected_tool:
print(f"✓ PASS: '{test_input}' → {used_tool}")
else:
print(f"✗ FAIL: '{test_input}' → 期待値: {expected_tool}, 実際: {used_tool}")
# テストケース
test_cases = [
("営業データを取得してください", "search_database"),
("メールを送信してください", "send_email"),
("天気を教えてください", "fetch_weather"),
]
test_agent_tool_selection(agent, test_cases)
原則7:定期的なツール削除と最適化
プロジェクトが進むにつれて、不要なツール定義が蓄積します。定期的に監視ログを確認し、以下の基準で削除を検討してください:
- 過去3ヶ月で1回も呼び出されていない
- 成功率が30%未満
- より一般的なツールと機能が重複している
ChatGPT APIでの実装上の注意点
ChatGPT APIでFunction Callingを使う場合、ツール定義の形式がLangChainと異なります。直接API呼び出しする場合:
from openai import OpenAI
client = OpenAI(api_key="your-api-key")
tools = [
{
"type": "function",
"function": {
"name": "search_documents",
"description": "ドキュメント検索エンジン。社内ナレッジベースから情報を取得",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "検索キーワード"
},
"category": {
"type": "string",
"enum": ["manual", "faq", "blog"],
"description": "検索対象カテゴリ"
}
},
"required": ["query"]
}
}
}
]
# 注意:ツール数が増えると、API呼び出しのコスト(トークン数)が増加
# Claude Codeでも同様の原則が適用される
response = client.chat.completions.create(
model="gpt-4-turbo",
messages=[
{"role": "user", "content": "最近のAPI仕様書を探してください"}
],
tools=tools,
tool_choice="auto"
)
ChatGPT APIの場合、tool_choice="auto" でエージェント的な動作ができますが、ツール数が増えるとトークン消費が線形に増加するため、前述の「5個以下制限」はより厳密に守るべきです。
まとめ:ツール多すぎ問題を完全に解決するチェックリスト
以下のリストを参考に、プロジェクト開始前・途中・定期見直し時に確認してください:
- エージェントあたりのツール数は5個以下か
- ツール名と説明から、その機能が一目で理解できるか
- 1つのツールが複数の責任を持っていないか
- すべてのツールでエラーハンドリングを実装しているか
- ツール出力形式が統一されているか
- ツール選択の精度をテストしたか
- ツール使用ログを監視し、不要なツールを定期削除しているか
- 複数ジャンルの機能が必要な場合、複数エージェント化を検討したか
- Claude Code使用時は、Tool Use specificationに従っているか
これらの対策により、Claude CodeやChatGPT APIで構築するエージェントの精度と応答速度を大幅に改善できます。
あわせて読みたい
- Claude Codeで複数ファイル編集時のContext制限エラーを解決する3つの方法
- Claude Codeで修正回数を減らす7つのプロンプトテクニック|精度を上げる質問構造
- Claude Codeのトークン消費を98%削減する方法【MCP活用+コンテキスト最適化】
参考ソース
- LangChain: ModuleNotFoundError - langchain_community
- LangChain: How to use a custom embedding model locally
- Differences between LangChain & LlamaIndex
- What is the difference between OpenAI and ChatOpenAI in LangChain
- How to create a langchain doc from an str
- OpenAI API: How to count tokens before sending a request