tool callingを「構造化された判断」として使う

はじめに

こんにちは、RightTouchでAIエンジニアをしている古賀 (corgi)です。今回は、LLMのtool callingの活用パターンについて書いてみます。

MCP(Model Context Protocol)やオーケストレーター構成、Skillsなど、AIエージェントを取り巻くフレームワークがどんどん高度になっているなか、tool callingそのものについて書くのは今さらと感じるかもしれません。

ただ、プロダクトの機能としてLLMを使っていく中で、出力の構造化は避けて通れない課題です。自分自身AIエージェントを構築する機会が増えるにつれて、その根幹にあるtool callingの特性を改めて意識するようになりました。

この記事では、そこで役立つtool callingの工夫のひとつとして、「行動の判断」と「判断に応じた構造化出力」を 1 回の LLM 呼び出しで同時に得るパターンを紹介します。

tool calling の一般的な理解

tool callingは、LLMに「利用可能なツール(関数)の一覧」を渡しておくと、入力に応じてLLMが適切なツールを選択し、その引数を生成してくれる仕組みです。OpenAIでは「function calling」、Anthropicでは「tool use」と呼ばれますが、本質的には同じ仕組みです。外部APIの呼び出しやMCPサーバー経由のツール連携など、主に「LLMの判断に基づいてツールを実行する」用途で使われています。

また、structured outputのようにLLMの出力を構造化する手段としてtool callingを使うテクニックも広く知られています。今回はその延長で、分類に応じて異なるスキーマで出力を出し分けるパターンに焦点を当てます。

tool calling による判断と構造化出力の同時取得

tool callingのレスポンスには「どのツールを選んだか(=判断)」と「その引数(=構造化された情報)」が含まれています。つまり、1 回の LLM 呼び出しで判断と構造化出力を同時に得られるわけです。

たとえば、メールの自動分類を考えてみます。「問い合わせ」「クレーム」「見積依頼」を判定し、分類ごとに異なるスキーマで情報を抽出したい。

  • 問い合わせcategory, summary
  • クレームseverity, summary, service
  • 見積依頼items[], summary, deadline, budget_range

メールの内容に応じて、どの構造で出力するかを LLM が判断し、その構造に沿った情報を抽出して返します。 tool callingを使うと、これが1回のLLM呼び出しで実現できます。具体的な実装は後ほど紹介しますが、まずは他の方法と比較してみます。

他のアプローチとの比較

同じことを実現する方法は他にもあります。それぞれの特徴を整理してみます。

structured output で全部入りスキーマにする

structured output(response_format)を使えば、LLMの出力を指定したJSONスキーマに従わせることができます。ただし出力スキーマは1つしか指定できないため、素直にやると全分類のフィールドを詰め込むことになります。たとえば問い合わせメールに対するレスポンスはこうなります。

{
  "classification": "inquiry",
  "category": "プラン変更",
  "summary": "上位プランの費用感と変更手順の確認",
  "severity": null,
  "service": null,
  "items": null,
  "deadline": null,
  "budget_range": null
}

分類によって使わないフィールドがnullで埋まり、受け取る側でも「classificationがinquiryならitemsは無視して...」という条件分岐が必要になります。せっかくの構造化の恩恵が薄れます。

分類と抽出を分けて 2 段構えにする

もう1つのアプローチは、「まず分類、次に抽出」と処理を分けることです。structured outputで2回呼ぶにせよ、エージェントフレームワークでルーティングするにせよ、構造としてはこうなります。

two_step

これなら分類ごとに異なるスキーマでの出力は実現できますが、最低でも 2 回の LLM 呼び出しが必要です。分類のためだけに1回、抽出のためにもう1回。コストもレイテンシもその分増えます。

tool calling なら 1 回で済む

今回紹介しているパターンでは、これが1回に集約されます。

tool_calling

分類の判断と、分類に応じた異なるスキーマでの情報抽出を、1 回の LLM 呼び出しで同時に得られます。

tool callingではすべてのtoolのdescriptionと引数構造が一覧として渡されるため、各toolの境界を比較しながら判断できます。経験上、LLMはこのツール選択の精度が高く、今回のパターンはその判断力を分類と構造化出力に活かしている形です。2段構えで起きがちなコンテキストの分断も避けられます。

複雑なワークフローにはエージェント構成が適していますが、ローレイテンシが求められる場面や大量処理のケースでは、このパターンが選択肢に入ってきます。

実装例

では、先ほどのメール分類を具体的に実装してみます。

Tool 定義

ここが設計の肝です。各ツールの引数が、「LLM に出力してほしいスキーマ」の定義になっています。メールの分類に対応する3つのtool(inquiry / complaint / quote_request)を定義します。

inquiry

抽出するのはcategory(問い合わせカテゴリ)とsummary(要約)の2つです。

{
  "name": "inquiry",
  "description": "一般的な問い合わせメールの場合に選択するツール",
  "parameters": {
    "type": "object",
    "properties": {
      "category": {
        "type": "string",
        "description": "問い合わせカテゴリ(例: プラン変更, 機能確認, 請求)"
      },
      "summary": { "type": "string", "description": "問い合わせ内容の1文要約" }
    },
    "required": ["category", "summary"]
  }
}

complaint

severity(深刻度)、summary、service(影響サービス名)の3つを抽出します。

{
  "name": "complaint",
  "description": "クレーム・障害報告メールの場合に選択するツール",
  "parameters": {
    "type": "object",
    "properties": {
      "severity": {
        "type": "string",
        "description": "深刻度: critical / major / minor"
      },
      "summary": { "type": "string", "description": "問題の1文要約" },
      "service": {
        "type": "string",
        "description": "影響を受けているサービス名"
      }
    },
    "required": ["severity", "summary", "service"]
  }
}

quote_request

summary、deadline、budget_rangeに加えて、items(品目リスト)がネストした配列構造になっています。各品目からnameとquantityを抽出します。

{
  "name": "quote_request",
  "description": "見積依頼メールの場合に選択するツール",
  "parameters": {
    "type": "object",
    "properties": {
      "items": {
        "type": "array",
        "description": "見積対象の品目リスト",
        "items": {
          "type": "object",
          "properties": {
            "name": { "type": "string", "description": "品名" },
            "quantity": { "type": "integer", "description": "数量" }
          },
          "required": ["name", "quantity"]
        }
      },
      "summary": { "type": "string", "description": "依頼内容の1文要約" },
      "deadline": {
        "type": "string",
        "description": "希望納期(記載がある場合)"
      },
      "budget_range": {
        "type": "string",
        "description": "予算感(記載がある場合)"
      }
    },
    "required": ["items", "summary"]
  }
}

これらのtool定義とシステムプロンプト(「入力されるメールの意図を判定し、適切なツールで分類してください」)をLLMに渡して、メールを入力するだけです。具体的な実装はSDKやフレームワークによりますが、主要なLLM APIでは同じ発想で実装できます。

以下、上記の設定で得られたレスポンスを紹介します(入力に使うメールのサンプルはLLMで生成しました)。

例 1:問い合わせメール

入力:

件名: プランのアップグレード手順について

お世話になっております。野村です。 現在 Business プランで月間 50 万リクエストほど利用しているのですが、 来期からチーム増員に伴い利用量が倍近くになる見込みです。 来週の役員会議までにプラン変更の費用感を報告したいので、 上位プランの詳細と変更手順を教えていただけますでしょうか。

レスポンス:

[
  {
    "name": "inquiry",
    "args": {
      "category": "プラン変更",
      "summary": "チーム増員による利用量増加に伴い、来週の役員会議までに上位プランの費用感と変更手順を確認したい"
    }
  }
]

例 2:見積依頼メール

入力:

件名: クラウド環境構築のお見積り依頼

お世話になっております。齋藤です。 社内システムのクラウド移行にあたり、以下の構成でお見積りをお願いいたします。

・Web サーバー: 4 台(4 コア / 16GB) ・DB サーバー: 2 台(8 コア / 64GB / SSD 1TB) ・ロードバランサー: 1 台 ・監視ツール一式

6 月末までに本番稼働させたいと考えており、月額 120 万円程度の予算感で検討しております。 なお、上長からは「もう少し安くならないか」と言われておりまして、 ボリュームディスカウント等があれば併せてご提示いただけると助かります。

レスポンス:

[
  {
    "name": "quote_request",
    "args": {
      "items": [
        { "name": "Webサーバー", "quantity": 4 },
        { "name": "DBサーバー", "quantity": 2 },
        { "name": "ロードバランサー", "quantity": 1 },
        { "name": "監視ツール一式", "quantity": 1 }
      ],
      "summary": "社内システムのクラウド移行に伴う見積もり依頼、ボリュームディスカウントも希望",
      "deadline": "6月末",
      "budget_range": "月額120万円程度"
    }
  }
]

例 3:1 通のメールに複数の意図が含まれる場合

入力:

件名: Webhook 機能とオプション追加について

お世話になっております。原田です。

現在 Starter プランを利用中なのですが、Webhook 機能は利用可能でしょうか? ドキュメントを確認したのですがプランごとの機能比較が見つけられず、 ご確認いただければ助かります。

また、あわせて以下のオプション追加も検討しておりまして、 お見積りをいただければ幸いです。できれば今月中にいただけると助かります。 ・専用 IP アドレス: 2 個 ・カスタムドメイン SSL 証明書 ・24 時間サポート窓口

レスポンス:

[
  {
    "name": "inquiry",
    "args": {
      "category": "機能確認",
      "summary": "StarterプランでWebhook機能が利用可能か"
    }
  },
  {
    "name": "quote_request",
    "args": {
      "items": [
        { "name": "専用IPアドレス", "quantity": 2 },
        { "name": "カスタムドメインSSL証明書", "quantity": 1 },
        { "name": "24時間サポート窓口", "quantity": 1 }
      ],
      "summary": "専用IPアドレス、カスタムドメインSSL証明書、24時間サポート窓口の追加見積もり"
    }
  }
]

1 回の LLM 呼び出しで、2 つの異なるスキーマの構造化出力が同時に返ってきています。

まとめ

今回紹介したのは、tool callingを「LLMの判断と構造化出力を1回で得るための設計パターン」として活用するアプローチです。

このパターンが特に有効なのは、以下のようなケースです。

  • 入力に応じて出力のスキーマを変えたい(分類ごとに抽出する情報が異なる)
  • 判断と情報抽出を 1 回の API 呼び出しで済ませたい(コスト・レイテンシの最小化)
  • 1つの入力に複数の意図が含まれうる(複数のtool callが同時に返る)

こうしたシンプルな仕組みは、エージェントを組む際のパーツとしても応用が利きます。設計の引き出しのひとつとして参考になれば幸いです。


採用情報

RightTouchでは一緒に働くエンジニアを募集しています。興味のある方はぜひご覧ください。