生成AIの活用ノウハウや、 注目の技術に関する記事を掲載
公開

Tool Useとは?モデルとアプリの役割分担から学ぶ生成AIのツール連携

はじめに

「注文A1001は今どこにありますか?」と聞かれても、LLMはその注文の状況を知りません。注文管理システムで伝票番号を調べ、配送会社のシステムで配送状況を確認する必要があります。
外部の関数を使って情報を取得したり、業務システムを操作したりする仕組みが、Tool Useです。モデルが使う関数と引数を選び、アプリが実行結果を返すことで、モデルは処理の結果をもとに回答できます。
この記事では、Amazon BedrockとPythonを使い、注文照会の結果から配送追跡へ進む2段階の処理を実装します。LLMのAPIを呼び出したことがある方を対象に、モデルとアプリの間で何を受け渡すのかを説明します。

Tool Useとは

Tool Useは、LLMが外部のツールを利用して、情報の取得や処理を行うための仕組みです。ツールには、データベースの検索、計算、外部APIの呼び出しなどを用意できます。関数をツールとして定義し、モデルから呼び出す仕組みはFunction Callingとも呼ばれます。
たとえば、注文状況への回答には、学習済みの知識だけでは足りません。注文番号を受け取って現在の情報を調べる処理が必要です。Tool Useを使うと、自然言語の依頼を、アプリが実行できる関数名と引数へつなげられます。

注文照会と配送追跡を組み合わせて回答するTool Use

注文照会と配送追跡を組み合わせて回答するTool Use

Tool Useの応用

Tool Useは、最新の情報や社内データを調べたいとき、決まった計算を行いたいとき、業務システムへ操作を依頼したいときに使えます。たとえば、次のようなツールをアプリ側で用意する構成が考えられます。

用途 利用者の依頼例 用意するツールと処理
注文・在庫の照会 「この商品は在庫がありますか?」 商品検索と在庫照会で、対象商品と現在の在庫数を取得する
社内文書の検索・エージェンティックRAG 「出張の宿泊費の上限と、超過した場合の申請方法を教えて」 規程を検索し、取得した情報に応じて申請手順を追加検索する
集計・計算 「先月の売上を部門別にまとめて」 対象期間の売上データを取得し、集計関数で部門別の金額を求める
予定の調整・登録 「来週、参加者全員が空いている時間に会議を入れたい」 空き時間を検索し、選んだ日時で予定を登録する
問い合わせの起票 「この不具合を担当部署に依頼して」 必要な情報をそろえ、チケット作成APIで依頼を登録する

社内文書の検索では、モデルが検索語を選び、結果に応じて追加の検索を行う構成にできます。宿泊費の例なら、規程から上限額を調べ、「超過時は例外申請が必要」と分かったら、その手順を検索して回答をまとめます。これはエージェンティックRAG(Agentic RAG)の実装方法の一つで、LangChainの解説でも、検索ツールを使うかどうかをエージェントが判断する構成が紹介されています。
以前の記事で紹介した「エージェント検索」も、この検索方針を判断する部分に当たります。テキスト検索やベクトル検索をツールとして提供し、その呼び出しと結果の受け渡しをTool Useでつなぐ、という関係です。

生成AI関連
RAGの検索設計:テキスト検索・ベクトル検索・ハイブリッド検索・エージェント検索の違い

集計の例なら、モデルは「先月」「部門別」という依頼を期間や集計条件へ変換し、数値の計算は関数に任せます。予定の登録やチケット作成のように、外部の状態を変更する使い方もあります。その場合は、実行前に内容を利用者へ提示して確認を得るなど、操作の影響に応じた手順をアプリ側で設けます。
どの機能も、利用したいシステムへ接続する関数やAPIを用意することが前提です。今回のデモでは情報の照会を題材に、注文と配送という2つの情報源をつないで回答する流れを扱います。

モデルには「使える処理」を定義して渡す

アプリは質問と一緒に、利用できるツールの定義をモデルへ渡します。注文照会であれば、次の情報です。

