火曜日, 9月 9, 2025
火曜日, 9月 9, 2025
- Advertisment -
ホームニューステックニュースいまさら聞けない? Function calling を Agent Development Kit (ADK) で理解する

いまさら聞けない? Function calling を Agent Development Kit (ADK) で理解する


はじめに

こんにちは、クラウドエース第三開発部のリュウセイです。最近は休日にパンケーキを焼きながら(表面はきつね色派)、AI エージェントと Agent Development Kit(ADK) をキャッチアップしています。

キャッチアップしていく中で、「Function calling(または Tool calling) 」という言葉を見かけることが多くありました。「Function calling(関数呼び出し機能)」 は名前だけ先行しがちで、仕組みが曖昧になりやすい概念です。

結論から言うと、Function calling は Large Language Model(LLM)にツール(=関数)の仕様を与え、自然言語での依頼を 構造化された(スキーマに沿った)ツール実行リクエスト(JSON) に変換してくれる強力な仕組みです。

本記事では、Function calling の概念とその重要性について、「いまさら聞けない」を合言葉に体系的に解説します。構成は 解説 → 図解 → 動作体験 の順で進め、前半で理論的な理解を深め、後半では ADK を使用した実装を通じて、Function calling の動作を実際に体験していただきます。

対象読者

  • Function calling (または Tool calling) の全体像を掴みたい方
  • ADK を使って最小構成で Function calling を体験したい Python 初〜中級者

Function calling を理解する前に、まずは「ツール」という概念を理解します。

ツールの概念図
ツールの概念図

ツールとは、LLM が利用できる外部機能を定義したものです。開発者は、名前・引数(型/必須)・説明などの構造(スキーマ)を事前に共有します。LLM は文脈から必要性を判断し、どのツールをどの引数で使うかを機械可読なリクエスト(JSON)として生成します。実行はアプリケーション側が行い、その結果(ツール実行結果)が LLM に返り、最終的な回答が組み立てられます。

使用されるツールの例として以下が挙げられます。

  • シンプルな関数呼び出し(Web 検索、Web 解析 等)
  • 外部 API 呼び出し(Weather API, Google Maps Platform, 等)
  • 別エージェントへの委譲(音楽生成、動画生成 等)

LLM 単体で「できること」「できないこと」

ツールについて何となく理解したところで、次に LLM 単体の振る舞いについて整理します。
LLM 単体の振る舞いはあくまで事前に学習済みの知識に基づく出力生成であり、
与えられた入力テキストのみを手がかりに応答を構成します。

以下に LLM 単体の振る舞いについて整理した図を示します。

LLM 単体の振る舞い

次で Function calling の詳細説明に進む前に、なぜ今 Function calling が注目されるのかを整理します。

Function calling が注目される理由

Function calling が注目される理由は、LLM の根本的な制約を解決すると考えられているからです。

LLM 単体の限界を突破

  • 知識の鮮度問題:LLM の学習データには学習した時点までの情報しか含まれていないため、最新情報の取得にはツールが必須
  • 副作用のある操作:外部システムへの書き込みや更新などの副作用は、LLM 単体では実行できないためツールで実現
  • 計算処理:複雑な数値計算や専門的なアルゴリズムは、専用ツールに委譲することで精度と効率を向上

Function calling とは?

Function calling は、LLM にツールの仕様(名前・引数・説明)を事前に提供することで、LLM が 「どのツールを、どの引数で呼び出すか」 を自動的に判断し、アプリケーション側でツールを実行して、実行結果を統合した最終回答 を生成する仕組みです。

これにより、LLM は単なるテキスト生成を超えて、外部システムとの連携や動的なデータ取得が可能になります。

より詳細を知りたい方は以下の公式ドキュメントをご覧ください。

https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling

Function calling の全体像

次に、より詳しく Function calling の全体像を見ていきます。
Function calling の全体像を表した図を以下に示します。
Function calling
Function calling の全体像

