Function Callingプロンプト設計のベストプラクティス

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

Function Callingプロンプト設計のベストプラクティス

Function Callingは、大規模言語モデル(LLM)が外部ツールやAPIと連携し、より高度なタスクを実行するための強力な機能です。本記事では、Function Callingを効果的に活用するためのプロンプト設計のベストプラクティスについて、ユースケース定義から評価、誤り分析、改良サイクルまでを体系的に解説します。

1. ユースケース定義

Function Callingは、LLMがユーザーの意図を解釈し、事前定義された関数(ツール)を呼び出すことで、リアルタイム情報取得、計算、外部システム操作などを行います。

典型的なユースケース:

  • 情報検索: リアルタイムの天気、株価、ニュースなどをAPI経由で取得。

  • データ操作: データベースの検索、更新、追加。

  • システム制御: スマートホームデバイスの操作、予約システムとの連携。

  • 計算処理: 複雑な数式計算、データ分析関数の実行。

  • ワークフロー自動化: 複数のツールを連携させたタスクの自動実行。

2. 制約付き仕様化(入出力契約)

Function Callingの安定運用には、明確な入出力契約の定義が不可欠です。

入力仕様:

  • ユーザーリクエスト: 自然言語によるユーザーの指示や質問。

  • 利用可能な関数定義: LLMに提供するツールの詳細な定義。一般的にOpenAPI SpecificationJSON Schema形式で記述され、関数名、説明、引数のスキーマ(型、必須/任意、説明)を含みます。

出力仕様:

  • 成功時の挙動: モデルは、呼び出すべき関数の名前とその引数をJSON形式で出力します。

    {
      "function_call": {
        "name": "function_name",
        "arguments": {
          "arg1": "value1",
          "arg2": "value2"
        }
      }
    }
    
  • 失敗時の挙動: 適切な関数が見つからない場合や、引数が不足している場合は、ユーザーに再質問するか、適切なエラーメッセージを生成します。

    • 例: {"error": "No suitable function found for your request. Please specify more details."}
  • 禁止事項:

    • 定義されていない関数名の呼び出し。

    • 関数の引数スキーマに準拠しない値の生成。

    • 意図しない情報開示や不適切な外部システム操作。

3. プロンプト設計

Function Callingプロンプトは、LLMがユーザーの意図を正確に理解し、最適な関数を適切な引数で呼び出すための指示を含みます。

共通の設計原則:

  1. Systemプロンプトによる役割定義: モデルの役割、利用可能なツール、出力フォーマット、制約を明確に指示します。

  2. 関数定義の明確性: 関数名、説明、引数を人間とLLMの双方にとって理解しやすいように記述します。

プロンプト案1: ゼロショット(Zero-shot)

最も基本的なアプローチで、LLMに直接、ユーザーの要求に基づいて関数呼び出しを生成させます。

SYSTEM:
あなたはユーザーの質問に回答するため、またはタスクを実行するために、提供されたツールを呼び出すことができるアシスタントです。
ツールを呼び出す際は、必ずJSON形式で "function_call" オブジェクトを返してください。
提供されたツール以外は使用しないでください。

TOOLS:
[
  {
    "name": "get_current_weather",
    "description": "指定された都市の現在の天気情報を取得します。",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "天気を知りたい都市名、例: '東京'"
        }
      },
      "required": ["location"]
    }
  }
]

USER:
今日の東京の天気は?

解説: 明確なユーザーの意図に対しては高い精度で機能しますが、曖昧な要求や複雑なシナリオには不向きです。

プロンプト案2: 少数例(Few-shot)

具体的な対話例(ユーザーの質問とそれに対するモデルの適切な応答/関数呼び出し)を提供することで、モデルの学習を助け、複雑なマッピングや微妙なニュアンスを理解させます。

SYSTEM:
あなたはユーザーの質問に回答するため、またはタスクを実行するために、提供されたツールを呼び出すことができるアシスタントです。
ツールを呼び出す際は、必ずJSON形式で "function_call" オブジェクトを返してください。
提供されたツール以外は使用しないでください。

TOOLS:
[
  {
    "name": "get_current_weather",
    "description": "指定された都市の現在の天気情報を取得します。",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "天気を知りたい都市名、例: '東京'"
        }
      },
      "required": ["location"]
    }
  },
  {
    "name": "recommend_movie",
    "description": "ユーザーの好みに基づいて映画を推薦します。",
    "parameters": {
      "type": "object",
      "properties": {
        "genre": {
          "type": "string",
          "description": "希望する映画のジャンル、例: 'SF', 'コメディ'"
        }
      },
      "required": ["genre"]
    }
  }
]