定義する項目 例 モデルに伝える内容
名前 get_order どの処理を要求するかを識別する名前
説明 注文番号から商品・発送状況・配送会社・配送伝票番号を取得する 何ができ、どのような質問に使う処理か
引数の形式 order_idという文字列が必須 実行するために必要な入力と、その型

モデルへ渡すのは、このツール定義です。関数のソースコードや、データベースの全内容を渡す必要はありません。モデルは説明を読み、質問に答えるためにどのツールが必要かを判断します。
「注文A1001の発送状況を教えて」という依頼なら、get_orderとorder_id=A1001を組み合わせた呼び出し要求を返す、という使い方になります。これは通常の返答文とは別の、APIで定められた構造を持つデータです。

ツールの実行を担当するのは、アプリかBedrockか

今回の構成では、PythonアプリからBedrockのAPIへ質問を送り、Bedrock上のモデルが呼び出し要求を返します。この関係で、APIを呼ぶPythonアプリを「クライアント側」、APIを提供するBedrockを「サーバー側」と呼んでいます。Pythonアプリを手元のPCで動かしても、自社のサーバーで動かしても、Bedrockに対してはクライアント側です。
ツールを呼び出して結果をモデルへ戻す処理を、どちらが担当するかで方式が分かれます。

方式 ツール実行までの流れ
クライアント側Tool Use(今回) Bedrockが呼び出し要求を返す → Pythonアプリが関数を実行する → アプリが結果をBedrockへ送る
サーバー側Tool Use Bedrockがモデルの要求を受け、登録されたツールを呼び出して、その結果をモデルへ渡す

たとえば今回の注文照会では、Pythonアプリがget_orderという要求を受け取り、アプリと同じ環境にある注文照会の関数を実行します。実際にどのデータベースへ接続するか、どのAPIを呼ぶかは、その関数の実装で決めます。この処理をConverse APIのクライアント側Tool Useとして書いていきます。
一方、Bedrockのサーバー側Tool Useでは、たとえばAWS Lambdaに用意した関数を登録し、Bedrockにその呼び出しを任せます。この方式では、Bedrockから呼べる実行先をあらかじめ用意します。
どちらも、モデルが使うツールと引数を選び、実行を担当するプログラムが処理する点は共通です。データベースへの接続設定やアクセス権限は、ツール定義とは別に実行側で用意します。

実行結果を返すと、モデルが次の処理を選ぶ

関数が返した注文情報は、アプリからモデルへ渡します。モデルはその結果を会話の続きとして受け取り、ユーザー向けの回答を作ります。関数の戻り値がJSONでも、ユーザーには自然な文章で説明できます。
1回のツール実行で情報がそろわない場合は、モデルが別のツールを要求します。注文照会で得た配送伝票番号を使い、配送追跡へ進むのが今回の例です。モデルが必要なツールを要求し、アプリが結果を返す往復を、回答に必要な情報がそろうまで繰り返します。
すべての質問でツールを使う必要はありません。「発送済みとはどういう意味か」という一般的な質問なら、そのまま回答できます。注文番号がなければ、利用者に聞き返す選択もあります。どの処理が必要かを会話と取得した情報に応じて選べる点が、Tool Useの特徴です。

Structured Outputs・MCPとは役割が異なる

Tool Useは、構造化出力やMCPと組み合わせて使えます。注文照会と配送追跡を例に、役割を整理します。

技術 主な役割 今回のデモとの関係
Tool Use ツールの利用を要求し、その結果を使う 注文照会と配送追跡をつなぐ
Structured Outputs モデルの出力を指定した構造に合わせる 回答のJSON化や、対応する機能でツール引数の形式を制約する場合に使う
MCP AIアプリとツール・データの間の通信方法を共通化する 注文照会などをMCPサーバー経由で提供する場合に使う

ツールの引数を定義することと、出力がその形式に従うよう厳密に制約することは分けて考えます。Bedrockの構造化出力は、対応モデルや機能を確認したうえで使います。