流れを追いながら、Function calling の全体像を説明します。

  1. ツール宣言(スキーマ提供): まず開発者は、利用可能なツールの一覧を「ツール宣言(ツール仕様)」として、事前に LLM に提供します。これにより、LLM はどのようなツールが使えるのかを把握します。
  2. プロンプト: ユーザーが LLM にプロンプト(指示や質問)を送信します。
  3. LLM による判断・構造化: LLM はプロンプトの内容を解釈し、ツールが必要かどうかを判断します。
  • ツールが不要な場合: 単純な会話など、ツールを使わずに回答できる場合は、そのままユーザーに通常のテキスト応答を返します。
  • ツールが必要な場合: どのツールを使うべきかを判断し、必要な引数を埋めて、ツール名 + 引数 を持つ JSON 形式のデータを生成します。
  1. ツール実行: アプリケーション側の「ツール群」が LLM から生成された JSON を受け取り、外部の API やデータベース、検索エンジンなどに対してリクエストを実行します。
  2. ツール結果の返却: 外部システムからのレスポンスを「ツール結果」として、再び LLM に渡します。
  3. 最終応答の生成: LLM は、ツール結果を元にユーザーへの自然な「最終応答」を生成し、ユーザーに返します(図の青い点線)。

この図からわかる重要なポイントは、LLM が司令塔として機能し、自らコードを実行するわけではないという点です。LLM はあくまで「どのツールを、どの引数で呼び出すべきか」という構造化された指示を生成する役割に徹しており、実際の外部との通信や処理はアプリケーション側の「ツール群」が担っています。

Function calling の動作

Function calling の動作は、以下の 4 つのフェーズ で覚えるとシンプルです。

  1. 定義:開発者がツールの仕様(名前・引数・説明)を LLM に登録
  2. 推論:LLM がユーザーの自然言語入力を解析し、適切なツールと引数を選択
  3. 実行:アプリケーションが選択されたツールを実際に実行
  4. 応答:実行結果を LLM に返し、コンテキストを含む自然言語の回答を生成

Function calling の流れ(フロー図)

Function calling の流れを天気と服装のアドバイスをもらうやりとりを例にフロー図で示します。

リクエスト:

  • 「天気」と「オススメの服装」という 2 つの情報を同時に求める質問

利用可能なツール:

  • get_weather(): 外部 API から天気情報を取得する関数

ここまで、Function calling の概念とその重要性について説明しました。次は、ADK を使って実際に Function calling を体験してみます。

ハンズオン:ADK で Function calling を体験

ここからは実際に手を動かして、Function calling の動作を体験してみましょう。天気情報を取得して服装をアドバイスするエージェントを ADK で構築します。(実装は Vibe Coding で行っています。)

作成するエージェントの概要を以下に示します。

エージェントの概要

セクション 内容
目的 日本語の都市名/対象日(今日/明日)を解析し、天気と服装アドバイスを返す
構成 LlmAgent
– FunctionTool(get_weather)
Open-Meteo API
入出力 下記の「入力」「出力」を参照
最終応答 ツール結果に基づく天気情報と服装提案(自然文)
振る舞い LLM がツール選定と引数推論を行い、実行はアプリ側が担当し結果を最終応答に統合

入力

フィールド 説明
city String 都市名
day String 対象日(today / tomorrow)

出力

フィールド 説明
status String ステータス
error String エラー内容(エラー時のみ)
city String 都市名
date String 日付(例: “1 月 14 日(今日)”)
weather String 天気
weather_code Number 天気コード
is_raining Boolean 雨判定
temperature Number 平均気温
max_temp Number 最高気温
min_temp Number 最低気温
precipitation Number 降水確率(%)
description String 説明文

まずは、ADK について軽く説明します。

Agent Development Kit (ADK) について

Google の Agent Development Kit(ADK)は、エージェント開発を「コードファースト」で進めるためのオープンソース基盤です。Gemini に最適化されつつ、モデルには依存しません。Python/Java の両言語をサポートし、開発〜評価〜運用までを一貫して支援します。

執筆時点では、Python は GA(安定版)、Java は Pre‑GA 段階です(Java の詳細は こちら を参照)。

ADK についてより詳細を知りたい方は以下をご覧ください。

以下のステップから実際に ADK を使用して Function calling を体験していきます。

ステップ 1:ディレクトリ構成の準備

以下のディレクトリ構成でプロジェクトを構築します。

weather-function-calling/
├── .env
└── weather_agent/
    ├── __init__.py
    ├── agent.py
    └── tools.py

ステップ 2:天気取得ツールの実装

以下では、Open-Meteo API を使用して実際の天気情報を取得するツールを実装します。