USER:
今日の大阪の天気は?
ASSISTANT:
{"function_call": {"name": "get_current_weather", "arguments": {"location": "大阪"}}}

USER:
何かSF映画をおすすめしてくれない?
ASSISTANT:
{"function_call": {"name": "recommend_movie", "arguments": {"genre": "SF"}}}

USER:
ロンドンの現在の天気は?

解説: ゼロショットよりロバストで、特定のパターン認識を促す際に有効です。プロンプトが長くなる傾向があります。

プロンプト案3: Chain-of-Thought(CoT)制約型

モデルに関数呼び出しに至るまでの思考プロセスを明示的に記述させることで、より複雑な推論を可能にし、誤りを減らします。[1] [4]

SYSTEM:
あなたはユーザーの質問に回答するため、またはタスクを実行するために、提供されたツールを呼び出すことができるアシスタントです。
ツールを呼び出す際は、必ずJSON形式で "function_call" オブジェクトを返してください。
思考プロセスをまず記述し、その後でツール呼び出しを行います。
提供されたツール以外は使用しないでください。

TOOLS:
[
  {
    "name": "get_stock_price",
    "description": "指定された企業の現在の株価を取得します。",
    "parameters": {
      "type": "object",
      "properties": {
        "symbol": {
          "type": "string",
          "description": "企業の証券コードまたはティッカーシンボル、例: 'GOOGL', 'AAPL'"
        }
      },
      "required": ["symbol"]
    }
  }
]

USER:
Googleの株価を教えてください。
ASSISTANT:
THOUGHT: ユーザーはGoogleの株価を知りたがっている。`get_stock_price`ツールが適切である。Googleのティッカーシンボルは'GOOGL'である。
{"function_call": {"name": "get_stock_price", "arguments": {"symbol": "GOOGL"}}}

解説: 透明性が高く、デバッグが容易になります。特に、複数のステップが必要なタスクや、セキュリティ上の確認が必要な場合に有効です。

4. 評価

Function Callingの評価は、モデルの出力が「正しい関数を」「正しい引数で」「正しい形式で」呼び出しているかを確認することが重要です。

評価シナリオ

  • 正例: 「現在のニューヨークの気温は?」→ get_current_weather(location="New York")

  • 難例: 「来週の金曜日に何か予定ある?」 (カレンダー関数に引数をどう渡すか、あるいは関数不要か判断が難しい場合)

  • コーナーケース:

    • 「猫について教えて」 (Function Callingが不要で、通常の情報提供で十分な場合)

    • 「今日の天気は?」 (場所が不明で、関数呼び出しに不十分な場合)

    • 悪意のあるプロンプト: 定義されていない関数を呼び出そうとする場合。

自動評価の擬似コード

LLMの出力に対する自動評価は、Pythonなどのスクリプトで実装できます。

import json
from jsonschema import validate, ValidationError

# 利用可能な関数のスキーマ定義(例)

AVAILABLE_FUNCTIONS = {
    "get_current_weather": {
        "parameters": {
            "type": "object",
            "properties": {"location": {"type": "string"}},
            "required": ["location"]
        }
    },
    "search_news": {
        "parameters": {
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"]
        }
    }
}

def evaluate_function_call(model_output: str, expected_call: dict, available_functions: dict) -> float:
    """
    モデルのFunction Calling出力を評価する。
    スコア: 1.0 (完璧), 0.0 (完全な失敗), 0.1-0.9 (部分的な成功/誤り)
    """
    try:
        output_json = json.loads(model_output)
    except json.JSONDecodeError:
        print("Error: JSON形式が不正です。")
        return 0.1  # 様式崩れ

    if "function_call" not in output_json:
        if expected_call is None:
            return 1.0 # 関数呼び出しが不要で、実際に出力されていない
        print("Error: 'function_call' キーが見つかりません。")
        return 0.2

    call = output_json["function_call"]
    function_name = call.get("name")
    arguments = call.get("arguments")

    if not function_name or not isinstance(arguments, dict):
        print("Error: 'function_call'の構造が不正です。")
        return 0.3 # 構造不正

    if function_name not in available_functions:
        print(f"Error: 存在しない関数名 '{function_name}' が呼び出されました。")
        return 0.4 # 存在しない関数名

    # 引数のスキーマバリデーション

    try:
        validate(instance=arguments, schema=available_functions[function_name]["parameters"])
    except ValidationError as e:
        print(f"Error: 引数がスキーマに準拠していません: {e.message}")
        return 0.5 # 引数スキーマ違反

    if expected_call is None:
        print("Error: 関数呼び出しが不要なケースで呼び出しが発生しました。")
        return 0.6 # 関数呼び出しが不要なケースでの誤った呼び出し

    # 期待される出力との比較

    if function_name == expected_call["name"] and arguments == expected_call["arguments"]:
        return 1.0 # 完全一致
    elif function_name == expected_call["name"] and arguments != expected_call["arguments"]:
        print("Warning: 関数名は一致しましたが、引数が異なります。")
        return 0.8 # 関数名は一致、引数に誤り
    else:
        print("Error: 関数名が一致しません。")
        return 0.7 # 関数名が不一致