生成AI関連
Amazon Bedrock Structured Outputs実装ガイド―JSON出力の変遷とベ…

また、今回のPythonアプリはローカルの関数を直接実行しているため、MCPサーバーは不要です。ツールを別のアプリからも共通の方法で利用したい場合は、MCPのクライアント・サーバー構成が接続方法の選択肢になります。

生成AI関連
MCPサーバーを活用する【前編:自作編】

デモ:注文番号から配送状況まで調べる

入力は、次の質問です。

注文A1001は今どこにありますか? 配送予定日も教えてください。

ユーザーが伝えるのは注文番号だけです。配送会社と配送伝票番号は、注文を照会して初めて分かるようにします。

ツール 引数 返す情報
get_order order_id:注文番号 商品、発送状況、配送会社、配送伝票番号
get_delivery_status carrier:配送会社、tracking_number:配送伝票番号 配送状況、最終更新日時、配送予定日

説明用データでは、A1001に配送会社demo-expressと伝票番号DEMO-001を登録します。配送データには、その組み合わせに対して「配送中」と配送予定日を登録します。会社名・番号・日時はすべて架空の固定値です。
正常系で想定する流れは、注文照会、配送追跡、回答の順です。モデルは注文照会の結果を受け取ってから、配送追跡の引数を作ります。

Pythonアプリを介してモデルが注文照会と配送追跡の結果を受け取る流れ

Pythonアプリを介してモデルが注文照会と配送追跡の結果を受け取る流れ

ここでは注文システムと配送システムを、別々のPython辞書で代用します。実際のECサイトや配送APIへは接続しません。ツール実装をDB照会やAPI呼び出しに置き換えても、モデルへの要求・結果の受け渡しは同じ考え方で組めます。

実行環境を準備する

Python 3.11以上、uv、Bedrockを呼び出せるAWS認証情報を用意します。モデルには、東京リージョンの推論プロファイルjp.anthropic.claude-haiku-5-5(Claude Haiku 5.5)を使い、動作を確認しました。別のモデルに変える場合は、ConverseのTool Useと、ツール結果の成功・失敗を伝えるstatusに対応しているかを確認してください。利用リージョンの対応状況はAWSのモデル一覧から確認できます。
認証情報には、対象モデルを呼び出すbedrock:InvokeModel権限が必要です。推論プロファイルを使う場合は、その呼び出し先も含めて権限と利用条件を確認します。

2つのツールと、結果を返すループを実装する

デモのコードは、次の4つのファイルに分けます。すべて同じディレクトリに保存してください。

ファイル 役割
tools.py 架空の注文・配送データと、2つのツールの処理本体
tool_config.py モデルに渡すツールの名前・説明・入力形式
agent.py モデルを呼び出し、ツール要求の実行結果を会話へ返すループ
main.py 設定の読み込みと質問の受け付け

以降の手順では、この順に各ファイルの内容を説明します。

1. 注文照会と配送追跡の関数を用意する

tools.pyには、架空データと2つの関数を書きます。未発送のA1002と、配送情報が見つからないA1003も用意します。

tools.py

# 注文・配送会社・伝票番号はすべて説明用の架空データです。
ORDERS = {
    "A1001": {"商品": "ワイヤレスキーボード", "発送状況": "発送済み",
              "配送会社": "demo-express", "配送伝票番号": "DEMO-001"},
    "A1002": {"商品": "USBハブ", "発送状況": "未発送",
              "配送会社": None, "配送伝票番号": None},
    "A1003": {"商品": "モニター", "発送状況": "発送済み",
              "配送会社": "demo-express", "配送伝票番号": "DEMO-999"},
}
DELIVERIES = {
    ("demo-express", "DEMO-001"): {
        "配送状況": "配送中", "最終更新日時": "2026-10-28 09:00 JST",
        "配送予定日": "2026-10-29",
    },
}


def get_order(order_id):
    if order_id not in ORDERS:
        raise ValueError("該当する注文がありません。注文番号を確認してください。")
    return {"注文番号": order_id, **ORDERS[order_id]}