tools.py の実装詳細

weather_agent/tools.py

from typing import Dict, Any
from google.adk.tools import FunctionTool, ToolContext
import requests
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo


def get_weather(city: str, day: str = "today", tool_context: ToolContext = None) -> Dict[str, Any]:
    """指定された都市の天気情報を取得します。

    このツールは、ユーザーが明確に天気情報を要求した場合にのみ使用してください。
    天気に関係のない一般的な質問には使用しないでください。

    Args:
        city: 天気情報を取得したい都市名
        day: 対象日 - "today"(今日)または "tomorrow"(明日)

    Returns:
        天気情報を含む辞書。気温、天気状況、降水確率が含まれます。
        例: {'status': 'success', 'city': 'Tokyo', 'weather': '晴れ', 'temperature': 22.0}
    """
    if not city or not city.strip():
        return {"error": "都市名が必要です", "status": "failed"}

    try:
        geo_resp = requests.get(
            "https://geocoding-api.open-meteo.com/v1/search",
            params={"name": city.strip(), "count": 1, "language": "en"},
            timeout=10
        )
        geo_resp.raise_for_status()
        geo_data = geo_resp.json()

        if not geo_data.get("results"):
            return {"error": f"都市を見つけることができませんでした: {city}", "status": "failed"}

        location = geo_data["results"][0]
        lat, lon = location["latitude"], location["longitude"]
    except requests.RequestException as e:
        return {"error": f"地名検索APIエラー: {str(e)}", "status": "failed"}
    except (KeyError, IndexError) as e:
        return {"error": f"地名検索レスポンスが無効です: {str(e)}", "status": "failed"}

    is_tomorrow = "tomorrow" in day.lower() or "明日" in day
    tz = ZoneInfo("Asia/Tokyo")
    current_date = datetime.now(tz).date()
    target_date = current_date + (timedelta(days=1) if is_tomorrow else timedelta(days=0))

    date_str = f"{target_date.month}{target_date.day}日({'明日' if is_tomorrow else '今日'})"

    try:
        weather_resp = requests.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": lat,
                "longitude": lon,
                "daily": "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_mean",
                "timezone": "Asia/Tokyo"
            },
            timeout=10
        )
        weather_resp.raise_for_status()
        weather_data = weather_resp.json()

        daily = weather_data["daily"]
        target_str = target_date.isoformat()

        if target_str not in daily["time"]:
            return {"error": f"予報データが利用できません: {target_str}", "status": "failed"}

        idx = daily["time"].index(target_str)
        code = daily["weather_code"][idx]
        tmax = daily["temperature_2m_max"][idx]
        tmin = daily["temperature_2m_min"][idx]
        pop = daily["precipitation_probability_mean"][idx]

    except requests.RequestException as e:
        return {"error": f"天気APIエラー: {str(e)}", "status": "failed"}
    except (KeyError, IndexError, ValueError) as e:
        return {"error": f"天気レスポンスが無効です: {str(e)}", "status": "failed"}

    weather_map = {
        0: "快晴", 1: "晴れ", 2: "くもり", 3: "曇り", 45: "霧", 48: "霧",
        51: "小雨", 53: "霧雨", 55: "雨", 61: "小雨", 63: "雨", 65: "強雨",
        71: "小雪", 73: "雪", 75: "大雪", 80: "にわか雨", 81: "にわか雨",
        82: "にわか雨", 95: "雷雨"
    }
    weather = weather_map.get(code, "不明")

    
    rain_codes = {51, 53, 55, 61, 63, 65, 80, 81, 82, 95}
    is_raining = code in rain_codes

    result = {
        "status": "success",
        "city": location.get("name", city),
        "date": date_str,
        "weather": weather,
        "weather_code": code,
        "is_raining": is_raining,
        "temperature": round((tmax + tmin) / 2, 1),
        "max_temp": round(tmax, 1),
        "min_temp": round(tmin, 1),
        "precipitation": pop,
        "description": f"{date_str}{weather}、降水確率{pop}%、最高{round(tmax, 1)}℃/最低{round(tmin, 1)}℃"
    }

    if tool_context:
        tool_context.state["last_weather_check"] = result

    return result


available_tools = [FunctionTool(func=get_weather)]

ステップ 3:エージェントの作成

