AIコーディング 2026.07.07

Claude CodeのFunction Calling実装|複数ツール並列実行のベストプラクティス【2026年版】

タグ:Claude Code / Function Calling / AIエージェント / Node.js / 並列実行

記事の目的:これを読んで何が実現できるか

この記事を読むと、以下が実現できます。

  • 複数のツール(データベース、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.js16.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_schemaparametersの代わりに使われます。

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比較表

項目ChatGPTClaude
ツール定義形式type: "function" でラップ直接ツール定義
パラメータキーparametersinput_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/)

参考ソース