AI Agents Claude

Claude Tool Use:AIエージェントに実際の能力を与える方法

Alejandro Rioja
Alejandro Rioja
4 分で読める
TL;DR

Claude tool useを使えば、エージェントがテキスト生成だけでなく実際のアクションを実行できます。ツールをJSONスキーマとして定義し、Claudeがいつ呼び出すかを決定し、コードが実際のアクションを実行します。ループは3ステップ:メッセージ送信→tool_useブロック受信→実行して結果を返す。これをCloudflare Workersの15以上の本番エージェントに実装しました。障害点はほぼAIではなく、ツールから返ってくる曖昧な結果にあります。

無料ニュースレター

毎週水曜。28,400人以上の読者。無駄なし。

目次

2026年7月更新。

TL;DR: Claude tool useを使えば、エージェントがテキスト生成だけでなく実際のアクションを実行できます。ツールをJSONスキーマとして定義し、Claudeがいつ呼び出すかを決定し、コードが実際のアクションを実行します。ループは3ステップ:メッセージ送信→tool_useブロック受信→実行して結果を返す。これをCloudflare Workersの15以上の本番エージェントに実装しました。障害点はほぼAIではなく、ツールから返ってくる曖昧な結果にあります。

[オペレーターの視点] コンサルティングブランドとPickleland(テキサス州プフルガービルのピックルボール施設)で30以上の本番AIエージェントを運用しています。その約半数がtool use——コードで定義した関数をモデルが呼び出せるClaude APIの機能——を使用しています。本番環境での実装と反復を経て収束したパターンを紹介します。

Tool useがエージェントにできることを変える理由

ツールがなければ、エージェントはテキストを生成するだけです。要約、下書き、分類には便利ですが、ほとんどのビジネス自動化が実際に必要とするものではありません。ビジネス自動化には情報の検索、データベースへの書き込み、APIの呼び出し、メッセージの送信が必要です。

Tool useはClaudeにそのアクセス権を与える方法です。JSONスキーマとしてツールセットを定義します。Claudeはスキーマを読み込み、どのツールをどの引数で呼び出すかを決定し、構造化されたtool_useコンテンツブロックを返します。コードが実際の関数を実行します。Claudeは結果を受け取り、次に何をすべきか決定します——別のツールを呼び出すか、最終的なテキスト応答を生成するかです。

重要点:Claudeがツールをいつ、どのように呼び出すかを決定します。 能力を定義するのはあなたです。モデルがいつ使用するかを推論します。

APIフローの仕組み

Tool useループには3つのステップがあります。モデルが行うツール呼び出しの回数に応じて、このループを1回または複数回実行します。

ステップ1:定義されたツールでメッセージを送信

typescript
const response = await anthropic.messages.create({
  model: "claude-haiku-4-5-20251001",
  max_tokens: 1024,
  tools: [
    {
      name: "check_court_availability",
      description:
        "Check if a court is available at a given date, time, and duration",
      input_schema: {
        type: "object",
        properties: {
          date: {
            type: "string",
            description: "Date in YYYY-MM-DD format",
          },
          time: {
            type: "string",
            description: "Start time in HH:MM format (24h)",
          },
          duration_minutes: {
            type: "number",
            description: "Duration of the booking in minutes",
          },
        },
        required: ["date", "time", "duration_minutes"],
      },
    },
  ],
  messages: [
    {
      role: "user",
      content: "Is a court available tomorrow at 2pm for 90 minutes?",
    },
  ],
});

ステップ2:Claudeがツールを呼び出したいか確認