# 例


# model_output_ok = '{"function_call": {"name": "get_current_weather", "arguments": {"location": "東京"}}}'


# expected_ok = {"name": "get_current_weather", "arguments": {"location": "東京"}}


# score = evaluate_function_call(model_output_ok, expected_ok, AVAILABLE_FUNCTIONS) # -> 1.0
  • 正規表現: 出力がJSON形式であるか、"function_call"キーが存在するかなどの初歩的な構造チェックに利用できます。

  • 関数評価: モデルが生成した引数を用いて実際にAPIを呼び出し、その結果が妥当であるかを検証することも有効です。

5. 誤り分析と抑制手法

Function Callingにおける主な失敗モードとその抑制手法を以下に示します。[1] [5]

失敗モード

  • 幻覚(Hallucination): 存在しない関数名や引数を生成したり、誤った引数にマッピングしたりします。

  • 様式崩れ(Malformation): JSONスキーマに準拠しない、または破損したJSON形式を出力します。

  • 脱線(Off-topic): 関数呼び出しが適切でないにもかかわらず呼び出しを試みたり、逆に呼び出すべき時に通常の応答を返したりします。

  • 禁止事項違反: セキュリティ上のリスクを招くような不適切な関数呼び出しや情報開示を試みます。

抑制手法

  • System指示の強化:

    • モデルの役割を厳密に定義: 「あなたは提供されたツールのみを使用し、決して新しいツールを発明しないでください。」

    • 出力フォーマットの強制: 「常に有効なJSON形式で出力し、他のテキストを含めないでください。」

    • 失敗時の挙動を指示: 「適切なツールが見つからない場合は、ユーザーに詳細を尋ねてください。」

  • 検証ステップの導入:

    • モデルの出力後に、JSON Schemaバリデーターを用いて構文的・意味的なバリデーションを実行します。

    • 不適切な引数の値(例: 数値型に文字列)を検出します。

  • リトライ戦略:

    • バリデーションに失敗した場合、エラー情報と修正指示(例: 「引数の’location’は文字列である必要があります」)をモデルにフィードバックし、再生成を促します。

    • 特定の回数リトライしても失敗する場合は、ユーザーにエラーを通知します。

  • Few-shot例の追加: 複雑なケースや曖昧な意図に対して、適切なマッピングを学習させるための具体的な例を追加します。

  • CoTの活用: モデルに思考プロセスを記述させることで、推論の透明性を高め、誤った判断を早期に特定・修正します。

6. 改良と再評価

Function Callingのプロンプト設計は、一度で完璧になることは稀です。継続的なフィードバックと改良のループが重要です。

graph TD
    A["初期プロンプト設計"] --> B("LLM Function Calling")
    B --> C{"関数呼び出し出力"}
    C -- |JSONスキーマ検証| --> D{"バリデーション層"}
    D -- |成功| --> E["外部ツール実行"]
    D -- |失敗| --> F["誤り分析"]
    F --> G["プロンプト/関数定義改良"]
    G --> A
    E -- |ツール実行結果| --> H["モデルへのフィードバック/ユーザー応答"]
  1. 誤り分析: 評価シナリオで発見された失敗モードを詳細に分析します。

  2. プロンプト/関数定義の改良: 分析結果に基づき、Systemプロンプト、Few-shot例、CoTの指示、または関数定義自体の改善を行います。

  3. 再評価: 改良されたプロンプトや関数定義を用いて、既存の評価シナリオと新規の難例・コーナーケースで再度評価し、改善度を確認します。

この反復プロセスにより、Function Callingのロバスト性と精度を継続的に向上させることができます。

7. まとめ

Function Callingプロンプト設計は、LLMと外部ツールの効果的な連携を可能にする重要なスキルです。本記事で解説したベストプラクティス、すなわち明確な入出力契約、多様なプロンプト設計手法(ゼロショット、Few-shot、CoT)、体系的な評価、そして誤り分析に基づく継続的な改良サイクルは、堅牢で信頼性の高いFunction Callingシステムを構築するための基盤となります。これらの原則を適用し、あなたのLLMアプリケーションの可能性を最大限に引き出してください。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました