<p><!--META
{
"title": "LLM Function Calling設計と契約",
"primary_category": "LLM",
"secondary_categories": ["プロンプト工学", "システム設計"],
"tags": ["Function Calling", "LLM", "API連携", "プロンプト設計", "契約設計", "ツール利用"],
"summary": "LLM Function Callingの堅牢な設計と運用に必要な契約定義、プロンプト設計、評価、失敗モード分析と抑制戦略を解説。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"LLMのFunction Callingを効果的に設計・運用するためのガイド。堅牢な入出力契約、プロンプト戦略、評価手法、失敗抑制を詳解。システム連携の品質向上に!
#LLM #FunctionCalling #プロンプト工学","hashtags":["#LLM","#FunctionCalling","#プロンプト工学"]},
"link_hints": []
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">LLM Function Calling設計と契約</h1>
<h2 class="wp-block-heading">ユースケース定義</h2>
<p>LLM Function Callingは、大規模言語モデル(LLM)が外部ツールやAPIと連携し、自然言語の指示に基づいて特定のアクションを実行する機能です。本記事では、ユーザーが特定の都市の現在の天気を尋ねるシナリオを対象とします。LLMはユーザーの質問を解釈し、指定された都市の天気情報を取得するための外部API(例: <code>call_weather_api</code>)を適切に呼び出すことを目指します。</p>
<h2 class="wp-block-heading">制約付き仕様化(入出力契約)</h2>
<p>Function Callingを堅牢に機能させるためには、入出力の厳密な契約定義が不可欠です。</p>
<h3 class="wp-block-heading">Function Callingの定義</h3>
<p>利用可能なツールは以下の関数とします。</p>
<ul class="wp-block-list">
<li><p><strong>関数名</strong>: <code>call_weather_api</code></p></li>
<li><p><strong>説明</strong>: 指定された都市の現在の天気情報を取得します。</p></li>
<li><p><strong>パラメータ</strong>:</p>
<ul>
<li><code>city</code>: <code>str</code>型。天気情報を取得したい都市の名前。非空文字列であり、有効な都市名である必要があります。</li>
</ul></li>
</ul>
<h3 class="wp-block-heading">入力契約</h3>
<ol class="wp-block-list">
<li><p><strong>ユーザープロンプト</strong>: 自然言語のテキスト(例: 「東京の天気教えて」)。</p></li>
<li><p><strong>LLMへの入力</strong>: システム指示、ツール定義、ユーザープロンプト。</p></li>
<li><p><strong>LLMからのFunction Calling出力</strong>: LLMはツールを呼び出す場合、以下のJSON形式で出力します。</p>
<ul>
<li><p><code>{"function_name": "call_weather_api", "parameters": {"city": "Tokyo"}}</code></p></li>
<li><p><code>city</code>パラメータは、<code>call_weather_api</code>で定義された<code>str</code>型制約に準拠すること。</p></li>
</ul></li>
</ol>
<h3 class="wp-block-heading">出力契約</h3>
<ol class="wp-block-list">
<li><p><strong>外部API実行結果</strong>: <code>call_weather_api</code>から返されるJSON形式のデータ(例: <code>{"temperature": 25, "condition": "Sunny"}</code>)。</p></li>
<li><p><strong>LLMの最終応答</strong>: ユーザーに対して自然言語で状況を説明するテキスト(例: 「東京の現在の天気は晴れで、気温は25度です。」)。</p></li>
</ol>
<h3 class="wp-block-heading">失敗時の挙動</h3>
<ul class="wp-block-list">
<li><p><strong>API呼び出しパラメータ不正</strong>:</p>
<ul>
<li><p>LLMが生成したパラメータがツールのスキーマに適合しない場合(例: <code>city</code>が数値)、LLMは修正を試みるか、ユーザーに明確なエラーメッセージを返す。</p></li>
<li><p>外部システムがパラメータの有効性を検証し、エラーをLLMにフィードバック。</p></li>
</ul></li>
<li><p><strong>API実行エラー</strong>:</p>
<ul>
<li>外部APIがエラー(例: 存在しない都市名、ネットワーク障害)を返した場合、LLMはエラー内容をユーザーに伝え、代替案(例: 「申し訳ありませんが、その都市の天気情報は取得できませんでした」)を提示する。</li>
</ul></li>
<li><p><strong>意図しないFunction Calling</strong>:</p>
<ul>
<li>ユーザーの意図と異なるツール呼び出しを生成した場合、LLMは直接呼び出さずに、ユーザーにその呼び出しの意図を確認するプロンプトを返す。</li>
</ul></li>
<li><p><strong>ツール呼び出しの失敗</strong>: JSONパースエラーなど、システム的な失敗の場合は、システム側でエラーを検知し、LLMに再試行を促すか、エラーログを記録する。</p></li>
</ul>
<h3 class="wp-block-heading">禁止事項</h3>
<ul class="wp-block-list">
<li><p><strong>ユーザーの許可なき機密情報の渡航</strong>: ユーザーが明示的に許可しない限り、個人情報や機密性の高い情報を外部APIに渡すことを禁止します。</p></li>
<li><p><strong>無限ループの発生</strong>: ツール呼び出しが連続し、システムに負荷をかけるような挙動を禁止します。システム側で呼び出し回数や深さの制限を設けます。</p></li>
<li><p><strong>機能の誤用</strong>: 定義された目的外でのツールの使用、またはユーザーが明示的に拒否した機能の呼び出しを禁止します。</p></li>
</ul>
<h2 class="wp-block-heading">プロンプト設計</h2>
<p>以下に、天気APIを呼び出すためのプロンプト案を3種類提示します。</p>
<h3 class="wp-block-heading">1. ゼロショットプロンプト</h3>
<div class="codehilite">
<pre data-enlighter-language="generic">あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
ユーザー: 東京の天気は?
アシスタント:
</pre>
</div>
<h3 class="wp-block-heading">2. 少数例プロンプト(Few-shot Prompt)</h3>
<p>ゼロショットプロンプトに具体的な入出力例を追加し、LLMの挙動を誘導します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
例1:
ユーザー: 今日のニューヨークの天気はどう?
アシスタント: {"function_name": "call_weather_api", "parameters": {"city": "New York"}}
例2:
ユーザー: 株価を教えてください。
アシスタント: 申し訳ありませんが、株価に関する情報を提供するツールは利用できません。
例3:
ユーザー: 大阪の天気は?
アシスタント: {"function_name": "call_weather_api", "parameters": {"city": "大阪"}}
ユーザー: {{ユーザーの質問}}
アシスタント:
</pre>
</div>
<h3 class="wp-block-heading">3. Chain-of-Thought(CoT)制約型プロンプト</h3>
<p>LLMに思考プロセスを明示させ、より信頼性の高いツール呼び出しを生成させます。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、**まず、どのようなツールが必要か、なぜそのツールを使うのかを検討してください。その上で、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。**
ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
思考プロセス:
1. ユーザーの意図を理解する。
2. 意図を満たすために利用可能なツールがあるか確認する。
3. ツールのパラメータを特定する。
4. JSON形式でツール呼び出しを生成する、またはツールが不要な場合は自然言語で応答する。
ユーザー: 東京の天気は?
アシスタント:
思考: ユーザーは東京の天気を尋ねています。これは`call_weather_api`ツールで対応可能です。都市は「東京」です。
{"function_name": "call_weather_api", "parameters": {"city": "東京"}}
ユーザー: {{ユーザーの質問}}
アシスタント:
</pre>
</div>
<h2 class="wp-block-heading">評価シナリオと自動評価</h2>
<p>Function Callingの品質を保証するためには、多様なシナリオでの評価が重要です。</p>
<h3 class="wp-block-heading">評価シナリオ</h3>
<ul class="wp-block-list">
<li><p><strong>正例</strong>:</p>
<ul>
<li><p>「福岡の天気は?」 -> <code>call_weather_api(city="福岡")</code></p></li>
<li><p>「今日のロンドンの天気」 -> <code>call_weather_api(city="London")</code></p></li>
</ul></li>
<li><p><strong>難例(曖昧性・複数要求・関連性なし)</strong>:</p>
<ul>
<li><p>「京都の天気は?」(京都府か京都市か) -> 曖昧な場合は「京都市ですか、それとも京都府ですか?」と質問するか、デフォルト(京都市)で呼び出す。</p></li>
<li><p>「東京と大阪の天気は?」 -> <code>call_weather_api(city="東京")</code>と<code>call_weather_api(city="大阪")</code>を生成するか、複数都市に対応しない旨を伝える。</p></li>
<li><p>「今日のニュースを教えて」 -> ツール呼び出しなし。</p></li>
<li><p>「東今日」 (誤字脱字) -> 正しい都市名に修正して呼び出すか、ユーザーに確認。</p></li>
</ul></li>
<li><p><strong>コーナーケース(不正な入力)</strong>:</p>
<ul>
<li><p>「天気は?」(都市名なし) -> ツール呼び出しなし、または都市名を尋ねる。</p></li>
<li><p>「123」 -> エラー、またはツール呼び出しなし。</p></li>
<li><p>「!@#$」 -> エラー、またはツール呼び出しなし。</p></li>
</ul></li>
</ul>
<h3 class="wp-block-heading">自動評価の擬似コード</h3>
<p>LLMの出力からFunction Callingを抽出し、期待値と比較する擬似コードをPythonで示します。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">import json
import re
def evaluate_function_calling(model_output: str, expected_call: dict, scenario_type: str) -> dict:
"""
LLMのFunction Calling出力を自動評価する関数。
Args:
model_output (str): LLMから生成された出力文字列。
expected_call (dict or None): 期待されるFunction CallingのJSON辞書。
ツール呼び出しが期待されない場合はNone。
scenario_type (str): 評価シナリオのタイプ(例: "正例", "難例", "コーナーケース")。
Returns:
dict: 評価結果(スコア、理由、シナリオタイプ)。
計算量: JSONパースはO(N) (Nは文字列長)、辞書比較はO(K) (Kはキー数)。
メモリ: モデル出力、期待値の辞書サイズに依存。
"""
score = 0.0
reason = []
actual_call = None
# LLM出力からJSON形式のFunction Callingを抽出
try:
match = re.search(r'\{.*\}', model_output.strip(), re.DOTALL)
if match:
json_str = match.group(0)
actual_call = json.loads(json_str)
else:
# JSON形式が見つからない場合、ツール呼び出しなしと判断
actual_call = None
except json.JSONDecodeError:
actual_call = None
reason.append("出力が有効なJSON形式ではありません。")
# 評価ロジック
if expected_call is None: # ツール呼び出しが期待されないシナリオ
if actual_call is None:
score = 1.0
reason.append("期待通りツール呼び出しがありませんでした。")
else:
score = 0.0
reason.append(f"ツール呼び出しが期待されていませんでしたが、'{actual_call.get('function_name')}'が検出されました。")
else: # ツール呼び出しが期待されるシナリオ
if actual_call:
if actual_call.get("function_name") == expected_call.get("function_name"):
reason.append("関数名が一致しました。")
if actual_call.get("parameters") == expected_call.get("parameters"):
score = 1.0
reason.append("パラメータが期待通りでした。")
else:
# パラメータの曖昧性や部分一致を考慮する場合、ここで詳細な比較を行う
score = 0.5 # 部分点
reason.append(f"パラメータが不一致です。期待: {expected_call.get('parameters')}, 実際: {actual_call.get('parameters')}")
else:
score = 0.0
reason.append(f"期待する関数名と異なります。期待: '{expected_call.get('function_name')}', 実際: '{actual_call.get('function_name')}'")
else:
score = 0.0
reason.append("ツール呼び出しが期待されましたが、検出されませんでした。")
return {"score": score, "reason": " ".join(reason), "scenario_type": scenario_type}
# 例:
# print(evaluate_function_calling('{"function_name": "call_weather_api", "parameters": {"city": "福岡"}}', {"function_name": "call_weather_api", "parameters": {"city": "福岡"}}, "正例"))
# # 出力: {'score': 1.0, 'reason': '関数名が一致しました。パラメータが期待通りでした。', 'scenario_type': '正例'}
# print(evaluate_function_calling('申し訳ありませんが、株価に関する情報を提供するツールは利用できません。', None, "難例"))
# # 出力: {'score': 1.0, 'reason': '期待通りツール呼び出しがありませんでした。', 'scenario_type': '難例'}
# print(evaluate_function_calling('{"function_name": "call_news_api", "parameters": {"query": "今日のニュース"}}', {"function_name": "call_weather_api", "parameters": {"city": "東京"}}, "難例"))
# # 出力: {'score': 0.0, 'reason': "期待する関数名と異なります。期待: 'call_weather_api', 実際: 'call_news_api'", 'scenario_type': '難例'}
</pre>
</div>
<h2 class="wp-block-heading">プロンプト設計・評価・改良のループ</h2>
<p>Function Callingの性能を継続的に向上させるためには、プロンプト設計、LLM推論、評価、改良のフィードバックループを確立することが重要です。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph LR
A["プロンプト設計"] -- |新しいプロンプト| --> B("LLM推論")
B -- |Function Calling結果| --> C{"自動評価"}
C -- |評価指標 & 失敗例| --> D["評価レポート"]
D -- |改善点特定| --> E["改善戦略立案"]
E -- |プロンプト修正| --> A
</pre></div>
<h2 class="wp-block-heading">失敗モードと抑制手法</h2>
<p>LLM Function Callingの設計において、様々な失敗モードを想定し、その抑制策を講じることが重要です。本記事執筆時点の2024年7月29日現在、これらの課題は活発に研究・開発されています。</p>
<figure class="wp-block-table"><table>
<thead>
<tr>
<th style="text-align:left;">失敗モード</th>
<th style="text-align:left;">問題点</th>
<th style="text-align:left;">抑制手法</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left;"><strong>幻覚 (Hallucination)</strong></td>
<td style="text-align:left;">存在しない関数名やパラメータを生成する。</td>
<td style="text-align:left;">システム指示でのツール定義の厳格化、出力スキーマの強制、API呼び出し前の厳密な検証。</td>
</tr>
<tr>
<td style="text-align:left;"><strong>様式崩れ (Format Breakage)</strong></td>
<td style="text-align:left;">JSON形式が崩れる、不完全なJSONを生成する。</td>
<td style="text-align:left;">JSONスキーマの提供と強制、出力パース後の厳格なバリデーション、リトライ戦略。</td>
</tr>
<tr>
<td style="text-align:left;"><strong>脱線 (Off-topic Calling)</strong></td>
<td style="text-align:left;">ユーザーの意図と異なるツールを呼び出す。</td>
<td style="text-align:left;">Few-shot例で非関連クエリに対する適切な拒否応答を示す、CoTで思考プロセスを明示。</td>
</tr>
<tr>
<td style="text-align:left;"><strong>禁止事項の逸脱</strong></td>
<td style="text-align:left;">ユーザーの許可なく機密情報を使用、無限ループを引き起こす。</td>
<td style="text-align:left;">システム指示での明確な禁止事項の提示、ユーザーの明示的な確認プロンプト、サンドボックス環境でのAPI実行、Rate Limit。</td>
</tr>
<tr>
<td style="text-align:left;"><strong>パラメータ誤解釈</strong></td>
<td style="text-align:left;">パラメータの値がユーザーの意図と異なる。</td>
<td style="text-align:left;">CoTによる思考プロセスの明示、Few-shot例での多様なパターン、正規表現による入力検証。</td>
</tr>
</tbody>
</table></figure>
<h2 class="wp-block-heading">まとめ</h2>
<p>LLM Function Callingは、LLMが外部ツールと連携することでその能力を飛躍的に拡張する強力な機能です。しかし、その効果的な利用には、明確な入出力契約の定義、ユースケースに合わせたプロンプト設計、そして体系的な評価と継続的な改良プロセスが不可欠です。本記事で示したように、各失敗モードに対する抑制策を講じながら、プロンプトの洗練と自動評価を繰り返すことで、より堅牢で信頼性の高いLLMシステムを構築することができます。</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証) です。
LLM Function Calling設計と契約
ユースケース定義
LLM Function Callingは、大規模言語モデル(LLM)が外部ツールやAPIと連携し、自然言語の指示に基づいて特定のアクションを実行する機能です。本記事では、ユーザーが特定の都市の現在の天気を尋ねるシナリオを対象とします。LLMはユーザーの質問を解釈し、指定された都市の天気情報を取得するための外部API(例: call_weather_api)を適切に呼び出すことを目指します。
制約付き仕様化(入出力契約)
Function Callingを堅牢に機能させるためには、入出力の厳密な契約定義が不可欠です。
Function Callingの定義
利用可能なツールは以下の関数とします。
入力契約
ユーザープロンプト : 自然言語のテキスト(例: 「東京の天気教えて」)。
LLMへの入力 : システム指示、ツール定義、ユーザープロンプト。
LLMからのFunction Calling出力 : LLMはツールを呼び出す場合、以下のJSON形式で出力します。
{"function_name": "call_weather_api", "parameters": {"city": "Tokyo"}}
cityパラメータは、call_weather_apiで定義されたstr型制約に準拠すること。
出力契約
外部API実行結果 : call_weather_apiから返されるJSON形式のデータ(例: {"temperature": 25, "condition": "Sunny"})。
LLMの最終応答 : ユーザーに対して自然言語で状況を説明するテキスト(例: 「東京の現在の天気は晴れで、気温は25度です。」)。
失敗時の挙動
禁止事項
ユーザーの許可なき機密情報の渡航 : ユーザーが明示的に許可しない限り、個人情報や機密性の高い情報を外部APIに渡すことを禁止します。
無限ループの発生 : ツール呼び出しが連続し、システムに負荷をかけるような挙動を禁止します。システム側で呼び出し回数や深さの制限を設けます。
機能の誤用 : 定義された目的外でのツールの使用、またはユーザーが明示的に拒否した機能の呼び出しを禁止します。
プロンプト設計
以下に、天気APIを呼び出すためのプロンプト案を3種類提示します。
1. ゼロショットプロンプト
あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
ユーザー: 東京の天気は?
アシスタント:
2. 少数例プロンプト(Few-shot Prompt)
ゼロショットプロンプトに具体的な入出力例を追加し、LLMの挙動を誘導します。
あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
例1:
ユーザー: 今日のニューヨークの天気はどう?
アシスタント: {"function_name": "call_weather_api", "parameters": {"city": "New York"}}
例2:
ユーザー: 株価を教えてください。
アシスタント: 申し訳ありませんが、株価に関する情報を提供するツールは利用できません。
例3:
ユーザー: 大阪の天気は?
アシスタント: {"function_name": "call_weather_api", "parameters": {"city": "大阪"}}
ユーザー: {{ユーザーの質問}}
アシスタント:
3. Chain-of-Thought(CoT)制約型プロンプト
LLMに思考プロセスを明示させ、より信頼性の高いツール呼び出しを生成させます。
あなたは賢いアシスタントで、ユーザーの質問に答えるためにツールを使用できます。
使用可能なツールは以下の通りです。
ツール定義:
call_weather_api(city: str): 指定された都市の現在の天気を取得します。
ユーザーの要求を分析し、**まず、どのようなツールが必要か、なぜそのツールを使うのかを検討してください。その上で、ツール呼び出しが必要な場合は、JSON形式でツール呼び出しを生成してください。**
ツールが不要な場合は、自然言語で直接応答してください。
JSON形式の例: {"function_name": "ツール名", "parameters": {"パラメータ名": "値"}}
思考プロセス:
1. ユーザーの意図を理解する。
2. 意図を満たすために利用可能なツールがあるか確認する。
3. ツールのパラメータを特定する。
4. JSON形式でツール呼び出しを生成する、またはツールが不要な場合は自然言語で応答する。
ユーザー: 東京の天気は?
アシスタント:
思考: ユーザーは東京の天気を尋ねています。これは`call_weather_api`ツールで対応可能です。都市は「東京」です。
{"function_name": "call_weather_api", "parameters": {"city": "東京"}}
ユーザー: {{ユーザーの質問}}
アシスタント:
評価シナリオと自動評価
Function Callingの品質を保証するためには、多様なシナリオでの評価が重要です。
評価シナリオ
正例 :
難例(曖昧性・複数要求・関連性なし) :
「京都の天気は?」(京都府か京都市か) -> 曖昧な場合は「京都市ですか、それとも京都府ですか?」と質問するか、デフォルト(京都市)で呼び出す。
「東京と大阪の天気は?」 -> call_weather_api(city="東京")とcall_weather_api(city="大阪")を生成するか、複数都市に対応しない旨を伝える。
「今日のニュースを教えて」 -> ツール呼び出しなし。
「東今日」 (誤字脱字) -> 正しい都市名に修正して呼び出すか、ユーザーに確認。
コーナーケース(不正な入力) :
「天気は?」(都市名なし) -> ツール呼び出しなし、または都市名を尋ねる。
「123」 -> エラー、またはツール呼び出しなし。
「!@#$」 -> エラー、またはツール呼び出しなし。
自動評価の擬似コード
LLMの出力からFunction Callingを抽出し、期待値と比較する擬似コードをPythonで示します。
import json
import re
def evaluate_function_calling(model_output: str, expected_call: dict, scenario_type: str) -> dict:
"""
LLMのFunction Calling出力を自動評価する関数。
Args:
model_output (str): LLMから生成された出力文字列。
expected_call (dict or None): 期待されるFunction CallingのJSON辞書。
ツール呼び出しが期待されない場合はNone。
scenario_type (str): 評価シナリオのタイプ(例: "正例", "難例", "コーナーケース")。
Returns:
dict: 評価結果(スコア、理由、シナリオタイプ)。
計算量: JSONパースはO(N) (Nは文字列長)、辞書比較はO(K) (Kはキー数)。
メモリ: モデル出力、期待値の辞書サイズに依存。
"""
score = 0.0
reason = []
actual_call = None
# LLM出力からJSON形式のFunction Callingを抽出
try:
match = re.search(r'\{.*\}', model_output.strip(), re.DOTALL)
if match:
json_str = match.group(0)
actual_call = json.loads(json_str)
else:
# JSON形式が見つからない場合、ツール呼び出しなしと判断
actual_call = None
except json.JSONDecodeError:
actual_call = None
reason.append("出力が有効なJSON形式ではありません。")
# 評価ロジック
if expected_call is None: # ツール呼び出しが期待されないシナリオ
if actual_call is None:
score = 1.0
reason.append("期待通りツール呼び出しがありませんでした。")
else:
score = 0.0
reason.append(f"ツール呼び出しが期待されていませんでしたが、'{actual_call.get('function_name')}'が検出されました。")
else: # ツール呼び出しが期待されるシナリオ
if actual_call:
if actual_call.get("function_name") == expected_call.get("function_name"):
reason.append("関数名が一致しました。")
if actual_call.get("parameters") == expected_call.get("parameters"):
score = 1.0
reason.append("パラメータが期待通りでした。")
else:
# パラメータの曖昧性や部分一致を考慮する場合、ここで詳細な比較を行う
score = 0.5 # 部分点
reason.append(f"パラメータが不一致です。期待: {expected_call.get('parameters')}, 実際: {actual_call.get('parameters')}")
else:
score = 0.0
reason.append(f"期待する関数名と異なります。期待: '{expected_call.get('function_name')}', 実際: '{actual_call.get('function_name')}'")
else:
score = 0.0
reason.append("ツール呼び出しが期待されましたが、検出されませんでした。")
return {"score": score, "reason": " ".join(reason), "scenario_type": scenario_type}
# 例:
# print(evaluate_function_calling('{"function_name": "call_weather_api", "parameters": {"city": "福岡"}}', {"function_name": "call_weather_api", "parameters": {"city": "福岡"}}, "正例"))
# # 出力: {'score': 1.0, 'reason': '関数名が一致しました。パラメータが期待通りでした。', 'scenario_type': '正例'}
# print(evaluate_function_calling('申し訳ありませんが、株価に関する情報を提供するツールは利用できません。', None, "難例"))
# # 出力: {'score': 1.0, 'reason': '期待通りツール呼び出しがありませんでした。', 'scenario_type': '難例'}
# print(evaluate_function_calling('{"function_name": "call_news_api", "parameters": {"query": "今日のニュース"}}', {"function_name": "call_weather_api", "parameters": {"city": "東京"}}, "難例"))
# # 出力: {'score': 0.0, 'reason': "期待する関数名と異なります。期待: 'call_weather_api', 実際: 'call_news_api'", 'scenario_type': '難例'}
プロンプト設計・評価・改良のループ
Function Callingの性能を継続的に向上させるためには、プロンプト設計、LLM推論、評価、改良のフィードバックループを確立することが重要です。
graph LR
A["プロンプト設計"] -- |新しいプロンプト| --> B("LLM推論")
B -- |Function Calling結果| --> C{"自動評価"}
C -- |評価指標 & 失敗例| --> D["評価レポート"]
D -- |改善点特定| --> E["改善戦略立案"]
E -- |プロンプト修正| --> A
失敗モードと抑制手法
LLM Function Callingの設計において、様々な失敗モードを想定し、その抑制策を講じることが重要です。本記事執筆時点の2024年7月29日現在、これらの課題は活発に研究・開発されています。
失敗モード
問題点
抑制手法
幻覚 (Hallucination)
存在しない関数名やパラメータを生成する。
システム指示でのツール定義の厳格化、出力スキーマの強制、API呼び出し前の厳密な検証。
様式崩れ (Format Breakage)
JSON形式が崩れる、不完全なJSONを生成する。
JSONスキーマの提供と強制、出力パース後の厳格なバリデーション、リトライ戦略。
脱線 (Off-topic Calling)
ユーザーの意図と異なるツールを呼び出す。
Few-shot例で非関連クエリに対する適切な拒否応答を示す、CoTで思考プロセスを明示。
禁止事項の逸脱
ユーザーの許可なく機密情報を使用、無限ループを引き起こす。
システム指示での明確な禁止事項の提示、ユーザーの明示的な確認プロンプト、サンドボックス環境でのAPI実行、Rate Limit。
パラメータ誤解釈
パラメータの値がユーザーの意図と異なる。
CoTによる思考プロセスの明示、Few-shot例での多様なパターン、正規表現による入力検証。
まとめ
LLM Function Callingは、LLMが外部ツールと連携することでその能力を飛躍的に拡張する強力な機能です。しかし、その効果的な利用には、明確な入出力契約の定義、ユースケースに合わせたプロンプト設計、そして体系的な評価と継続的な改良プロセスが不可欠です。本記事で示したように、各失敗モードに対する抑制策を講じながら、プロンプトの洗練と自動評価を繰り返すことで、より堅牢で信頼性の高いLLMシステムを構築することができます。
コメント