typescript
if (response.stop_reason === "tool_use") {
  const toolUseBlock = response.content.find(
    (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
  );

  if (!toolUseBlock) throw new Error("Expected tool_use block");

  // Run your actual function
  const toolResult = await checkCourtAvailability(
    toolUseBlock.input as CourtAvailabilityInput
  );

  // Step 3: Return the result to Claude
  const finalResponse = await anthropic.messages.create({
    model: "claude-haiku-4-5-20251001",
    max_tokens: 1024,
    tools: [
      /* same tools as before */
    ],
    messages: [
      {
        role: "user",
        content: "Is a court available tomorrow at 2pm for 90 minutes?",
      },
      { role: "assistant", content: response.content },
      {
        role: "user",
        content: [
          {
            type: "tool_result",
            tool_use_id: toolUseBlock.id,
            content: JSON.stringify(toolResult),
          },
        ],
      },
    ],
  });

  // finalResponse.content now has the text answer
}

これがパターン全体です。ツール呼び出し1回につき3回のAPIインタラクション:ツール定義→tool_useブロック受信→結果を返す。

実例:Pickleland空き状況チェッカー

Pickleballはピックルボール施設です。Facebook Messenger、コメント、チャットボットで予約の問い合わせを受けます。質問はほぼ常に「土曜の午後3時は開いていますか?」や「8人グループでコートを予約できますか?」といった類のものです。

空き状況チェッカーエージェントは、定型文の回答を返す代わりに、tool useを使ってリアルタイムで実際の予約システムを照会します。

完全なエージェントを示します——簡略化していますが、本番環境に忠実です:

typescript
// workers/availability-checker.ts
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic();

const AVAILABILITY_TOOLS: Anthropic.Tool[] = [
  {
    name: "check_availability",
    description:
      "Check court availability for a date, time, and group size. Returns available courts and their prices.",
    input_schema: {
      type: "object",
      properties: {
        date: { type: "string", description: "YYYY-MM-DD" },
        start_time: { type: "string", description: "HH:MM (24h)" },
        duration_minutes: { type: "number" },
        players: { type: "number", description: "Number of players" },
      },
      required: ["date", "start_time", "duration_minutes"],
    },
  },
  {
    name: "get_pricing",
    description:
      "Get current pricing for court rentals and open play sessions",
    input_schema: {
      type: "object",
      properties: {
        session_type: {
          type: "string",
          enum: ["court_rental", "open_play", "clinics"],
        },
      },
      required: ["session_type"],
    },
  },
];

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

  // Agentic loop — keep going until stop_reason is "end_turn"
  while (true) {
    const response = await anthropic.messages.create({
      model: "claude-haiku-4-5-20251001",
      max_tokens: 512,
      system:
        "You are the booking assistant for Pickleland, a pickleball facility in Pflugerville, TX. " +
        "Use the tools to look up real availability and pricing. Never make up availability or prices. " +
        "If the customer wants to book, direct them to pickleland.com/book.",
      tools: AVAILABILITY_TOOLS,
      messages,
    });

    // Push the assistant's response into message history
    messages.push({ role: "assistant", content: response.content });

    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find(
        (b): b is Anthropic.TextBlock => b.type === "text"
      );
      return (
        textBlock?.text ??
        "I wasn't able to answer that — please call us directly."
      );
    }

    if (response.stop_reason === "tool_use") {
      // Process ALL tool calls in this response (Claude can request multiple at once)
      const toolResults: Anthropic.ToolResultBlockParam[] = [];

      for (const block of response.content) {
        if (block.type !== "tool_use") continue;

        let result: unknown;

        switch (block.name) {
          case "check_availability":
            result = await checkAvailability(
              block.input as AvailabilityInput,
              env
            );
            break;
          case "get_pricing":
            result = await getPricing(block.input as PricingInput, env);
            break;
          default:
            result = { error: `Unknown tool: ${block.name}` };
        }

        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: JSON.stringify(result),
        });
      }

      // Return all tool results in a single user message
      messages.push({ role: "user", content: toolResults });
    }
  }
}

2点注目すべき点があります。

エージェントループ。 stop_reason === "end_turn"になるまで続けます。Claudeはcheck_availabilityを呼び出し、料金も必要と判断してget_pricingを呼び出し、最終的な回答を生成するかもしれません——これは1つのユーザーメッセージに対する3回のAPI呼び出しです。ループは特別なロジックなしでこれを処理します。

ターンごとの複数ツール呼び出し。 Claudeは1つの応答で複数のtool_useブロックを返せます。すべてを処理し、単一のuserメッセージですべての結果を返します。個別に処理して個別に返すと、会話の流れが壊れてトークンを無駄にします。

実例:リード調査エージェント

コンサルティングブランドでは、話す前にインバウンドリードを充実させる調査エージェントを使用しています。誰かが問い合わせフォームに記入すると、エージェントが会社を調査し、通話前に知る必要があることを抽出します。

このエージェントのツール定義には書き込みツールが含まれています——そこでパターンが面白くなります:

typescript
const RESEARCH_TOOLS: Anthropic.Tool[] = [
  {
    name: "search_company",
    description: "Search for information about a company",
    input_schema: {
      type: "object",
      properties: {
        company_name: { type: "string" },
        website: { type: "string", description: "Company website if known" },
      },
      required: ["company_name"],
    },
  },
  {
    name: "save_research",
    description:
      "Save the completed research summary to Airtable. Call this when all research is complete.",
    input_schema: {
      type: "object",
      properties: {
        company_summary: { type: "string" },
        estimated_size: {
          type: "string",
          enum: ["1-10", "11-50", "51-200", "200+"],
        },
        likely_use_case: { type: "string" },
        priority: { type: "string", enum: ["high", "medium", "low"] },
        notes: { type: "string" },
      },
      required: [
        "company_summary",
        "estimated_size",
        "likely_use_case",
        "priority",
      ],
    },
  },
];

save_research書き込みツールと呼んでいます——情報取得が目的ではなく、Claudeの出力を構造化された形でデータベースに確定することが目的です。テキスト応答からJSONを解析する代わりにこのパターンを使用します。Claudeは調査が完了したことを知り、正しく型付けされたフィールドでsave_researchを呼び出します。パーサーを書く必要がありません。

これがtool useの最もクリーンな応用です:欲しい正確なスキーマで「最終アクション」ツールを定義すると、Claudeがツール呼び出しを通じて構造化された出力を提供します。テキストの解析なし、正規表現なし、自由テキスト出力のJSONSchema検証なし。

1つのツール vs. 多数

tool useを始めるときの本能は、すべてを行う巨大なツールを作ることです。これに抵抗してください。小さく焦点を絞ったツールの方が優れています。理由は3つ:

  1. Claudeは小さなツールについてより適切に推論します。 get_court_statusという名前のツールが空き状況を返す方が、modeパラメータを受け取り内部で分岐するmanage_facilityよりもモデルが処理しやすいです。

  2. 小さなツールはテストが簡単です。 各ツールはLLMとは独立してユニットテストできるTypeScript関数です。そうすべきです——ツールのバグはライブな会話の中でデバッグするのが困難です。

  3. Claudeは小さなツールを並列化できます。 2つのツールが互いに依存していない場合、Claudeは同じ応答でそれらを呼び出し、並列に処理できます。これはツールが本当に独立している場合にのみ機能します。

例外:大量の共有内部状態へのアクセスが必要なツール。関数が同じデータソースから10個の変数を必要とする場合、それぞれがデータベースにアクセスする10個のツールよりも、より豊富なスキーマを持つ1つのツールの方が優れています。

私の経験則:異なる能力ごとに1つのツールから始めます。すべてのリクエストで一緒に呼び出しているのを見た場合にのみ、ツールをマージします。

コストの影響

tool useはトークンを追加します。各ツール定義はシステムプロンプトのコンテキストに入ります。各tool_usetool_resultブロックは会話履歴のトークンを消費します。マルチターンのエージェントループでは、これが急速に積み重なります。

Pickleland空き状況チェッカーの場合、典型的な会話は合計3〜4回のAPI呼び出し(最初のメッセージ + 1〜2回のツール呼び出し + 最終回答)を実行し、それぞれ600〜900トークンを処理します。Haiku価格では、1リクエストあたり$0.001未満のコストです。AIエージェントコスト計算の投稿で説明したように、Haikuは明確に定義されたツール呼び出しタスクを確実に処理し、同じトークン量でSonnetの10倍安価です。

リード調査エージェントはSonnetで動作しています。なぜなら、判断の決定——リードの優先順位付け、適合度の推定——は、Haikuがオープンな入力に対して確実に提供するよりも高い推論能力を必要とするからです。それほど頻繁に実行しないため(週数回、1日数千回ではなく)、計算はまだ機能します。モデルの選択はタスクの複雑さに従い、個人的な好みではありません。

誰も話さない障害点

本番環境のtool useで見る最も一般的な障害点は、Claudeが間違ったツールを呼び出すことではありません。ツールがClaudeが明確に推論できないものを返すことです。