def get_delivery_status(carrier, tracking_number):
    delivery = DELIVERIES.get((carrier, tracking_number))
    if delivery is None:
        raise ValueError("配送情報が見つかりません。現在地・到着日は確認できません。")
    return {"配送会社": carrier, "配送伝票番号": tracking_number, **delivery}


FUNCTIONS = {"get_order": get_order, "get_delivery_status": get_delivery_status}
ARGUMENTS = {"get_order": {"order_id"},
             "get_delivery_status": {"carrier", "tracking_number"}}


def execute_tool(name, arguments):
    if name not in FUNCTIONS:
        raise ValueError("登録されていないツールです。")
    if not isinstance(arguments, dict) or set(arguments) != ARGUMENTS[name]:
        raise ValueError("引数の項目がツール定義と一致しません。")
    if any(not isinstance(v, str) or not v.strip() for v in arguments.values()):
        raise ValueError("引数には空でない文字列を指定してください。")
    return FUNCTIONS[name](**arguments)

execute_toolは、モデルが返した関数名を登録済みの関数と照合します。引数についても、項目名と空でない文字列であることを確認します。注文や配送情報が存在しない場合は、理由をエラーとして返します。

2. モデルに渡すツールの説明を定義する

tool_config.pyには、ツールの名前・説明・入力形式を書きます。関数本体や注文一覧は、この定義には含めません。

tool_config.py

TOOL_CONFIG = {
    "tools": [
        {"toolSpec": {
            "name": "get_order",
            "description": "注文番号から商品・発送状況・配送会社・配送伝票番号を取得する。",
            "inputSchema": {"json": {
                "type": "object",
                "properties": {
                    "order_id": {"type": "string", "description": "利用者が伝えた注文番号"},
                },
                "required": ["order_id"],
                "additionalProperties": False,
            }},
        }},
        {"toolSpec": {
            "name": "get_delivery_status",
            "description": "配送会社と配送伝票番号で現在の配送状況と配送予定日を取得する。",
            "inputSchema": {"json": {
                "type": "object",
                "properties": {
                    "carrier": {"type": "string", "description": "注文照会で取得した配送会社"},
                    "tracking_number": {
                        "type": "string", "description": "注文照会で取得した配送伝票番号",
                    },
                },
                "required": ["carrier", "tracking_number"],
                "additionalProperties": False,
            }},
        }},
    ],
    "toolChoice": {"auto": {}},
}

入力形式はJSON Schemaで指定します。get_delivery_statusの引数の説明には、注文照会で取得した値を使うことを記載しています。
toolChoiceはautoにし、ツールが必要かどうかをモデルに選ばせます。注文番号が足りなければ確認を求め、一般的な質問であればツールを使わず答えることを想定しています。

3. 呼び出し要求を処理し、結果を会話に追加する

agent.pyが処理の中心です。モデルが返したメッセージを履歴へ追加し、終了理由に応じて回答の表示かツール実行へ進みます。

agent.py

import json

from tool_config import TOOL_CONFIG
from tools import execute_tool

SYSTEM = """注文・配送に関する質問へ日本語で簡潔に答えてください。
個別の注文情報・配送情報はツールの結果に基づいて答えてください。
番号や配送会社を推測せず、注文番号が不足していれば利用者へ確認してください。
未発送で配送伝票番号がない場合は、配送追跡せず未発送と伝えてください。
取得できない情報は確認できないと伝え、同じ失敗を繰り返さないでください。"""


