LESSON 30分

ストーリー

田中VPoE
ツール設計の原則を学んだところで、実際にLLMにツールを使わせる仕組み「ファンクションコーリング」を学ぼう
あなた
LLMに「このAPIを呼んで」と指示するだけではダメなんですか?
田中VPoE
テキストベースで指示するだけだと、LLMの出力をパースして正しいAPI呼び出しに変換する処理が必要になる。ファンクションコーリングは、LLMがJSON形式で構造化されたツール呼び出しを直接出力してくれる仕組みだ
あなた
パースエラーの心配がなくなるんですね
田中VPoE
その通り。OpenAIの Function Calling、Anthropic の Tool Use、どちらも同じ思想で設計されている。スキーマを定義してAPIに渡せば、LLMが適切なツール呼び出しをJSON形式で返してくれる

ファンクションコーリングの仕組み

基本フロー

1. ツールのスキーマを定義
   → ツール名、説明、パラメータの型を JSON Schema で記述

2. LLM APIにスキーマと共にメッセージを送信
   → messages + tools(スキーマ一覧)を送信

3. LLMがツール呼び出しをJSON形式で返却
   → {tool_name, arguments} の構造化データ

4. アプリケーションがツールを実行
   → LLMの指定通りにツールを呼び出し

5. 結果をLLMに返却
   → ツールの実行結果をメッセージとして追加

6. LLMが最終回答を生成
   → ツールの結果を踏まえてユーザーに回答

OpenAI API での実装

ツールスキーマの定義

import OpenAI from "openai";

const tools: OpenAI.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "search_orders",
      description: "注文番号、顧客ID、ステータスで注文情報を検索します。部分一致検索に対応。最大100件を返却します。",
      parameters: {
        type: "object",
        properties: {
          order_id: {
            type: "string",
            description: "注文番号(例: ORD-12345)"
          },
          customer_id: {
            type: "string",
            description: "顧客ID(例: CUST-001)"
          },
          status: {
            type: "string",
            enum: ["pending", "confirmed", "shipped", "delivered", "cancelled"],
            description: "注文ステータス"
          }
        }
      }
    }
  },
  {
    type: "function",
    function: {
      name: "track_shipment",
      description: "追跡番号または注文番号で配送状況を追跡します。配送業者、現在地、配達予定日を返します。",
      parameters: {
        type: "object",
        properties: {
          tracking_number: {
            type: "string",
            description: "追跡番号"
          },
          order_id: {
            type: "string",
            description: "注文番号(追跡番号が不明な場合に使用)"
          }
        }
      }
    }
  }
];

API呼び出しとツール実行ループ

async function runAgent(userMessage: string): Promise<string> {
  const messages: OpenAI.ChatCompletionMessageParam[] = [
    {
      role: "system",
      content: "あなたはNetShop社のカスタマーサポートエージェントです。"
    },
    { role: "user", content: userMessage }
  ];

  const MAX_ITERATIONS = 10;

  for (let i = 0; i < MAX_ITERATIONS; i++) {
    const response = await openai.chat.completions.create({
      model: "gpt-4o",
      messages,
      tools,
      tool_choice: "auto" // LLMが判断してツール呼び出しを行う
    });

    const message = response.choices[0].message;
    messages.push(message);

    // ツール呼び出しがない場合 → 最終回答
    if (!message.tool_calls || message.tool_calls.length === 0) {
      return message.content ?? "";
    }

    // ツール呼び出しを実行
    for (const toolCall of message.tool_calls) {
      const args = JSON.parse(toolCall.function.arguments);
      const result = await executeTool(toolCall.function.name, args);

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content: JSON.stringify(result)
      });
    }
  }

  return "処理の最大回数に達しました。";
}

Anthropic Claude API での実装

ツール定義

import Anthropic from "@anthropic-ai/sdk";