40フィールドを持つ生のデータベースオブジェクトを返すと、Claudeはどのフィールドが重要か混乱します。ツールが例外をスローする(ツール結果ではなくWorkerのクラッシュとして現れる)と、ループが静かに壊れます。ツールが「結果なし」を意味するときにnullを返すと、Claudeは再試行するか諦めるか分かりません。

ツール結果の3つのルール:

コンパクトで明示的な結果を返す。 { available: true, courts: ["Court 3", "Court 5"], price_per_hour: 20 }——完全なデータベース行ではなく。

ツール関数内でエラーをキャッチし、構造化された結果として返す。 { error: "booking system timeout", retry: true }——Workerをクラッシュさせるスローされた例外ではなく。

「結果なし」を明示的にする。 { available: false, next_available: "2026-07-23T14:00:00Z" }——コンテキストのないnullや空の配列ではなく。

Claudeは曖昧な戻り値よりも明確なシグナルについてはるかによく推論します。本番環境でtool useのデバッグに費やした時間はすべて、モデルの推論ではなく不明確な結果についてでした。

オペレーターの結論

Tool useはClaudeをテキストジェネレーターからオペレーターに変える機能です。明確な入力スキーマを持つ焦点を絞ったツールを定義します。すべてのtool_useブロックをモデルへの単一の応答で処理します。stop_reason === "end_turn"になるまでエージェントループを実行します。ツール関数からクリーンでコンパクトな結果を返します——生のデータオブジェクトではなく、スローされた例外でもなく、曖昧なnullでもなく。

モデルが推論を担当します。コードが実際のアクションを担当します。この2つの仕事を明確に分離し続ければ、ツールを追加しても、アーキテクチャは維持可能なままです。

最初のtool useエージェントを構築している場合、上記の空き状況チェッカーパターンから始めてください——1つのツール、1つの目的、1つのエージェントループ。それをデプロイします。それから2番目のツールを追加します。


関連記事: 30以上の本番エージェントを運用するために使うエージェントスタック · Haiku vs Sonnet:エージェントタスクのコスト計算 · イベントトリガーvs定期実行エージェント:どちらのパターンがどの仕事に向くか

Tool useエージェントを構築していて行き詰まっていますか? お問い合わせ——オペレーターチームのために本番エージェントアーキテクチャを設計・構築しています。

よくある質問

Claude tool useはすべてのモデルで動作しますか?

はい——tool useは現在のすべてのClaudeモデルでサポートされています。Claude Haikuは明確なスキーマを持つ明確に定義されたツールを確実に処理し、大容量タスクタイプの最も安価なオプションです。Sonnetはより曖昧またはオープンエンドなツール呼び出し決定をより適切に処理します。Haikuから始め、出力品質が不十分であれば上位モデルに移行します。

Claude tool useとOpenAIの関数呼び出しの違いは何ですか?

機械的に同一です。OpenAIが「function calling」を作り、AnthropicがそれをとしてI使っています。どちらの場合も:JSONスキーマを定義し、モデルが構造化された呼び出しを返し、コードが関数を実行します。APIの形式は異なりますが、概念は同じです。

Claudeは1つの応答で複数のツールを呼び出せますか?

はい。Claudeは単一のassistant応答で複数のtool_useブロックを返せます。すべてを処理し、単一のuserメッセージですべての結果を返します。上記のPickleandの例のエージェントループパターンを参照してください——response.content上のforループがこれを正しく処理します。

エージェントごとにいくつのツールを定義すべきですか?

エージェントごとに8〜10個未満に抑えています。それ以上では、Claudeが最初の試みで間違ったツールを選択することがあり、修正ループでトークンを無駄にします。10以上の能力が必要な場合は、すべてを知る1つのエージェントを構築するのではなく、特殊なツールセットを持つ複数のエージェントにエージェントを分割します。

構造化出力のためにtool useを使うべきですか?

はい——save_research書き込みツールパターンは、Claudeにテキストブロックでをて返させてから解析するよりもクリーンです。欲しい正確なスキーマで「最終アクション」ツールを定義します。Claudeは完了したときに正しく型付けされたフィールドでそれを呼び出します。パーサーは不要です。

続きを読む

関連記事

続きを読む

AIプレイブックをメールでお届け

毎週水曜。28,400人以上の読者。無駄なし。

↵ すべての結果を見る esc esc で閉じる