def answer(client, model_id, messages):
    tool_count = 0
    for turn in range(1, 6):
        response = client.converse(
            modelId=model_id, messages=messages,
            system=[{"text": SYSTEM}], toolConfig=TOOL_CONFIG,
            inferenceConfig={"maxTokens": 2048},
        )
        message = response["output"]["message"]
        messages.append(message)
        reason = response["stopReason"]
        print(f"\n[モデル呼び出し {turn}] 終了理由: {reason}")
        if reason == "end_turn":
            print("[回答]")
            for block in message["content"]:
                if "text" in block:
                    print(block["text"])
            return True
        if reason != "tool_use":
            print("[中断] 通常の回答完了ではありません。設定と応答を確認してください。")
            return False
        requests = [b["toolUse"] for b in message["content"] if "toolUse" in b]
        if not requests or turn == 5 or tool_count + len(requests) > 8:
            print("[中断] ツール要求が空、または呼び出し上限に達しました。")
            return False
        results = []
        for request in requests:
            name, arguments = request["name"], request["input"]
            tool_count += 1
            print(f"[ツール要求 {tool_count}] {name}")
            print("  引数:", json.dumps(arguments, ensure_ascii=False))
            try:
                data = execute_tool(name, arguments)
                status = "success"
            except ValueError as error:
                data, status = {"エラー": str(error)}, "error"
            print(f"  実行結果: {status}")
            for key, value in data.items():
                print(f"  {key}: {value if value is not None else 'なし'}")
            results.append({"toolResult": {
                "toolUseId": request["toolUseId"],
                "content": [{"json": data}], "status": status,
            }})
        messages.append({"role": "user", "content": results})

注文照会と配送追跡の順序は、このループには書いていません。各回でモデルが返した要求を処理し、その結果を渡して次の判断を受け取ります。
Boto3のConverseリファレンスに沿い、ツール要求のtoolUseIdを対応するtoolResultへ付けています。これにより、どの要求に対する結果なのかをモデルへ伝えます。1回の応答に複数のツール要求がある場合も、それぞれの結果をまとめて返します。
失敗も会話の情報として返します。注文が見つからなければ、アプリをその場で終了させるのではなく、エラー内容を読んだモデルに次の回答を任せます。ただし、API自体の認証・通信エラーはこの処理の対象外です。

4. 質問を受け付ける

main.pyで設定を読み込み、質問を受け付けます。質問と応答の履歴を残すので、モデルから注文番号を聞かれたら、続けて番号を入力できます。

main.py

import os
from pathlib import Path

import boto3
from dotenv import load_dotenv

from agent import answer


def main():
    load_dotenv(Path(__file__).with_name(".env"))
    model_id = os.environ.get("BEDROCK_MODEL_ID", "").strip()
    region = os.environ.get("AWS_DEFAULT_REGION", "").strip()
    if not model_id or not region:
        raise SystemExit(".envにBEDROCK_MODEL_IDとAWS_DEFAULT_REGIONを設定してください。")
    client = boto3.client("bedrock-runtime", region_name=region)
    messages = []
    print(f"モデル: {model_id}\nリージョン: {region}")
    print("架空データのデモです。空入力で終了します。質問はBedrockへ送信されます。")
    while True:
        question = input("\n質問: ").strip()
        if not question:
            break
        messages.append({"role": "user", "content": [{"text": question}]})
        if not answer(client, model_id, messages):
            break


if __name__ == "__main__":
    main()

ファイルを保存したら、次のコマンドで起動します。

bash

uv run main.py

質問欄に「注文A1001は今どこにありますか? 配送予定日も教えてください。」と入力します。空入力で終了します。
実行すると、モデルはget_orderが返したdemo-expressとDEMO-001を、get_delivery_statusの引数に使います。配送情報を取得した後は、商品名と配送状況、配送予定日をまとめて回答しました。モデル呼び出しは3回、ツール実行は2回です。

注文A1001のツール要求・実行結果・回答

注文A1001のツール要求・実行結果・回答

続けて、未発送の注文について質問します。

注文A1002は今どこにありますか?

A1002は未発送のため、配送会社と配送伝票番号が登録されていません。モデルはget_orderの結果から追跡に必要な情報がないことを読み取り、get_delivery_statusを呼ばずに回答しました。モデル呼び出しは2回、ツール実行は1回です。

注文A1002で注文照会だけを要求して回答した結果

注文A1002で注文照会だけを要求して回答した結果

