Claude CodeのFunction Calling実装|複数ツール並列実行のベストプラクティス【2026年版】
記事の目的:これを読んで何が実現できるか
この記事を読むと、以下が実現できます。
- 複数のツール(データベース、API、外部サービス)をAIエージェントから並列実行できる実装パターンが身につく
- Function Calling(ツール呼び出し)の設計・実装・エラー処理が具体的なコード例で理解できる
- ChatGPTとClaudeそれぞれの実装パターンの違いを把握し、適切なツールを選択できる
- 意思決定自動化(Decision Automation)を実現するために必要なエージェントアーキテクチャが把握できる
- 実務開発での落とし穴(エージェントメモリの汚染、ツール呼び出しの順序問題)を事前に回避できる
業界 5.0(Industry 5.0)の実現には、AIモデルの性能だけでなく、意思決定の自動化が鍵になります。AIエージェントが複数のツールを正確に、効率的に呼び出せることが、その差を大きく広げます。
Function Calling とは何か
Function Calling(ファンクション・コーリング)は、大規模言語モデル(LLM)が外部のツールやAPIを自動的に呼び出す仕組みです。Claude Code、ChatGPT、CursorといったAIコーディングツールを使う開発者にとって、このパターンを理解することは開発速度の向上と信頼性の高いシステム構築に直結します。
簡単に言うと:
- AIが「このタスクには、データベースから顧客情報を取得し、外部APIで料金計算し、結果をメール送信する必要がある」と判断したとき
- それぞれの処理を、AIが自動的に正しい順序で、正しいパラメータで呼び出す
- 人間は最初の指示をするだけで、後は自動的に進む
AIが「何を計算したいか」を構造化データとして出力し、その結果をもとに次のアクションを決める。この循環がエージェント型AIの中核です。2026年時点で、複数ツールの並列実行やスマートなモデルルーティングは、単なるオプション機能ではなく、生産的なAI開発の必須スキルになっています。
前提環境・必要なツール
以下の環境を前提とします。
| 項目 | 要件 | 説明 |
|---|---|---|
| Node.js | 16.x以上 | 動作確認済みは 18.x 以上推奨 |
| @anthropic-sdk/sdk | 最新 | Claude APIクライアントライブラリ |
| npm | 最新版 | パッケージ管理 |
| Claude API キー | 有効 | Anthropic の公式サイトから取得 |
また、この記事では以下を前提とします。
- JavaScriptの基礎知識(async/await、Promise)
- REST API の概念(GET、POST、ステータスコード)
- JSONデータの扱い方
- ターミナル(command line)での操作経験
ChatGPTのFunction Calling実装パターン
ChatGPTのFunction Calling(正式には「Tools」機能)は、OpenAI APIで最もシンプルに実装できます。基本的な流れは以下の通りです。
ステップ 1:ツール定義をJSONスキーマで記述
ChatGPT APIではTools機能を使って、モデルが呼び出し可能な関数を事前に定義します。
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定した都市の現在の天気を取得",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名(例:東京、大阪)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度の単位"
}
},
"required": ["location"]
}
}
}
descriptionフィールドは自然言語で書き、AIがタスクを判断する際の助言になります。具体例や制約条件を含めることが重要です。
ステップ 2:APIリクエストでツール情報を渡す
import openai
client = openai.OpenAI(api_key="sk-...")
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定した都市の現在の天気を取得",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["location"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "user", "content": "東京の天気はどうですか?"}
],
tools=tools,
tool_choice="auto"
)
print(response.choices[0].message)
tool_choice="auto"で、モデルが必要なときだけツールを呼び出すように指示できます。tool_choice="required"にするとツール呼び出しを強制します。
ステップ 3:モデルからのレスポンスを処理
if response.choices[0].finish_reason == "tool_calls":
for tool_call in response.choices[0].message.tool_calls:
print(f"Tool: {tool_call.function.name}")
print(f"Arguments: {tool_call.function.arguments}")
# 実際のツール処理をここで実行
ステップ 4:ツール実行結果をモデルに返す
import json
def execute_tool(tool_name, arguments):
if tool_name == "get_current_weather":
location = json.loads(arguments)["location"]
return {"location": location, "temperature": 22, "condition": "晴れ"}
return {"error": "Unknown tool"}
messages = [
{"role": "user", "content": "東京の天気はどうですか?"},
response.choices[0].message
]
for tool_call in response.choices[0].message.tool_calls:
result = execute_tool(tool_call.function.name, tool_call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
final_response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools
)
print(final_response.choices[0].message.content)
この流れが「Function Calling→実行→結果フィードバック」のサイクルです。
ClaudeのFunction Calling実装パターン
Claude(Claude 3.5 Sonnetなど)もFunction Callingをサポートしていますが、APIの設計がChatGPTと異なります。
ステップ 1:プロジェクトのセットアップ
mkdir ai-agent-project
cd ai-agent-project
npm init -y
npm install @anthropic-sdk/sdk dotenv
.env ファイルを作成し、Claude API キーを設定します。
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxx
ステップ 2:ツール定義(Tool Schema)の設計
ClaudeではOpenAIのfunctionラッパーがなく、直接ツール定義を渡します。またinput_schemaがparametersの代わりに使われます。
from anthropic import Anthropic
client = Anthropic()
tools = [
{
"name": "get_customer_info",
"description": "顧客IDから顧客情報をデータベースで検索します。名前、メール、購買履歴を返します。",
"input_schema": {
"type": "object",
"properties": {
"customer_id": {
"type": "string",
"description": "検索対象の顧客ID(例: C12345)"
}
},
"required": ["customer_id"]
}
},
{
"name": "calculate_price",
"description": "商品IDと数量から、税込み価格を計算します。割引ルールも適用します。",
"input_schema": {
"type": "object",
"properties": {
"product_id": {
"type": "string",
"description": "商品ID(例: P001)"
},
"quantity": {
"type": "number",
"description": "購買数量"
}
},
"required": ["product_id", "quantity"]
}
},
{
"name": "send_email",
"description": "顧客にメールを送信します。必ず get_customer_info でメールアドレスを取得してから実行してください。",
"input_schema": {
"type": "object",
"properties": {
"recipient": {
"type": "string",
"description": "宛先のメールアドレス"
},
"subject": {
"type": "string",
"description": "メールの件名"
},
"body": {
"type": "string",
"description": "メールの本文"
}
},
"required": ["recipient", "subject", "body"]
}
}
]
ツール定義のコツ
descriptionは具体的に。AIが判断するのに十分な情報をここに書くpropertiesで渡すパラメータと、その説明を明確にrequiredには必須パラメータだけを列挙- 依存関係があるツールは
descriptionで実行順序を明示的に記述
ステップ 3:ツール実行関数(Tool Executor)の実装
// toolExecutor.js
const customerDB = {
"C001": { name: "田中太郎", email: "tanaka@example.com", history: ["P001", "P002"] },
"C002": { name: "田中花子", email: "hanako@example.com", history: ["P003"] }
};
const productDB = {
"P001": { name: "商品A", base_price: 1000 },
"P002": { name: "商品B", base_price: 2000 },
"P003": { name: "商品C", base_price: 1500 }
};
async function executeGetCustomerInfo(customerId) {
if (!customerDB[customerId]) {
return { error: `顧客ID ${customerId} は見つかりません` };
}
return customerDB[customerId];
}
async function executeCalculatePrice(productId, quantity) {
if (!productDB[productId]) {
return { error: `商品ID ${productId} は見つかりません` };
}
const basePrice = productDB[productId].base_price;
const taxRate = 0.1;
const total = basePrice * quantity * (1 + taxRate);
return {
product_id: productId,
quantity: quantity,
base_total: basePrice * quantity,
tax: basePrice * quantity * taxRate,
total_price: Math.round(total)
};
}
async function executeSendEmail(recipient, subject, body) {
console.log(`[メール送信] To: ${recipient}`);
console.log(`件名: ${subject}`);
console.log(`本文: ${body}`);
return { success: true, message: "メールを送信しました" };
}
async function executeTool(toolName, toolInput) {
switch (toolName) {
case "get_customer_info":
return await executeGetCustomerInfo(toolInput.customer_id);
case "calculate_price":
return await executeCalculatePrice(toolInput.product_id, toolInput.quantity);
case "send_email":
return await executeSendEmail(toolInput.recipient, toolInput.subject, toolInput.body);
default:
return { error: `未知のツール: ${toolName}` };
}
}
module.exports = { executeTool };
ステップ 4:AIエージェントの実装(Claudeの場合)
messages = [
{"role": "user", "content": "東京の天気はどうですか?"}
]
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=messages
)
# ツール呼び出しの検出と実行
import json
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
result = {"location": tool_input.get("location", ""), "temperature": 22, "condition": "晴れ"}
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result)
}
]
})
final_response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final_response.content[0].text)
ステップ 5:複数ツール並列実行への拡張
複数のツール呼び出しを同時に実行することで、待ち時間を短縮できます。
// agentParallel.js
const Anthropic = require("@anthropic-sdk/sdk");
const tools = require("./tools");
const { executeTool } = require("./toolExecutor");
require("dotenv").config();
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
async function runAgentParallel(userMessage) {
console.log(`\n📝 ユーザーの指示: ${userMessage}\n`);
let messages = [
{
role: "user",
content: userMessage
}
];
while (true) {
const response = await client.messages.create({
model: "claude-3-5-sonnet-20241022",
max_tokens: 4096,
tools: tools,
messages: messages
});
console.log(`[Claude の判断] Stop reason: ${response.stop_reason}`);
if (response.stop_reason === "tool_use") {
const assistantMessage = { role: "assistant", content: response.content };
messages.push(assistantMessage);
// ⭐ ポイント:複数のツール呼び出しを Promise.all で並列実行
const toolUses = response.content.filter(c => c.type === "tool_use");
const toolPromises = toolUses.map(async (toolUse) => {
console.log(`🔧 ツール実行: ${toolUse.name}`);
console.log(` 入力: ${JSON.stringify(toolUse.input)}`);
const result = await executeTool(toolUse.name, toolUse.input);
console.log(` 結果: ${JSON.stringify(result)}`);
return {
type: "tool_result",
tool_use_id: toolUse.id,
content: JSON.stringify(result)
};
});
const toolResults = await Promise.all(toolPromises);
messages.push({
role: "user",
content: toolResults
});
} else {
let finalText = "";
for (const contentBlock of response.content) {
if (contentBlock.type === "text") {
finalText += contentBlock.text;
}
}
console.log(`\n✅ エージェントの最終回答:\n${finalText}`);
return finalText;
}
}
}
(async () => {
try {
await runAgentParallel("顧客 C001 と C002 の情報を同時に取得してください。");
} catch (error) {
console.error("エラーが発生しました:", error);
}
})();
ChatGPTとClaudeのFunction Calling比較表
| 項目 | ChatGPT | Claude |
|---|---|---|
| ツール定義形式 | type: "function" でラップ | 直接ツール定義 |
| パラメータキー | parameters | input_schema |
| ツール呼び出しの表現 | tool_calls[] 配列 | content[] 内の tool_use ブロック |
| 結果の送信方法 | role: "tool" | role: "user" + tool_result ブロック |
| ツール強制 | tool_choice="required" | 標準的なパラメータなし |
どちらを使うか判断する場合、APIレイテンシーやモデルの推論品質、ツール実装の複雑度が異なるため、実装前にベンチマークを取ることを推奨します。
複数ツール並列実行の最適化テクニック
パターン 1:非同期処理による並列実行
複数の独立したツールを同時に呼び出すことで、処理時間を削減できます。
import asyncio
from typing import List, Dict
async def call_tool_async(tool_name: str, arguments: dict) -> Dict:
await asyncio.sleep(0.5) # API呼び出し遅延
if tool_name == "get_weather":
return {"temperature": 22}
elif tool_name == "get_location":
return {"lat": 35.6762, "lng": 139.6503}
return {}
async def execute_tools_parallel(tool_calls: List[Dict]) -> List[Dict]:
tasks = [call_tool_async(call["name"], call["arguments"]) for call in tool_calls]
return await asyncio.gather(*tasks)
tool_calls = [
{"name": "get_weather", "arguments": {"location": "東京"}},
{"name": "get_location", "arguments": {"address": "東京"}},
{"name": "get_news", "arguments": {"topic": "東京"}}
]
results = asyncio.run(execute_tools_parallel(tool_calls))
3つのツール呼び出しが順序不問なら、並列実行で処理時間を約3分の1に短縮できます。
パターン 2:キャッシュ戦略による重複排除
同じパラメータでツールが複数回呼ばれる場合、キャッシュを活用します。
from functools import lru_cache
@lru_cache(maxsize=128)
def get_weather_cached(location: str):
print(f"Fetching weather for {location}")
return {"location": location, "temperature": 22}
print(get_weather_cached("東京"))
print(get_weather_cached("東京")) # キャッシュから取得
print(get_weather_cached("大阪"))
パターン 3:スマートなモデルルーティング
複数のモデルを組み合わせて、タスクの特性に応じて最適なモデルを選択します。軽い質問には軽いモデル(Claude Haiku)、複雑な推論が必要な場合は重いモデル(Claude Opus)を使うアプローチです。
def route_to_model(query: str) -> str:
if len(query) < 30 and "と" not in query:
return "claude-3-5-haiku-20241022"
elif any(word in query for word in ["なぜ", "どのように", "分析"]):
return "claude-3-5-opus-20241022"
else:
return "claude-3-5-sonnet-20241022"
query = "東京と大阪の気候の違いはなぜ生じるのでしょうか?"
model = route_to_model(query)
print(f"Selected model: {model}")
このルーティングにより、API使用量を削減しながら品質を保つことができます。
つまずきやすいポイントと解決策
1. エージェントメモリの汚染
問題:複数ターンのツール呼び出しを繰り返すと、メッセージ履歴が非常に長くなり、APIコストが増加し、コンテキストウィンドウを超える可能性があります。
解決策:定期的にメッセージ履歴を圧縮または切り詰めます。
function trimMessages(messages, maxLength = 10) {
if (messages.length > maxLength) {
return messages.slice(-maxLength);
}
return messages;
}
messages = trimMessages(messages);
2. ツール呼び出しの順序依存問題
問題:あるツール A の結果が、別のツール B のパラメータになる場合、並列実行できません。AIが順序を誤認識することもあります。
解決策:ツール定義の description で依存関係を明確に記述します。また、依存性のあるツール同士は「複合ツール」として定義することも有効です。
{
name: "customer_order_flow",
description: "顧客情報取得 → 価格計算 → メール送信 の一連の処理を実行します(自動で順序管理)",
input_schema: {
type: "object",
properties: {
customer_id: { type: "string" },
product_id: { type: "string" },
quantity: { type: "number" }
},
required: ["customer_id", "product_id", "quantity"]
}
}
3. レスポンスループの無限化
問題:エージェントが永遠にツールを呼び続ける。
解決策:必ず最大試行回数を設定します。
max_iterations = 10
iteration = 0
while iteration < max_iterations:
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=tools
)
iteration += 1
if response.choices[0].finish_reason == "tool_calls":
# ツール実行...
pass
else:
break
if iteration >= max_iterations:
print("Warning: Max iterations reached")
4. エラーハンドリングの欠落
問題:ツール実行失敗時に適切な処理がない。
解決策:失敗情報をモデルに返し、次のアクション判断に活用します。
try:
result = execute_tool(tool_name, arguments)
if not result:
raise ValueError("Tool returned empty result")
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
except Exception as e:
error_message = f"Tool execution failed: {str(e)}"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": error_message
})
5. APIキーの漏洩
問題:.env ファイルをGitHubにコミットしてしまう。
解決策:
echo ".env" >> .gitignore
実務でよく使うパターン集
パターン 1:複数顧客の情報並列取得
async function fetchMultipleCustomers(customerIds) {
const userMessage = `以下の複数の顧客ID に対して、同時に顧客情報を取得してください: ${customerIds.join(", ")}`;
return await runAgentParallel(userMessage);
}
パターン 2:商品の複数条件での価格計算
async function calculateBulkPrices(items) {
const itemStrings = items.map(i => `商品${i.product_id} を ${i.quantity}個`).join("、");
const userMessage = `以下の商品の価格を全て計算してください: ${itemStrings}`;
return await runAgentParallel(userMessage);
}
パターン 3:意思決定自動化(Decision Automation)
顧客情報を取得 → 購買条件を判定 → 自動でアクションを実行(例:割引メール送信)
async function autoDecisionWorkflow(customerId) {
const userMessage = `
顧客${customerId}の情報を取得して、以下のルールで自動判定してください:
- 購買履歴が3件以上なら、割引クーポン付きのメールを送信
- そうでなければ、商品おすすめメールを送信
`;
return await runAgentParallel(userMessage);
}
エージェント設計のベストプラクティス
1. エージェント設計パターン
Function Callingを活用したエージェントには4つの基本パターンがあります。
Reflect-Execute(反省と実行):モデルが毎ステップ計画を立て、結果を確認してから次のアクションを決めます。信頼性が高いが処理時間がかかります。
Pipeline(パイプライン):複数のタスクを順序立てて実行します。あらかじめ実行順序が決まっているタスクに向いています。
Hierarchical(階層的):大きなタスクを小タスクに分解し、再帰的に解きます。複雑な問題解決に向いていますが、APIコストが増加します。
Reactive(即応型):ユーザーの入力に直接ツールで応答するシンプルなパターンです。レスポンス時間が短く、シンプルなタスク向けです。
2. エラーハンドリングとリトライ
async function executeToolWithRetry(toolName, toolInput, maxRetries = 2) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await executeTool(toolName, toolInput);
if (result.error) {
throw new Error(result.error);
}
return result;
} catch (error) {
console.warn(`⚠️ ツール実行エラー (試行 ${attempt}/${maxRetries}): ${error.message}`);
if (attempt === maxRetries) {
return { error: error.message, retryFailed: true };
}
// 指数バックオフ
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
}
3. トレーシングと監視
本番環境では、エージェントの動作を記録・監視する必要があります。すべてのツール呼び出しをログに記録しましょう。
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
def execute_tool_with_logging(tool_name: str, arguments: dict) -> dict:
start_time = datetime.now()
logger.info(f"Calling tool: {tool_name} with args: {arguments}")
try:
result = execute_tool(tool_name, arguments)
elapsed = (datetime.now() - start_time).total_seconds()
logger.info(f"Tool {tool_name} completed in {elapsed:.2f}s: {result}")
return result
except Exception as e:
logger.error(f"Tool {tool_name} failed: {str(e)}")
raise
4. タイムアウト設定
ツール呼び出しが無限に待たされるのを防ぎます。
import threading
def execute_tool_with_timeout(tool_name: str, arguments: dict, timeout: int = 30) -> dict:
result = {}
exception = None
def run_tool():
nonlocal result, exception
try:
result
---
## あわせて読みたい
- [Claude Codeサブエージェント機能の使い方|5つの並列タスク実行ガイド](/code/claude-code-parallel-development-productivity/)
- [Claude Code完全ガイド:AIペアプログラミングを5分で始める](/code/claude-code-implementation-guide/)
- [Claude CodeでMCPサーバー連携【5ステップで外部API接続】初心者向け](/code/claude-code-external-data-integration-with-mcp/)