ツールを定義したら、それらを使用するエージェントを作成します。

__init__.py の実装詳細

weather_agent/__init__.py

from .agent import root_agent
agent.py の実装詳細

weather_agent/agent.py

from google.adk.agents import Agent
from .tools import available_tools

root_agent = Agent(
    model="gemini-2.5-flash",
    name="weather_agent",
    description="天気情報と服装アドバイスを提供するエージェント",
    instruction="""あなたは天気・服装アドバイザーです。

## 都市名の処理について
- 日本の都市名(東京、大阪、札幌など)は、get_weatherツールを呼び出す際に英語名(Tokyo、Osaka、Sapporoなど)に変換してください
- 英語圏の都市名はそのまま使用してください

## 回答要件
- ツールレスポンスの`date`フィールドの値(例:「1月14日(今日)」「1月15日(明日)」)を必ず回答に含めてください
- 単に「今日」「明日」ではなく、具体的な日付を明記してください
- ツールレスポンスの`status`フィールドを確認し、「failed」の場合はエラー内容をユーザーに説明してください

## 作業手順
1. get_weatherツールを使用して天気情報を取得
2. **APIの天気コードを最優先で判断**:
   - 雨の強度に応じた傘の種類を提案:
     * 小雨・霧雨:折りたたみ傘を持参
     * 雨・強雨・にわか雨・雷雨:しっかりとした傘が必要
   - 降水確率の数値に関係なく、APIが雨を検出していれば雨対策を最優先
   - 曇り:天気の変化に注意、念のため折りたたみ傘
   - 晴れ:日差し対策を推奨
3. 気温に基づく服装提案(雨の場合は天気に応じた傘も併記):
   - 10℃未満:コートやダウンジャケット + 雨の強度に応じた傘
   - 10-15℃:ジャケットやセーター + 雨の強度に応じた傘
   - 15-20℃:長袖やカーディガン + 雨の強度に応じた傘
   - 20-25℃:半袖や薄手の長袖 + 雨の強度に応じた傘
   - 25℃以上:半袖やタンクトップ + 雨の強度に応じた傘
4. **総合判断**:天気状況と気温の両方を考慮した実用的なアドバイス

## 回答例
- 晴れの場合:「1月14日(今日)の東京は晴れで、気温22℃です。この気温なら半袖か薄手の長袖が快適でしょう。」
- 小雨の場合:「1月25日(今日)の大阪は小雨で、気温29℃です。暖かいので半袖で大丈夫ですが、小雨が降っているので折りたたみ傘を持参してください。」
- 強い雨の場合:「1月15日(明日)の東京は雨で、気温8℃です。寒いのでコートが必要で、雨もしっかり降るのでしっかりとした傘をお持ちください。」

## 重要な注意事項
- **必ず回答の最後に免責事項を追加**:「※この情報は予報データに基づいており、実際の天気と異なる場合があります。」
- 実際の天気と異なる場合は、ユーザーの指摘を受け入れて謝罪し、予報データの限界を説明

簡潔で正確な天気情報と実用的な服装アドバイスを提供してください。""",
    tools=available_tools
)

ステップ 4:ADK Web での動作確認

ADK はローカルで Web UI を提供できます。プロジェクト直下で以下を実行します。

ブラウザで http://localhost:8000 が開きます。左側のエージェント選択から、作成した weather_agent を選び、「東京の今日の天気は?」のように入力して動作を確認していきます。

ADK Web
ADK Web の UI

ステップ 5:実際に動かす

動作確認のポイント

  1. ツールの自動選択:天気に関する質問 → get_weather ツールが自動で選択される
  2. 引数の推論:都市名と日付をユーザー入力から適切に抽出・変換
  3. エラーハンドリング:存在しない都市名や API エラー時の適切な応答
  4. 自然な統合:ツール結果を使った自然で実用的な回答生成

試してみる質問例

以下の 3 つの質問を入力して動作を確認してみます。

- 東京の今日の天気は?
- 京都の明日は雨ですか?
- 火星の明日の天気は?

ステップ 6:動作確認の結果

まずは、「東京の今日の天気は?」の質問を入力して動作を確認してみます。
質問したところ、以下の結果が得られました。

ADK Web 東京の今日の天気
東京の今日の天気の結果