const tools: Anthropic.Tool[] = [
  {
    name: "search_orders",
    description: "注文番号、顧客ID、ステータスで注文情報を検索します。部分一致検索に対応。最大100件を返却します。",
    input_schema: {
      type: "object",
      properties: {
        order_id: {
          type: "string",
          description: "注文番号(例: ORD-12345)"
        },
        customer_id: {
          type: "string",
          description: "顧客ID(例: CUST-001)"
        },
        status: {
          type: "string",
          enum: ["pending", "confirmed", "shipped", "delivered", "cancelled"],
          description: "注文ステータス"
        }
      }
    }
  }
];

API呼び出し

async function runClaudeAgent(userMessage: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: userMessage }
  ];

  const MAX_ITERATIONS = 10;

  for (let i = 0; i < MAX_ITERATIONS; i++) {
    const response = await anthropic.messages.create({
      model: "claude-sonnet-4-20250514",
      max_tokens: 4096,
      system: "あなたはNetShop社のカスタマーサポートエージェントです。",
      messages,
      tools
    });

    // stop_reason が "end_turn" → 最終回答
    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find(b => b.type === "text");
      return textBlock?.text ?? "";
    }

    // stop_reason が "tool_use" → ツール呼び出し
    if (response.stop_reason === "tool_use") {
      messages.push({ role: "assistant", content: response.content });

      const toolResults: Anthropic.ToolResultBlockParam[] = [];
      for (const block of response.content) {
        if (block.type === "tool_use") {
          const result = await executeTool(block.name, block.input);
          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: JSON.stringify(result)
          });
        }
      }

      messages.push({ role: "user", content: toolResults });
    }
  }

  return "処理の最大回数に達しました。";
}

パラメータバリデーション

バリデーション層の実装

LLMが生成するパラメータは常に正しいとは限りません。実行前にバリデーションを行います。

import { z } from "zod";

// Zodスキーマでバリデーション定義
const SearchOrdersSchema = z.object({
  order_id: z.string().regex(/^ORD-\d+$/).optional(),
  customer_id: z.string().regex(/^CUST-\d+$/).optional(),
  status: z.enum(["pending", "confirmed", "shipped", "delivered", "cancelled"]).optional()
});

async function executeTool(
  name: string,
  args: Record<string, unknown>
): Promise<ToolResponse> {
  try {
    switch (name) {
      case "search_orders": {
        const validated = SearchOrdersSchema.parse(args);
        return await searchOrders(validated);
      }
      // ... 他のツール
      default:
        return { success: false, error: { code: "UNKNOWN_TOOL", message: `不明なツール: ${name}` } };
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        success: false,
        error: {
          code: "VALIDATION_ERROR",
          message: `パラメータエラー: ${error.errors.map(e => e.message).join(", ")}`,
          suggestion: "パラメータの形式を確認して再試行してください"
        }
      };
    }
    throw error;
  }
}

tool_choice オプション

LLMにツール呼び出しの判断をどこまで任せるかを制御できます。

オプション動作使い所
autoLLMが判断(呼ぶ/呼ばない)通常はこれを使用
required必ずいずれかのツールを呼ぶツール使用を強制したい場合
noneツールを呼ばないテキスト回答のみ欲しい場合
特定ツール指定指定したツールを必ず呼ぶ特定の処理を強制したい場合

まとめ

ポイント内容
ファンクションコーリングLLMがJSON形式で構造化されたツール呼び出しを出力する仕組み
スキーマ定義JSON Schemaでツールのインターフェースを厳密に定義
実行ループツール呼び出し → 実行 → 結果返却 → 次の判断のサイクル
バリデーションLLMの出力を信頼せず、実行前にパラメータを検証

チェックリスト

  • ファンクションコーリングの基本フローを理解した
  • OpenAI / Claude APIでのツール定義方法を把握した
  • ツール実行ループの実装パターンを理解した
  • パラメータバリデーションの重要性と実装方法を理解した

次のステップへ

次は「ツール選択戦略」を学びます。複数のツールからLLMが適切なものを選ぶための説明文設計やコンテキスト管理を理解しましょう。


推定読了時間: 30分