agent.pyのループには、2つのツールを呼ぶ順番を書いていません。システムプロンプトで「未発送で配送伝票番号がない場合は配送追跡しない」という方針を伝え、次にどのツールを呼ぶか、ツールを使わずに回答するかは、取得した情報をもとにモデルが選びます。

業務データへつなぐ前に確認すること

今回のコードは、架空データで仕組みを確かめるための最小構成です。実際の注文システムや配送APIへつなぐ前に、次の4点を見直します。

確認すること このデモでの扱い 業務で追加する対応
要求を実行してよいか 登録済みの関数名と引数の形だけを確認する。利用者の認証や注文の所有者は確認しない その利用者が閲覧できる注文かを、アプリやデータ取得側で確認する
引数の値が正しいか 配送会社・伝票番号は、ツールの説明と会話履歴をもとにモデルが選ぶ。取得元はコードで強制しない 値の一致が必須なら、注文照会で取得済みのデータと照合する
外部へ送られる情報 質問とツールの結果がBedrockへ送信される モデルに渡してよい項目だけをツール結果に含める
繰り返しの上限 1つの質問につき、モデル呼び出しは最大5回、ツール要求は合計8件まで 費用や処理時間の要件に合わせて上限を決める

どのツールを呼ぶかはモデルが選びますが、その要求を実行してよいかはアプリ側で判断します。また、注文をローカルの関数で取得しても、その結果はモデルへ渡すためにBedrockへ送信されます。上限の回数はデモ用の値で、費用や処理時間を保証するものではありません。

まとめ

Tool Useでは、モデルがツールの名前と引数を含む要求を返し、アプリがその関数を実行して結果を渡します。結果を受け取ったモデルは、回答するか、さらに必要なツールを要求します。
注文照会と配送追跡のように、1つ目の結果を次のツールの入力として使うことで、最初の質問だけでは分からなかった情報を取得できます。アプリは実行と結果の返却を担当し、モデルはその情報を使って次の処理や回答を決めます。
まずは参照だけを行う小さな関数を用意し、通常の照会、情報不足、取得失敗の各ケースで、どのツールが呼ばれるかを確認するところから始められます。

生成AI活用支援サービスのご紹介

Tech Funでは、お客様のフェーズに合わせ、生成AI活用に向けた様々な支援をご提供しています。

  1. 無料診断パック:業務・プロセスの現状を無料で診断し、生成AI活用の可能性をレポートします。
  2. 検証(PoC)パック:診断で有効性が確認された業務を対象に、プロトタイプ構築を支援します。
  3. コンサルティングサービス:生成AI導入戦略の策定から運用体制構築までを包括的に支援します。
  4. Plain RAG:社内文書をもとに回答し、社内問い合わせを効率化するAIチャットパッケージです。

生成AIに限らず、Web・業務システム開発やインフラ設計など、技術領域を問わずご相談を承っています。「何から始めれば良いか分からない」という段階でも構いませんので、ぜひお気軽にお問い合わせください。

執筆・編集

Tech Fun Magazine R&Dチーム
Tech Funの生成AI研究に携わるエンジニアが、最新のAIモデル動向やプロンプト設計、実業務への応用手法など、生成AIに特化した知見を執筆・編集しています。
モデル評価や業務シナリオに応じたAI活用設計など、日々のR&D活動で得られる実践的なノウハウをわかりやすく紹介します。

ARTICLE
生成AI関連記事一覧

生成AI関連

Tool Useとは?モデルとアプリの役割分担から学ぶ生成A…

生成AI関連

Google Workspace Studioで問い合わせメ…

生成AI関連

Elephant-Goldfish Modelとは?Goog…

生成AI関連

Google Workspace Studioとは?Gemi…

生成AI関連

判断特化AIモデルとは ― OpenAI Decisions…

生成AI関連

Codex・Claude CodeのCloudWatch設定…

生成AI関連

ChatGPT Workのネットワークアクセスとは?オン・オ…

生成AI関連

Codex・Claude Codeの利用状況をAWSで可視化…

生成AI関連

ChatGPT Workとは?Businessで週次報告を任…

記事一覧を見る