結果から、LLM が天気情報を取得するために get_weather ツールを呼び出して回答を生成していることがわかります。ADK は「イベント」という単位で実行の流れと履歴を管理します。イベントについての詳細は公式のドキュメントをご覧ください。

まずは、Function Call イベント(関数呼び出し)を見てみます。

ADK Web 東京の今日の天気
Function Call イベントのログ

Function Call イベントには、LLM が呼び出した関数名と与えた引数が記録されています。
この例では、プロンプト「東京の今日の天気は?」に対して、LLM が get_weather を選択し、引数として day: "today"city: "Tokyo" を付与して実行していることが確認できます。

次に、Function Response イベント(関数呼び出しの結果)を見てみます。

ADK Web 東京の今日の天気
Function Response イベントのログ

Function Response イベントには、get_weather から返却された以下の JSON 結果が記録されています。

{
  "response": {
    "status": "success",
    "city": "Tokyo",
    "date": "8月26日(今日)",
    "weather": "曇り",
    "weather_code": 3,
    "is_raining": false,
    "temperature": 28.6,
    "max_temp": 32.7,
    "min_temp": 24.5,
    "precipitation": 4,
    "description": "8月26日(今日)の曇り、降水確率4%、最高32.7℃/最低24.5℃"
  }
}

この JSON 結果をもとに、次の最終応答イベントで自然文の回答が生成されます。

ADK Web 東京の今日の天気
東京の今日の天気の結果 イベントログ

回答結果

8月26日(今日)の東京は曇りで、気温は28.6℃です。
最高気温は32.7℃、最低気温は24.5℃の予報です。
この気温なら半袖やタンクトップが快適でしょう。
曇りですが、念のため折りたたみ傘を持参すると安心です。

※この情報は予報データに基づいており、実際の天気と異なる場合があります。

この最終応答は、直前の Function Response に含まれる JSON の値をもとに、設定された指示に従って日本語で整形されたものです。特に方針どおり、天気コードを優先して傘の有無・種類を判断し、気温帯に応じた服装提案を付与しています。ADK ではこの一連のやり取りがイベントとして記録され、Function CallFunction Response → 最終応答の順で Function calling の処理の流れを確認できます。

他の質問についても結果を見てみます。(処理は同様のため詳細は省略します。)

- 京都の明日は雨ですか?

ADK Web 京都の明日は雨ですか?
京都の明日は雨ですか?の結果

記事執筆時点では、Google 天気予報では雨の予報であったため、そのままの正しい結果が返ってきています。(日本語の自然さには若干の改善余地がありますが。。。)

最後に、存在しない都市名を入力してみます。

- 火星の明日の天気は?

ADK Web 火星の明日の天気
火星の明日の天気の結果

上記の質問に対する最終応答は、次のとおりでした。

申し訳ありませんが、火星の天気予報はできません。
私は地球上の都市の天気予報のみを提供できます。

このやり取りでは、Function Call を実行せずに LLM が直接最終応答を生成しています。get_weather の地名検索(Open‑Meteo Geocoding)は地球上の都市を前提としているため、「火星」は一致せず、エージェントの指示に従って制約事項を説明する応答を返しました。

また、明示的に get_weather を指定しても、「火星の天気」は対象外であるため、ツール呼び出しは行われず、同様に制約説明の最終応答が返ります。

ADK Web 火星の明日の天気
火星の明日の天気の結果

これまでの結果を要約すると、以下の通りです。

  1. 「東京の今日の天気」: Function Callget_weather)→ Function Response(成功)→ 最終応答(天気・服装)
  2. 「京都の明日は雨ですか?」: 同様に成功し、最終応答で天気傾向を提示
  3. 「火星の明日の天気」: 対象外のためツール未実行で最終応答(制約説明)

いずれのケースも、LLM がツールを呼び出すかどうかを判断し、その結果を最終応答に反映しています。

まとめ

ここまで Function calling について、その概念から ADK での実装まで一通り体験してきました。Function calling は、LLM が外部システムと連携するための仕組みです。自然言語での依頼から構造化されたツール呼び出しに変換され、その結果を統合した回答が得られます。

最近話題の MCP(Model Context Protocol) って、実は Function calling の概念を理解していると、より理解しやすくなるんじゃないかと思いました。

参考文献



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -