<p><!--META
{
"title": "LLMにおけるFunction Callingと構造化出力の設計と評価",
"primary_category": "LLM開発",
"secondary_categories": ["プロンプトエンジニアリング", "API連携"],
"tags": ["Function Calling", "Structured Output", "JSON Schema", "Gemini", "OpenAI"],
"summary": "LLMのFunction Callingと構造化出力を、入出力契約、プロンプト設計、評価、誤り分析、改良の観点から解説します。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"LLMのFunction Callingと構造化出力は、外部システム連携やデータ抽出に不可欠。プロンプト設計、評価、失敗モード分析、改良までを網羅的に解説しています。
#LLM #プロンプトエンジニアリング","hashtags":["#LLM","#FunctionCalling","#StructuredOutput"]},
"link_hints": ["https://ai.google.dev/docs/function_calling", "https://platform.openai.com/docs/guides/function-calling"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">LLMにおけるFunction Callingと構造化出力の設計と評価</h1>
<p>LLM(大規模言語モデル)の進化により、自然言語処理の能力は飛躍的に向上しました。特にFunction Callingと構造化出力は、LLMを外部システムと連携させたり、複雑な情報を正確に抽出したりするための重要な技術です。本記事では、これらの機能の設計、評価、および改良プロセスについて詳しく解説します。</p>
<h2 class="wp-block-heading">ユースケース定義</h2>
<p>LLMのFunction Callingと構造化出力は、以下のような幅広いユースケースで活用されます。</p>
<ol class="wp-block-list">
<li><p><strong>外部API連携</strong>: ユーザーの自然言語による指示に基づき、天気予報API、データベース検索API、EコマースAPIなどの外部サービスを呼び出し、情報を取得または操作する。</p></li>
<li><p><strong>データ抽出と加工</strong>: 自由形式のテキスト(例: 顧客レビュー、契約書、医療記録)から、特定のエンティティ(例: 製品名、価格、日付、症状)をJSONなどの構造化された形式で抽出し、後続のデータ処理パイプラインに渡す。</p></li>
<li><p><strong>タスク自動化</strong>: ユーザーの発話から意図と引数を抽出し、システム内の特定の操作(例: カレンダーイベントの作成、メール送信、リマインダー設定)をトリガーする。</p></li>
</ol>
<h2 class="wp-block-heading">入出力契約と制約付き仕様化</h2>
<p>LLMのFunction Callingおよび構造化出力を利用する際、明確な入出力契約を定義し、期待される動作を制約付きで仕様化することが重要です。</p>
<h3 class="wp-block-heading">入力契約</h3>
<ul class="wp-block-list">
<li><p><strong>ユーザープロンプト</strong>: 自然言語による指示。例:「今日の東京の天気予報を教えて。」</p></li>
<li><p><strong>ツール定義 (Function Calling)</strong>: 利用可能な外部関数をJSON Schema形式で記述。関数名、説明、引数の型と説明を含む。</p></li>
<li><p><strong>出力スキーマ (構造化出力)</strong>: 期待されるJSON出力の形式をJSON Schemaで記述。</p></li>
</ul>
<h3 class="wp-block-heading">出力契約</h3>
<ul class="wp-block-list">
<li><p><strong>Function Calling</strong>: モデルは、ユーザープロンプトに基づいて呼び出すべき関数名と、その関数に渡す引数をJSONオブジェクトとして出力します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"function_call": {
"name": "get_weather_forecast",
"args": {
"location": "東京",
"date": "今日"
}
}
}
</pre>
</div></li>
<li><p><strong>構造化出力</strong>: モデルは、指定されたJSON Schemaに厳密に準拠したJSONオブジェクトを出力します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"product_name": "スマートウォッチX",
"price": 299.99,
"currency": "USD",
"features": ["心拍数モニター", "GPS"]
}
</pre>
</div></li>
</ul>
<h3 class="wp-block-heading">失敗時の挙動</h3>
<ul class="wp-block-list">
<li><p><strong>Function Calling</strong>: モデルが適切な関数を特定できない場合、または必要な引数を抽出できない場合、通常のテキスト応答を返すか、エラーを示す特定のフォーマットで応答する。実装によっては、エラーメッセージを含むテキストを返すモデルもあります。</p></li>
<li><p><strong>構造化出力</strong>: モデルが指定されたJSON Schemaに準拠できない場合、パースエラーが発生するか、不完全・不正なJSONが出力される。この場合、アプリケーション側でバリデーションエラーを検出し、リトライまたはフォールバック処理を行う。</p></li>
</ul>
<h3 class="wp-block-heading">禁止事項</h3>
<ul class="wp-block-list">
<li><p><strong>不適切なAPI呼び出し</strong>: ユーザーの意図に反する、またはセキュリティリスクを伴う外部APIの呼び出し。</p></li>
<li><p><strong>スキーマ外のデータ生成</strong>: 構造化出力において、JSON Schemaで定義されていないフィールドの追加や、型違反のデータ出力。</p></li>
<li><p><strong>幻覚による引数生成</strong>: 存在しない引数値や、誤ったフォーマットの引数値を生成する。</p></li>
</ul>
<h2 class="wp-block-heading">プロンプト設計</h2>
<p>Function Callingと構造化出力を成功させるためには、プロンプト設計が鍵となります。ここでは、3種類のプロンプト案を提示します。</p>
<h3 class="wp-block-heading">ゼロショットプロンプト</h3>
<p>モデルに特別な例を与えず、タスク指示とツール定義/スキーマ情報のみで実行させます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
</pre>
</div>
<p>ユーザーの質問:
今日の東京の天気予報を教えてください。</p>
<pre data-enlighter-language="generic">
### 少数例プロンプト (Few-shot Prompt)
いくつかの入出力例を示すことで、モデルの挙動を誘導します。
```text
あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
</pre>
<p>例1:
ユーザーの質問:
今日のニューヨークの天気は?
モデルの出力:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"function_call": {
"name": "get_current_weather",
"args": {
"location": "ニューヨーク"
}
}
}
</pre>
</div>
<p>例2:
ユーザーの質問:
モデルの出力:
こんにちは!何かお手伝いできることはありますか?</p>
<p>ユーザーの質問:
今日の東京の天気予報を教えてください。</p>
<pre data-enlighter-language="generic">
### Chain-of-Thought(CoT)制約型プロンプト
モデルに思考プロセスを明示させ、出力を特定の形式に制約します。
```text
あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
出力形式は必ずJSONです。Function Callingを行う場合は {"tool_call": {...}}、テキスト応答の場合は {"text_response": "..."} としてください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
</pre>
<p>思考プロセス:</p>
<ol class="wp-block-list">
<li><p>ユーザーの質問「今日の東京の天気予報を教えてください。」を分析します。</p></li>
<li><p>質問内容は「天気予報」に関するものであり、利用可能なツール <code>get_current_weather</code> と関連性があります。</p></li>
<li><p><code>get_current_weather</code> ツールに必要な引数は <code>location</code> です。質問から「東京」が抽出できます。</p></li>
<li><p>ツールを呼び出すべきだと判断し、適切なJSON形式で出力を生成します。</p></li>
</ol>
<p>ユーザーの質問:
今日の東京の天気予報を教えてください。</p>
<pre data-enlighter-language="generic">
## 評価シナリオと自動評価
モデルのFunction Callingおよび構造化出力の性能を評価するためには、多様なシナリオに基づいたテストと自動評価が必要です。
### 評価シナリオ
1. **正例**:
* 明確な指示: 「明日の大阪の最高気温を教えて。」
* 全引数指定: 「2024年7月15日(JST)のパリの天気は?」
2. **難例**:
* 引数不足: 「天気予報をお願い。」(都市名が不足)
* 曖昧な指示: 「何か旅行の計画を立てたい。」(複数ツールが候補となる可能性)
* 複数関数が選択肢: 「ホテルを予約して、その後観光地を検索して。」(Tool Chainingの必要性)
3. **コーナーケース**:
* 無関係な指示: 「今日のランチは何がいいかな?」
* セキュリティリスクのある指示: 「システムの設定を変更して。」
* 無効な引数: 「存在しない都市 'Hogehoge' の天気予報を。」
### 自動評価の擬似コード
評価は、出力の正確性、形式の遵守、および意図の理解度に基づいて行われます。
```python
import json
from jsonschema import validate, ValidationError
import re
def evaluate_function_calling_output(output_json: str, expected_call: dict, tool_schema: dict) -> dict:
"""
Function Callingの出力を評価する。
:param output_json: LLMからの出力JSON文字列
:param expected_call: 期待される関数呼び出し {"name": "func_name", "args": {...}}
:param tool_schema: ツール定義のJSON Schema
:return: 評価結果 (dict)
"""
results = {
"is_valid_json": False,
"is_schema_compliant": False,
"function_name_match": False,
"args_match": False,
"score": 0.0
}
# 1. JSON形式の検証
try:
parsed_output = json.loads(output_json)
results["is_valid_json"] = True
except json.JSONDecodeError:
return results # JSONが無効ならここで終了
# 2. JSONスキーマの検証(Function Callingの出力構造全体)
# 通常、ツール呼び出しは特定の構造(例: {"function_call": {...}})を持つため、それに対するスキーマを定義
# ここでは簡略化のため、tool_schemaが関数の引数部分のみを指すと仮定し、
# 実際のツール定義スキーマを別途用意する必要がある。
# 例: Function Callingのトップレベルスキーマ
function_call_schema = {
"type": "object",
"properties": {
"function_call": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": tool_schema["function"]["parameters"] # 引数スキーマ
},
"required": ["name", "args"]
}
},
"required": ["function_call"]
}
try:
validate(instance=parsed_output, schema=function_call_schema)
results["is_schema_compliant"] = True
except ValidationError:
pass # スキーマ不適合
# 3. 関数名の一致
if "function_call" in parsed_output and "name" in parsed_output["function_call"]:
if parsed_output["function_call"]["name"] == expected_call["name"]:
results["function_name_match"] = True
results["score"] += 0.5
# 4. 引数の一致(順序不問、キーと値の一致)
if results["function_name_match"] and "args" in parsed_output["function_call"]:
actual_args = parsed_output["function_call"]["args"]
expected_args = expected_call["args"]
if actual_args == expected_args: # 辞書比較
results["args_match"] = True
results["score"] += 0.5
# スコアリングの調整
if not results["is_valid_json"] or not results["is_schema_compliant"]:
results["score"] = 0.0 # JSON形式またはスキーマ不適合ならスコア0
return results
def evaluate_structured_output(output_json: str, expected_data: dict, schema: dict) -> dict:
"""
構造化出力の出力を評価する。
:param output_json: LLMからの出力JSON文字列
:param expected_data: 期待される構造化データ (dict)
:param schema: 期待される出力のJSON Schema
:return: 評価結果 (dict)
"""
results = {
"is_valid_json": False,
"is_schema_compliant": False,
"data_match": False,
"score": 0.0
}
# 1. JSON形式の検証
try:
parsed_output = json.loads(output_json)
results["is_valid_json"] = True
except json.JSONDecodeError:
return results
# 2. JSONスキーマの検証
try:
validate(instance=parsed_output, schema=schema)
results["is_schema_compliant"] = True
except ValidationError:
pass
# 3. データの正確性検証(期待値との比較)
if results["is_valid_json"] and results["is_schema_compliant"]:
# ここでは単純な辞書比較を行うが、実運用ではキーごとに比較ロジックを調整する
if parsed_output == expected_data:
results["data_match"] = True
results["score"] = 1.0 # 完全に一致すれば1.0
else:
# 部分一致や特定のフィールドの重要度に応じてスコアを調整可能
# 例: 必須フィールドが一致すれば0.5、全フィールド一致で1.0
# 今回は単純化し、完全一致のみ1.0とする
pass
return results
# 例: JSON Schema for structured output
product_schema = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"price": {"type": "number"},
"currency": {"type": "string", "enum": ["USD", "JPY", "EUR"]},
"features": {"type": "array", "items": {"type": "string"}}
},
"required": ["product_name", "price", "currency"]
}
# LLM出力例 (成功)
llm_output_success = '{"product_name": "スマートウォッチX", "price": 299.99, "currency": "USD", "features": ["心拍数モニター", "GPS"]}'
expected_product_data = {"product_name": "スマートウォッチX", "price": 299.99, "currency": "USD", "features": ["心拍数モニター", "GPS"]}
# 評価実行
# print(evaluate_structured_output(llm_output_success, expected_product_data, product_schema))
# {"is_valid_json": true, "is_schema_compliant": true, "data_match": true, "score": 1.0}
</pre>
<h2 class="wp-block-heading">誤り分析と抑制手法</h2>
<p>Function Callingと構造化出力には固有の失敗モードがあり、これらを理解し、抑制する手法が必要です。</p>
<h3 class="wp-block-heading">失敗モード</h3>
<ol class="wp-block-list">
<li><p><strong>幻覚(Hallucination)</strong>:</p>
<ul>
<li><p><strong>内容</strong>: 存在しない関数を呼び出そうとする、または引数に誤った値や意味のない値を生成する。</p></li>
<li><p><strong>例</strong>: <code>get_weather_forecast</code> ツールしかないのに <code>book_flight</code> を呼び出そうとする。</p></li>
</ul></li>
<li><p><strong>様式崩れ(Format Deviation)</strong>:</p>
<ul>
<li><p><strong>内容</strong>: JSON Schemaで定義されたフォーマット(型、必須フィールド、列挙型など)に準拠しない出力を生成する。</p></li>
<li><p><strong>例</strong>: <code>{ "product_name": "...", "price": "299.99ドル" }</code> (<code>price</code>が文字列型になっている)</p></li>
</ul></li>
<li><p><strong>脱線(Off-topic Generation)</strong>:</p>
<ul>
<li><p><strong>内容</strong>: ユーザーの意図やタスクから逸脱し、無関係なテキストを生成したり、不要な関数を呼び出したりする。</p></li>
<li><p><strong>例</strong>: 天気予報の質問に対して、レシピの関数を呼び出そうとする。</p></li>
</ul></li>
<li><p><strong>禁止事項違反</strong>:</p>
<ul>
<li><p><strong>内容</strong>: セキュリティポリシーや運用ルールで禁じられているアクションを提案または実行しようとする。</p></li>
<li><p><strong>例</strong>: ユーザーの個人情報を抽出して外部に送信する関数を呼び出す。</p></li>
</ul></li>
</ol>
<h3 class="wp-block-heading">抑制手法</h3>
<ol class="wp-block-list">
<li><p><strong>厳密なSystem Instruction</strong>:</p>
<ul>
<li><p>モデルの役割、遵守すべきルール、出力形式を明確かつ具体的に指示します。特に、ツール呼び出しをしない場合の挙動を定義することが重要です。</p></li>
<li><p>例:「あなたはユーザーの質問にのみ答え、提供されたツールのみを使用してください。ツールが適切でない場合は、その旨をユーザーに伝えてください。」</p></li>
</ul></li>
<li><p><strong>詳細なJSON Schemaとツール定義</strong>:</p>
<ul>
<li><p>引数や出力フィールドの型、<code>enum</code>、<code>pattern</code>、<code>minimum</code>/<code>maximum</code>などの制約を最大限に活用し、モデルが生成できる値の範囲を制限します。</p></li>
<li><p>各ツールの<code>description</code>を明確にし、モデルがそのツールの用途を正確に理解できるようにします[1]。</p></li>
</ul></li>
<li><p><strong>応答のパースとバリデーション</strong>:</p>
<ul>
<li><p>LLMからの出力を受け取った後、アプリケーション側でJSONパースを行い、JSON Schemaに照らしてバリデーションを厳格に実施します。</p></li>
<li><p>不正な出力はエラーとして扱い、再試行やフォールバック処理に移行します。</p></li>
</ul></li>
<li><p><strong>リトライ戦略とフォールバック</strong>:</p>
<ul>
<li><p>バリデーションエラーが発生した場合、単に失敗とするのではなく、モデルにフィードバックを加えて再試行させる(例:「出力されたJSONがスキーマに準拠していません。再度正しい形式で生成してください。」)。</p></li>
<li><p>複数回の再試行後も成功しない場合は、人間による介入を促す、デフォルトの応答を返す、あるいは別のLLMモデルに切り替えるなどのフォールバックメカニズムを用意します。</p></li>
</ul></li>
<li><p><strong>少数例の活用</strong>:</p>
<ul>
<li>成功例だけでなく、意図しない出力を避けるための「これは誤りである」というネガティブな例や、境界条件の例を少数例として提供することも有効です。</li>
</ul></li>
</ol>
<h2 class="wp-block-heading">プロンプト→モデル→評価→改良のループ</h2>
<p>Function Callingと構造化出力の品質を継続的に向上させるためには、以下のサイクルを回すことが不可欠です。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["プロンプト設計"] --> B("LLM実行");
B --> C{"出力結果"};
C -- 正しい --> D["評価"];
C -- 間違っている --> D;
D -- 成功 --> E["運用/デプロイ"];
D -- 失敗 --> F["誤り分析"];
F --> A;
E --> G["監視/フィードバック"];
G --> A;
</pre></div>
<p><em>図1: プロンプト設計から運用の継続的改善ループ</em></p>
<p>このループにおいて、プロンプト設計はLLMの振る舞いを定義し、LLM実行でその定義に基づいた出力が生成されます。評価ステップでは、自動評価スクリプトや手動レビューを用いて出力の品質が測定され、成功・失敗が判定されます。失敗した場合は誤り分析を通じて原因を特定し、プロンプトの改善点を見つけ出します。そして、再びプロンプト設計に戻ることで、システムの性能を段階的に向上させていきます。運用後の監視とフィードバックも、新たなユースケースやエッジケースの発見につながり、ループの起点となり得ます。</p>
<h2 class="wp-block-heading">まとめ</h2>
<p>LLMにおけるFunction Callingと構造化出力は、AIシステムが外部環境とインタラクトし、より賢明なタスクを実行するための強力な機能です。本記事では、ユースケース定義から始まり、入出力契約の確立、ゼロショット、少数例、Chain-of-Thought制約型といったプロンプト設計の手法、さらに自動評価のシナリオと擬似コードを紹介しました。また、幻覚や様式崩れといった失敗モードを分析し、厳密なSystem Instructionやバリデーション、リトライ戦略などの抑制手法を提示しました。これらの設計・評価・改良のプロセスを継続的に実施することで、信頼性と堅牢性の高いLLMアプリケーションを構築できます。</p>
<hr/>
<p>[1] OpenAI Blog. “Function calling and other API updates.” 2023年6月14日(JST)更新. <a href="https://openai.com/blog/function-calling-and-other-api-updates">https://openai.com/blog/function-calling-and-other-api-updates</a>
[2] Google AI for Developers. “Gemini API Function calling.” 2024年5月16日(JST)更新. <a href="https://ai.google.dev/docs/function_calling">https://ai.google.dev/docs/function_calling</a>
[3] JSON Schema. “JSON Schema Core.” 2020年12月15日(JST)公開. <a href="https://json-schema.org/draft/2020-12/json-schema-validation.html">https://json-schema.org/draft/2020-12/json-schema-validation.html</a></p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
LLMにおけるFunction Callingと構造化出力の設計と評価
LLM(大規模言語モデル)の進化により、自然言語処理の能力は飛躍的に向上しました。特にFunction Callingと構造化出力は、LLMを外部システムと連携させたり、複雑な情報を正確に抽出したりするための重要な技術です。本記事では、これらの機能の設計、評価、および改良プロセスについて詳しく解説します。
ユースケース定義
LLMのFunction Callingと構造化出力は、以下のような幅広いユースケースで活用されます。
外部API連携: ユーザーの自然言語による指示に基づき、天気予報API、データベース検索API、EコマースAPIなどの外部サービスを呼び出し、情報を取得または操作する。
データ抽出と加工: 自由形式のテキスト(例: 顧客レビュー、契約書、医療記録)から、特定のエンティティ(例: 製品名、価格、日付、症状)をJSONなどの構造化された形式で抽出し、後続のデータ処理パイプラインに渡す。
タスク自動化: ユーザーの発話から意図と引数を抽出し、システム内の特定の操作(例: カレンダーイベントの作成、メール送信、リマインダー設定)をトリガーする。
入出力契約と制約付き仕様化
LLMのFunction Callingおよび構造化出力を利用する際、明確な入出力契約を定義し、期待される動作を制約付きで仕様化することが重要です。
入力契約
ユーザープロンプト: 自然言語による指示。例:「今日の東京の天気予報を教えて。」
ツール定義 (Function Calling): 利用可能な外部関数をJSON Schema形式で記述。関数名、説明、引数の型と説明を含む。
出力スキーマ (構造化出力): 期待されるJSON出力の形式をJSON Schemaで記述。
出力契約
失敗時の挙動
Function Calling: モデルが適切な関数を特定できない場合、または必要な引数を抽出できない場合、通常のテキスト応答を返すか、エラーを示す特定のフォーマットで応答する。実装によっては、エラーメッセージを含むテキストを返すモデルもあります。
構造化出力: モデルが指定されたJSON Schemaに準拠できない場合、パースエラーが発生するか、不完全・不正なJSONが出力される。この場合、アプリケーション側でバリデーションエラーを検出し、リトライまたはフォールバック処理を行う。
禁止事項
不適切なAPI呼び出し: ユーザーの意図に反する、またはセキュリティリスクを伴う外部APIの呼び出し。
スキーマ外のデータ生成: 構造化出力において、JSON Schemaで定義されていないフィールドの追加や、型違反のデータ出力。
幻覚による引数生成: 存在しない引数値や、誤ったフォーマットの引数値を生成する。
プロンプト設計
Function Callingと構造化出力を成功させるためには、プロンプト設計が鍵となります。ここでは、3種類のプロンプト案を提示します。
ゼロショットプロンプト
モデルに特別な例を与えず、タスク指示とツール定義/スキーマ情報のみで実行させます。
あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
ユーザーの質問:
今日の東京の天気予報を教えてください。
### 少数例プロンプト (Few-shot Prompt)
いくつかの入出力例を示すことで、モデルの挙動を誘導します。
```text
あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
例1:
ユーザーの質問:
今日のニューヨークの天気は?
モデルの出力:
{
"function_call": {
"name": "get_current_weather",
"args": {
"location": "ニューヨーク"
}
}
}
例2:
ユーザーの質問:
モデルの出力:
こんにちは!何かお手伝いできることはありますか?
ユーザーの質問:
今日の東京の天気予報を教えてください。
### Chain-of-Thought(CoT)制約型プロンプト
モデルに思考プロセスを明示させ、出力を特定の形式に制約します。
```text
あなたはユーザーの質問に答えるアシスタントです。
以下のツールが利用可能です。ユーザーの指示に最も合致するツールを選択し、必要な引数をJSON形式で出力してください。
ツールは利用できない場合、通常のテキストで応答してください。
出力形式は必ずJSONです。Function Callingを行う場合は {"tool_call": {...}}、テキスト応答の場合は {"text_response": "..."} としてください。
ツール定義:
```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "指定された都市の現在の天気を取得する",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名"
}
},
"required": ["location"]
}
}
}
思考プロセス:
ユーザーの質問「今日の東京の天気予報を教えてください。」を分析します。
質問内容は「天気予報」に関するものであり、利用可能なツール get_current_weather と関連性があります。
get_current_weather ツールに必要な引数は location です。質問から「東京」が抽出できます。
ツールを呼び出すべきだと判断し、適切なJSON形式で出力を生成します。
ユーザーの質問:
今日の東京の天気予報を教えてください。
## 評価シナリオと自動評価
モデルのFunction Callingおよび構造化出力の性能を評価するためには、多様なシナリオに基づいたテストと自動評価が必要です。
### 評価シナリオ
1. **正例**:
* 明確な指示: 「明日の大阪の最高気温を教えて。」
* 全引数指定: 「2024年7月15日(JST)のパリの天気は?」
2. **難例**:
* 引数不足: 「天気予報をお願い。」(都市名が不足)
* 曖昧な指示: 「何か旅行の計画を立てたい。」(複数ツールが候補となる可能性)
* 複数関数が選択肢: 「ホテルを予約して、その後観光地を検索して。」(Tool Chainingの必要性)
3. **コーナーケース**:
* 無関係な指示: 「今日のランチは何がいいかな?」
* セキュリティリスクのある指示: 「システムの設定を変更して。」
* 無効な引数: 「存在しない都市 'Hogehoge' の天気予報を。」
### 自動評価の擬似コード
評価は、出力の正確性、形式の遵守、および意図の理解度に基づいて行われます。
```python
import json
from jsonschema import validate, ValidationError
import re
def evaluate_function_calling_output(output_json: str, expected_call: dict, tool_schema: dict) -> dict:
"""
Function Callingの出力を評価する。
:param output_json: LLMからの出力JSON文字列
:param expected_call: 期待される関数呼び出し {"name": "func_name", "args": {...}}
:param tool_schema: ツール定義のJSON Schema
:return: 評価結果 (dict)
"""
results = {
"is_valid_json": False,
"is_schema_compliant": False,
"function_name_match": False,
"args_match": False,
"score": 0.0
}
# 1. JSON形式の検証
try:
parsed_output = json.loads(output_json)
results["is_valid_json"] = True
except json.JSONDecodeError:
return results # JSONが無効ならここで終了
# 2. JSONスキーマの検証(Function Callingの出力構造全体)
# 通常、ツール呼び出しは特定の構造(例: {"function_call": {...}})を持つため、それに対するスキーマを定義
# ここでは簡略化のため、tool_schemaが関数の引数部分のみを指すと仮定し、
# 実際のツール定義スキーマを別途用意する必要がある。
# 例: Function Callingのトップレベルスキーマ
function_call_schema = {
"type": "object",
"properties": {
"function_call": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": tool_schema["function"]["parameters"] # 引数スキーマ
},
"required": ["name", "args"]
}
},
"required": ["function_call"]
}
try:
validate(instance=parsed_output, schema=function_call_schema)
results["is_schema_compliant"] = True
except ValidationError:
pass # スキーマ不適合
# 3. 関数名の一致
if "function_call" in parsed_output and "name" in parsed_output["function_call"]:
if parsed_output["function_call"]["name"] == expected_call["name"]:
results["function_name_match"] = True
results["score"] += 0.5
# 4. 引数の一致(順序不問、キーと値の一致)
if results["function_name_match"] and "args" in parsed_output["function_call"]:
actual_args = parsed_output["function_call"]["args"]
expected_args = expected_call["args"]
if actual_args == expected_args: # 辞書比較
results["args_match"] = True
results["score"] += 0.5
# スコアリングの調整
if not results["is_valid_json"] or not results["is_schema_compliant"]:
results["score"] = 0.0 # JSON形式またはスキーマ不適合ならスコア0
return results
def evaluate_structured_output(output_json: str, expected_data: dict, schema: dict) -> dict:
"""
構造化出力の出力を評価する。
:param output_json: LLMからの出力JSON文字列
:param expected_data: 期待される構造化データ (dict)
:param schema: 期待される出力のJSON Schema
:return: 評価結果 (dict)
"""
results = {
"is_valid_json": False,
"is_schema_compliant": False,
"data_match": False,
"score": 0.0
}
# 1. JSON形式の検証
try:
parsed_output = json.loads(output_json)
results["is_valid_json"] = True
except json.JSONDecodeError:
return results
# 2. JSONスキーマの検証
try:
validate(instance=parsed_output, schema=schema)
results["is_schema_compliant"] = True
except ValidationError:
pass
# 3. データの正確性検証(期待値との比較)
if results["is_valid_json"] and results["is_schema_compliant"]:
# ここでは単純な辞書比較を行うが、実運用ではキーごとに比較ロジックを調整する
if parsed_output == expected_data:
results["data_match"] = True
results["score"] = 1.0 # 完全に一致すれば1.0
else:
# 部分一致や特定のフィールドの重要度に応じてスコアを調整可能
# 例: 必須フィールドが一致すれば0.5、全フィールド一致で1.0
# 今回は単純化し、完全一致のみ1.0とする
pass
return results
# 例: JSON Schema for structured output
product_schema = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"price": {"type": "number"},
"currency": {"type": "string", "enum": ["USD", "JPY", "EUR"]},
"features": {"type": "array", "items": {"type": "string"}}
},
"required": ["product_name", "price", "currency"]
}
# LLM出力例 (成功)
llm_output_success = '{"product_name": "スマートウォッチX", "price": 299.99, "currency": "USD", "features": ["心拍数モニター", "GPS"]}'
expected_product_data = {"product_name": "スマートウォッチX", "price": 299.99, "currency": "USD", "features": ["心拍数モニター", "GPS"]}
# 評価実行
# print(evaluate_structured_output(llm_output_success, expected_product_data, product_schema))
# {"is_valid_json": true, "is_schema_compliant": true, "data_match": true, "score": 1.0}
誤り分析と抑制手法
Function Callingと構造化出力には固有の失敗モードがあり、これらを理解し、抑制する手法が必要です。
失敗モード
幻覚(Hallucination):
様式崩れ(Format Deviation):
内容: JSON Schemaで定義されたフォーマット(型、必須フィールド、列挙型など)に準拠しない出力を生成する。
例: { "product_name": "...", "price": "299.99ドル" } (priceが文字列型になっている)
脱線(Off-topic Generation):
禁止事項違反:
抑制手法
厳密なSystem Instruction:
詳細なJSON Schemaとツール定義:
応答のパースとバリデーション:
リトライ戦略とフォールバック:
少数例の活用:
- 成功例だけでなく、意図しない出力を避けるための「これは誤りである」というネガティブな例や、境界条件の例を少数例として提供することも有効です。
プロンプト→モデル→評価→改良のループ
Function Callingと構造化出力の品質を継続的に向上させるためには、以下のサイクルを回すことが不可欠です。
graph TD
A["プロンプト設計"] --> B("LLM実行");
B --> C{"出力結果"};
C -- 正しい --> D["評価"];
C -- 間違っている --> D;
D -- 成功 --> E["運用/デプロイ"];
D -- 失敗 --> F["誤り分析"];
F --> A;
E --> G["監視/フィードバック"];
G --> A;
図1: プロンプト設計から運用の継続的改善ループ
このループにおいて、プロンプト設計はLLMの振る舞いを定義し、LLM実行でその定義に基づいた出力が生成されます。評価ステップでは、自動評価スクリプトや手動レビューを用いて出力の品質が測定され、成功・失敗が判定されます。失敗した場合は誤り分析を通じて原因を特定し、プロンプトの改善点を見つけ出します。そして、再びプロンプト設計に戻ることで、システムの性能を段階的に向上させていきます。運用後の監視とフィードバックも、新たなユースケースやエッジケースの発見につながり、ループの起点となり得ます。
まとめ
LLMにおけるFunction Callingと構造化出力は、AIシステムが外部環境とインタラクトし、より賢明なタスクを実行するための強力な機能です。本記事では、ユースケース定義から始まり、入出力契約の確立、ゼロショット、少数例、Chain-of-Thought制約型といったプロンプト設計の手法、さらに自動評価のシナリオと擬似コードを紹介しました。また、幻覚や様式崩れといった失敗モードを分析し、厳密なSystem Instructionやバリデーション、リトライ戦略などの抑制手法を提示しました。これらの設計・評価・改良のプロセスを継続的に実施することで、信頼性と堅牢性の高いLLMアプリケーションを構築できます。
[1] OpenAI Blog. “Function calling and other API updates.” 2023年6月14日(JST)更新. https://openai.com/blog/function-calling-and-other-api-updates
[2] Google AI for Developers. “Gemini API Function calling.” 2024年5月16日(JST)更新. https://ai.google.dev/docs/function_calling
[3] JSON Schema. “JSON Schema Core.” 2020年12月15日(JST)公開. https://json-schema.org/draft/2020-12/json-schema-validation.html
コメント