本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
LLMのFunction Callingと構造化出力: プロンプト設計と評価戦略
- はじめに
- ユースケース定義
- 入出力契約(制約付き仕様化)
- プロンプト設計
- 出力: {“function_call”: {“name”: “get_weather”, “arguments”: {“location”: “東京”, “date”: “{{今日の日付: YYYY-MM-DD}}”}}}`
- 出力: {“function_call”: {“name”: “get_weather”, “arguments”: {“location”: “京都”, “date”: “{{来週金曜日の日付: YYYY-MM-DD}}”}}}`
- 出力: {“error”: “Function not matched.”}
- 誤り分析と抑制手法
- 改良と再評価のループ
- まとめ
はじめに
大規模言語モデル(LLM)の進化により、自然言語処理の可能性は大きく広がっています。特に、LLMが外部ツールやAPIと連携するための「Function Calling」機能や、LLMからの出力を機械可読な形式で取得する「構造化出力」は、LLMをシステムに組み込む上で不可欠な要素となっています。Function Callingは、LLMがユーザーの意図を解釈し、適切なツールを特定して引数を生成することで、システムが実行すべきアクションを指示します。構造化出力は、JSONやXMLなどの形式で情報を抽出し、後続の処理やデータベースへの保存を容易にします。 、これらの強力な機能を効果的に活用するためのプロンプト設計、評価戦略、そして一般的な課題と対処法について、プロンプトエンジニアリングの観点から解説します。
ユースケース定義
本記事では、以下のユースケースを想定してプロンプト設計と評価を論じます。
Function Callingのユースケース:
目的: ユーザーからの自然言語によるリクエストから、天気予報APIを呼び出すための都市名と日付(任意)を抽出する。
例: 「明日の東京の天気は?」「来週の京都の気温を教えて」
構造化出力のユースケース:
目的: ユーザーからの商品レビューテキストから、商品名、評価点(1-5)、ポジティブ/ネガティブなコメントをJSON形式で抽出する。
例: 「このスマホ、電池持ちが最高だけどカメラがイマイチ。総合評価は4点。」
入出力契約(制約付き仕様化)
LLMの挙動を予測可能にするため、厳密な入出力契約を定義します。
入力:
形式: 自然言語のテキスト(最大1000トークン)。
前提: ユーザーの意図または対象データを含む。
出力:
形式: Function Callingの場合は、呼び出す関数名と引数を格納した厳格なJSON形式。構造化出力の場合は、指定されたJSON Schemaに準拠したJSON形式。
例(Function Calling):
{"function_call": {"name": "get_weather", "arguments": {"location": "東京", "date": "2024-07-27"}}}例(構造化出力):
{"product_name": "スマホX", "rating": 4, "comments": {"positive": ["電池持ちが良い"], "negative": ["カメラがイマイチ"]}}
文字エンコーディング: UTF-8。
失敗時の挙動:
関数が特定できない、またはスキーマに合致しない場合: モデルは
{"error": "Function or schema not matched."}のようなエラーメッセージを含むJSONを生成するか、Function Callingの場合はツールコールを生成しない。JSON形式のバリデーションエラー: モデル出力後に外部バリデーターで検出し、リトライまたはエラー通知を行う。
禁止事項:
Function Calling: 定義されていない関数名や引数の生成。
構造化出力: JSONスキーマで定義されていないフィールドの追加、指定されたデータ型(例: 数値が文字列)からの逸脱。
無関係な情報や説明文の混入。
プロンプト設計
最低3種のプロンプト案を提示します。
1. ゼロショットプロンプト (Zero-Shot Prompt)
モデルに明示的な例を与えず、タスクの指示と出力形式のみを伝えます。
あなたはユーザーの入力に基づいて、適切な関数を呼び出すためのJSONを生成するAIアシスタントです。
利用可能な関数は以下の通りです。
```json
{
"functions": [
{
"name": "get_weather",
"description": "指定された場所と日付の天気予報を取得します。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "天気予報を取得する都市名。必須。",
"enum": ["東京", "大阪", "京都", "札幌", "福岡"]
},
"date": {
"type": "string",
"format": "date",
"description": "天気予報を取得する日付(YYYY-MM-DD形式)。デフォルトは今日。"
}
},
"required": ["location"]
}
}
]
}
指示: ユーザーの入力から関数呼び出しに必要な引数を抽出し、{"function_call": {"name": "...", "arguments": {...}}}形式のJSONを生成してください。関数が特定できない場合は{"error": "Function not matched."}と出力してください。
ユーザー入力: 明日の札幌の天気を教えてください。
### 2. 少数例プロンプト (Few-Shot Prompt)
具体的な入出力例をいくつか提示し、モデルにタスクのパターンを学習させます。
```text
あなたはユーザーの入力に基づいて、適切な関数を呼び出すためのJSONを生成するAIアシスタントです。
利用可能な関数は以下の通りです。
```json
{
"functions": [
{
"name": "get_weather",
"description": "指定された場所と日付の天気予報を取得します。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "天気予報を取得する都市名。必須。",
"enum": ["東京", "大阪", "京都", "札幌", "福岡"]
},
"date": {
"type": "string",
"format": "date",
"description": "天気予報を取得する日付(YYYY-MM-DD形式)。デフォルトは今日。"
}
},
"required": ["location"]
}
}
]
}
以下の例を参考に、ユーザー入力から関数呼び出しに必要な引数を抽出し、{"function_call": {"name": "...", "arguments": {...}}}形式のJSONを生成してください。関数が特定できない場合は{"error": "Function not matched."}と出力してください。
ユーザー入力: 東京の今日の天気は?
出力: {“function_call”: {“name”: “get_weather”, “arguments”: {“location”: “東京”, “date”: “{{今日の日付: YYYY-MM-DD}}”}}}`
ユーザー入力: 来週金曜日の京都の天気はどうなる?
出力: {“function_call”: {“name”: “get_weather”, “arguments”: {“location”: “京都”, “date”: “{{来週金曜日の日付: YYYY-MM-DD}}”}}}`
ユーザー入力: ニューヨークの気温を教えて。
出力: {“error”: “Function not matched.”}
ユーザー入力: 明日の札幌の天気を教えてください。
### 3. Chain-of-Thought制約型プロンプト (CoT Constrained Prompt)
モデルに思考プロセスを段階的に指示し、各ステップで制約を設けます。
```text
あなたはユーザーの入力に基づいて、適切な関数を呼び出すためのJSONを生成するAIアシスタントです。
利用可能な関数は以下の通りです。
```json
{
"functions": [
{
"name": "get_weather",
"description": "指定された場所と日付の天気予報を取得します。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "天気予報を取得する都市名。必須。",
"enum": ["東京", "大阪", "京都", "札幌", "福岡"]
},
"date": {
"type": "string",
"format": "date",
"description": "天気予報を取得する日付(YYYY-MM-DD形式)。デフォルトは今日。"
}
},
"required": ["location"]
}
}
]
}
以下の手順に従って、ユーザー入力から関数呼び出しに必要な引数を抽出し、JSONを生成してください。
意図特定: ユーザー入力がどの関数を呼び出そうとしているか特定します。利用可能な関数リストから選択してください。関数が特定できない場合は、「Function not matched」と判断してください。
引数抽出: 選択した関数のパラメーター定義に従い、ユーザー入力から必要な引数(location, date)を厳密に抽出します。
location: 必ずenumで指定された都市名から選んでください。date: 必ずYYYY-MM-DD形式に変換してください。今日を示す場合は「{{JST_TODAY: YYYY-MM-DD}}」を使用してください。
JSON生成: 抽出した情報に基づいて、
{"function_call": {"name": "...", "arguments": {...}}}形式のJSONを生成します。意図特定で「Function not matched」と判断した場合は、{"error": "Function not matched."}を生成してください。
ユーザー入力: 明日の札幌の天気を教えてください。
## 評価
LLMの出力を客観的に評価するためのシナリオと自動評価の擬似コードを定義します。
### 評価シナリオ
* **正例**:
* `「今日の東京の天気」` -> `get_weather(location="東京", date="{{2024-07-26}}")`
* `「この本、面白かったけど終わり方が微妙。総合評価は3点」` -> `{"product_name": "本", "rating": 3, "comments": {"positive": ["面白かった"], "negative": ["終わり方が微妙"]}}`
* **難例**:
* `「来週の火曜日の福岡の気温は?」` (日付計算が必要)
* `「これは最高のゲーム!だけど少しバグがある。4点」` (商品名が明示されていない)
* **コーナーケース**:
* `「天気予報を教えて」` (場所が不明) -> `{"error": "Function not matched."}`
* `「評価は5点。でも製品名は何だろう?」` (商品名がない) -> `{"error": "Schema not matched (missing product_name)."}` (スキーマ定義による)
### 自動評価(擬似コード)
採点ルーブリックに基づき、Pythonで記述された擬似コードで自動評価を行います。
```python
import json
import re
from datetime import datetime, timedelta
# JSON Schema for Function Calling output
FUNCTION_CALL_SCHEMA = {
"type": "object",
"properties": {
"function_call": {
"type": "object",
"properties": {
"name": {"type": "string"},
"arguments": {"type": "object"}
},
"required": ["name", "arguments"]
},
"error": {"type": "string"}
},
"oneOf": [
{"required": ["function_call"]},
{"required": ["error"]}
]
}
# JSON Schema for Structured Output (example for product review)
REVIEW_SCHEMA = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"rating": {"type": "integer", "minimum": 1, "maximum": 5},
"comments": {
"type": "object",
"properties": {
"positive": {"type": "array", "items": {"type": "string"}},
"negative": {"type": "array", "items": {"type": "string"}}
},
"required": ["positive", "negative"]
}
},
"required": ["product_name", "rating", "comments"]
}
def validate_json_schema(data, schema):
"""
JSONデータを指定されたスキーマで検証します。
前提: 'jsonschema'ライブラリがインストールされていること。
入出力: data (dict), schema (dict) -> bool
計算量: スキーマの複雑さに比例。
メモリ: スキーマとデータのサイズに比例。
"""
try:
from jsonschema import validate
validate(instance=data, schema=schema)
return True
except Exception as e:
print(f"Schema validation error: {e}")
return False
def evaluate_function_call_output(llm_output: str, expected_call: dict, jst_today_str: str) -> dict:
"""
LLMのFunction Calling出力を評価します。
入出力: llm_output (str), expected_call (dict), jst_today_str (str) -> dict
前提: llm_outputはJSON形式、jst_today_strはYYYY-MM-DD形式。
計算量: JSONパースと辞書比較に依存。
メモリ: 出力と期待値のサイズに比例。
"""
score = 0
feedback = []
try:
output_data = json.loads(llm_output)
except json.JSONDecodeError:
feedback.append("Malformed JSON output.")
return {"score": 0, "feedback": feedback}
if not validate_json_schema(output_data, FUNCTION_CALL_SCHEMA):
feedback.append("Output does not conform to Function Calling schema.")
return {"score": 0, "feedback": feedback}
if "error" in output_data:
if "error" in expected_call and output_data["error"] == expected_call["error"]:
score += 1 # エラーメッセージが一致
feedback.append("Correctly identified as error and matched error type.")
else:
feedback.append(f"Identified as error but expected a function call or different error. Expected: {expected_call}, Got: {output_data}")
return {"score": score, "feedback": feedback}
if "function_call" not in output_data:
feedback.append("Expected function call but not found.")
return {"score": 0, "feedback": feedback}
actual_call = output_data["function_call"]
# 関数名の検証
if actual_call.get("name") == expected_call["name"]:
score += 0.5
feedback.append("Function name matched.")
else:
feedback.append(f"Function name mismatch. Expected: {expected_call['name']}, Got: {actual_call.get('name')}")
# 引数の検証
actual_args = actual_call.get("arguments", {})
expected_args = expected_call["arguments"]
# 日付の動的処理
if "date" in expected_args and expected_args["date"] == "{{2024-07-26}}":
expected_args["date"] = jst_today_str
elif "date" in expected_args and expected_args["date"].startswith("{{"):
# 例: {{来週金曜日の日付: YYYY-MM-DD}}のようなパターンを処理
match_date_pattern = re.search(r"\{\{(.+?)(?::\s*(.+?))?\}\}", expected_args["date"])
if match_date_pattern:
date_modifier = match_date_pattern.group(1).lower()
today = datetime.strptime(jst_today_str, "%Y-%m-%d")
target_date = today
if "明日" in date_modifier:
target_date += timedelta(days=1)
elif "来週金曜日" in date_modifier:
# 今日の曜日 (月=0, ... 土=5, 日=6)
days_until_friday = (4 - today.weekday() + 7) % 7
if days_until_friday == 0: # 今日が金曜日の場合、次の金曜日
days_until_friday = 7
target_date += timedelta(days=days_until_friday + 7) # 次の金曜日
expected_args["date"] = target_date.strftime("%Y-%m-%d")
# 全ての期待される引数が存在し、値が一致するか
all_args_match = True
for key, value in expected_args.items():
if key not in actual_args or actual_args[key] != value:
all_args_match = False
feedback.append(f"Argument '{key}' mismatch. Expected: '{value}', Got: '{actual_args.get(key)}'")
break
if all_args_match:
score += 0.5
feedback.append("All expected arguments matched.")
return {"score": score, "feedback": feedback}
# --- 評価実行例 ---
# jst_today = "2024-07-26" # JSTの今日の日付
# # 正例
# llm_output_correct = '{"function_call": {"name": "get_weather", "arguments": {"location": "東京", "date": "2024-07-26"}}}'
# expected_correct = {"name": "get_weather", "arguments": {"location": "東京", "date": "{{2024-07-26}}"}}
# print(evaluate_function_call_output(llm_output_correct, expected_correct, jst_today)) # 期待: {"score": 1.0, "feedback": ["Function name matched.", "All expected arguments matched."]}
# # 難例 (日付計算)
# # 仮に "来週火曜日" が 2024-07-30 とする (2024-07-26が金曜日なので、来週火曜日は4日後)
# jst_today = "2024-07-26" # 金曜日
# llm_output_difficult = '{"function_call": {"name": "get_weather", "arguments": {"location": "福岡", "date": "2024-07-30"}}}'
# expected_difficult = {"name": "get_weather", "arguments": {"location": "福岡", "date": "{{来週火曜日の日付: YYYY-MM-DD}}"}}
# print(evaluate_function_call_output(llm_output_difficult, expected_difficult, jst_today))
# # コーナーケース (場所不明)
# llm_output_error = '{"error": "Function not matched."}'
# expected_error = {"error": "Function not matched."}
# print(evaluate_function_call_output(llm_output_error, expected_error, jst_today))
誤り分析と抑制手法
LLMは完全に完璧ではなく、いくつかの失敗モードが存在します。
失敗モード
幻覚(Hallucination):
存在しない関数名や引数、または定義されていない
enum値などを生成する。構造化出力において、ソーステキストにない情報を捏造する。
様式崩れ(Malformed output):
JSON形式が壊れている、必須フィールドが欠落している、データ型が異なる(例: 数値が文字列になっている)。
Function Callingで、
function_callオブジェクトやargumentsオブジェクトが欠落している。
脱線(Off-topic generation):
関数呼び出しのJSONだけでなく、余計な説明文やコメントを付加する。
構造化出力のJSONの後に、関連性のない自然言語テキストを続けて生成する。
禁止事項の無視:
- プロンプトで明示的に禁止された動作(例: 特定のキーワードの使用、外部URLへのアクセス試行)を実行する。
抑制手法
System指示の強化:
プロンプトの冒頭で「あなたはJSONのみを生成するAIアシスタントです。一切の追加テキストは禁止します。」といった明確な役割と出力形式の制約を課す。
Function Callingのスキーマを正確にプロンプトに含め、
enumなどの制約を強調する。2024年4月10日にGoogle Cloud Blogで発表されたGeminiモデルの機能強化により、より複雑なFunction Callingの指示や引数生成の精度が向上しています[3]。
出力の検証ステップ:
LLMからの出力後、JSON Schemaバリデーションライブラリ(Pythonの
jsonschemaなど)を使用して、生成されたJSONが定義されたスキーマに準拠しているかを確認する。Pydanticのようなライブラリを利用し、モデルの出力から直接Pythonオブジェクトを生成し、型チェックとバリデーションを自動化する。
リトライ戦略:
バリデーションエラーが発生した場合、エラーメッセージとともに元のプロンプトを修正し、モデルに再試行させる。
例: 「出力されたJSONはスキーマに準拠していません。以下のエラーを修正し、再度JSONを生成してください: [エラーメッセージ]」
Few-shot学習の質の向上:
提供する少数例は、多様なケース(成功例、エラー例、エッジケース)を網羅し、誤解を招かないように注意深く選定する。
OpenAIのFunction Callingに関するガイド[1]やGoogleのFunction Callingドキュメント[2]でも、具体的なスキーマ定義や例の提示が重要であると示唆されています。
改良と再評価のループ
プロンプト設計は一度で完璧になるものではなく、継続的な評価と改良が必要です。
graph TD
A["ユーザー入力"] --> B{"プロンプト設計"};
B --> C["LLM推論"];
C --> D{"LLM出力"};
D --> E{"出力検証"}|JSON Schemaバリデーション/型チェック|;
E -- 成功 --> F["評価指標計算"]|正解率/F1スコアなど|;
E -- 失敗 --> G["誤り分析"]|幻覚/様式崩れ/脱線|;
F --> H{"評価結果/レポート"};
G --> H;
H -- 改善点あり --> B;
H -- 改善完了 --> I["デプロイ"];
このループでは、ユーザー入力からLLMへのプロンプト設計、LLMからの出力、その出力の検証、そして評価と誤り分析を行います。評価結果や誤り分析で見つかった課題は、プロンプトの改良(System指示の調整、Few-shot例の追加、CoTステップの修正など)にフィードバックされ、精度が目標レベルに達するまで繰り返されます。
まとめ
LLMのFunction Callingと構造化出力は、AIシステムをより柔軟かつ堅牢にするための重要な技術です。効果的なプロンプト設計、厳密な入出力契約、包括的な評価シナリオ、そして反復的な改良サイクルを通じて、LLMの精度と信頼性を最大限に引き出すことができます。幻覚や様式崩れといった失敗モードに対しては、プロンプトでの明確な指示、出力後の検証、リトライ戦略を組み合わせることで、堅牢なシステム構築が可能です。今後もモデルの進化とともに、これらの技術の応用範囲はさらに広がっていくでしょう。
参考文献 [1] OpenAI Developers. “Function calling.” (2023年11月6日更新). https://platform.openai.com/docs/guides/function-calling [2] Google AI for Developers. “Function calling in Gemini models.” (2024年1月公開). https://ai.google.dev/docs/function_calling [3] Google Cloud Blog. “New updates to Gemini models: enhanced function calling and more.” (2024年4月10日). https://cloud.google.com/blog/products/ai-machine-learning/new-updates-to-gemini-models-enhanced-function-calling-and-more?hl=ja

コメント