<p><!--META
{
"title": "LLMのJSON出力制御と評価",
"primary_category": "AI/LLM/プロンプトエンジニアリング",
"secondary_categories": ["プロンプト設計","評価指標"],
"tags": ["LLM", "JSON", "プロンプトエンジニアリング", "構造化出力", "Pydantic"],
"summary": "LLMにJSON形式で出力させるためのプロンプト設計、評価、失敗モードと抑制手法について解説。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"LLMのJSON出力制御と評価に関する実践ガイド。プロンプト設計から自動評価、失敗モード対策まで網羅。AIの信頼性向上に!","hashtags":["#LLM","#プロンプトエンジニアリング","#AI"]},
"link_hints": ["https://platform.openai.com/docs/guides/function-calling", "https://ai.google.dev/docs/function_calling", "https://docs.pydantic.dev/latest/", "https://github.com/outlines-dev/outlines"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">LLMのJSON出力制御と評価</h1>
<p>大規模言語モデル(LLM)からのJSON形式での出力制御は、API連携、データ抽出、自動化ワークフローなど、多岐にわたるアプリケーションで不可欠です。本記事では、LLMに正確で構造化されたJSONを出力させるためのプロンプト設計、評価手法、そして一般的な失敗モードとその抑制戦略について解説します。</p>
<h2 class="wp-block-heading">1. ユースケース定義</h2>
<p>LLMのJSON出力制御は、以下のようなシナリオで特に重要となります。</p>
<ul class="wp-block-list">
<li><p><strong>API呼び出し引数の生成</strong>: ユーザーの自然言語によるリクエストを解析し、外部APIが期待するJSON形式の引数を生成する。</p></li>
<li><p><strong>構造化データ抽出</strong>: 非構造化テキスト(例: 記事、レビュー)から特定のエンティティや情報を抽出し、JSONオブジェクトとして整形する。</p></li>
<li><p><strong>レポート自動生成</strong>: 複数のデータポイントを結合し、特定のスキーマに準拠したサマリーレポートをJSON形式で出力する。</p></li>
<li><p><strong>設定ファイル生成</strong>: アプリケーションの構成情報をユーザーの指示に基づきJSON形式で出力する。</p></li>
</ul>
<h2 class="wp-block-heading">2. 入出力契約の定義</h2>
<p>LLMのJSON出力において、期待される挙動と制約を明確にする「入出力契約」を事前に定義することが重要です。</p>
<h3 class="wp-block-heading">フォーマット</h3>
<ul class="wp-block-list">
<li><p><strong>必須</strong>: 常に出力は有効なJSON形式であること。</p></li>
<li><p><strong>スキーマ準拠</strong>: 事前に定義されたJSON Schemaに厳密に準拠すること。余分なプロパティを含めないこと。</p></li>
<li><p><strong>データ型</strong>: 各プロパティの値は、スキーマで指定されたデータ型(文字列、数値、真偽値、配列など)と一致すること。</p></li>
</ul>
<h3 class="wp-block-heading">失敗時の挙動</h3>
<ul class="wp-block-list">
<li><p><strong>無効なJSON</strong>: モデルが無効なJSONを生成した場合、アプリケーション側でバリデーションエラーを発生させ、リトライ戦略を適用する。</p></li>
<li><p><strong>スキーマ逸脱</strong>: JSONは有効だがスキーマに準拠しない場合、特定のエラーコードを返したり、ログを記録したりする。</p></li>
<li><p><strong>情報不足</strong>: 入力テキストから必要な情報を抽出できない場合、必須プロパティにnullまたは特定のデフォルト値を設定するか、ユーザーに情報不足を通知する特別なJSON構造を返す。</p></li>
</ul>
<h3 class="wp-block-heading">禁止事項</h3>
<ul class="wp-block-list">
<li><p>JSON以外の余計なテキスト(例: 説明文、謝辞)をJSONの前後に含めない。</p></li>
<li><p>指定されたJSON Schemaに含まれないプロパティを勝手に追加しない。</p></li>
<li><p>出力は常に単一のJSONオブジェクト(またはJSON配列)であること。</p></li>
</ul>
<h2 class="wp-block-heading">3. 制約付き仕様化</h2>
<p>JSON Schemaは、期待されるJSON出力の構造、データ型、必須プロパティ、許容値などを厳密に定義するための強力なツールです。主要なLLMプラットフォームでは、Function Calling (OpenAI) [1] や Tool Calling (Google Gemini) [2] のメカニズムを通じて、このJSON Schemaを直接モデルに渡し、構造化出力を誘導できます。</p>
<p><strong>例: ユーザーの問い合わせからタスク情報を抽出するJSON Schema</strong></p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TaskInformation",
"description": "ユーザーの問い合わせからタスクのタイトル、優先度、期限を抽出するスキーマ。",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "タスクのタイトル。50文字以内。"
},
"priority": {
"type": "string",
"description": "タスクの優先度。'高', '中', '低'のいずれか。",
"enum": ["高", "中", "低"]
},
"due_date": {
"type": "string",
"format": "date",
"description": "タスクの期限。YYYY-MM-DD形式。"
},
"assigned_to": {
"type": "array",
"items": {
"type": "string"
},
"description": "タスクを割り当てるユーザー名(複数可)。"
}
},
"required": ["title", "priority"]
}
</pre>
</div>
<h2 class="wp-block-heading">4. プロンプト設計</h2>
<p>LLMにJSON形式で出力させるためのプロンプトには、いくつかの戦略があります。</p>
<h3 class="wp-block-heading">4.1. ゼロショットプロンプト</h3>
<p>モデルに特別な例を与えず、指示のみでJSONを生成させる最もシンプルな方法です。Systemプロンプトで出力形式を厳しく指定することが重要です。</p>
<div class="codehilite">
<pre data-enlighter-language="generic">System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
JSONは以下のスキーマに厳密に従ってください。スキーマにないプロパティは追加しないでください。
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TaskInformation",
"description": "ユーザーの問い合わせからタスクのタイトル、優先度、期限を抽出するスキーマ。",
"type": "object",
"properties": {
"title": { "type": "string", "description": "タスクのタイトル。50文字以内。" },
"priority": { "type": "string", "description": "タスクの優先度。'高', '中', '低'のいずれか。", "enum": ["高", "中", "低"] },
"due_date": { "type": "string", "format": "date", "description": "タスクの期限。YYYY-MM-DD形式。" },
"assigned_to": { "type": "array", "items": { "type": "string" }, "description": "タスクを割り当てるユーザー名(複数可)。" }
},
"required": ["title", "priority"]
}
</pre>
</div>
<p>User:
来週金曜までに「月次レポート作成」を完了してください。優先度は高めです。</p>
<p>Assistant:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"title": "月次レポート作成",
"priority": "高",
"due_date": "2024-08-02",
"assigned_to": []
}
</pre>
</div>
<pre data-enlighter-language="generic">
### 4.2. 少数例プロンプト (Few-shot Prompting)
具体的な入出力例をいくつか示すことで、モデルが期待されるJSON構造と抽出ルールを学習しやすくなります。複雑な抽出や、曖昧な入力に対する挙動を定義するのに有効です。
```text
System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
以下の例を参考に、正確なJSONを生成してください。スキーマにないプロパティは追加しないでください。
---
Text: 「緊急!システム障害調査を本日中に。担当は田中さん。」
Output:
```json
{
"title": "システム障害調査",
"priority": "高",
"due_date": "2024-07-26",
"assigned_to": ["田中"]
}
</pre>
<hr/>
<p>Text: 「提案書ドラフト作成をお願いします。期限は来月末、優先度は中です。」
Output:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"title": "提案書ドラフト作成",
"priority": "中",
"due_date": "2024-08-31",
"assigned_to": []
}
</pre>
</div><hr/>
<p>Text: 「新規プロジェクトの企画書作成。優先度は低くていいです。」
Output:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"title": "新規プロジェクト企画書作成",
"priority": "低",
"due_date": null,
"assigned_to": []
}
</pre>
</div><hr/>
<p>User:
「請求書処理を進めてください。来週の火曜日までには。」</p>
<p>Assistant:</p>
<div class="codehilite">
<pre data-enlighter-language="generic">{
"title": "請求書処理",
"priority": "中",
"due_date": "2024-07-30",
"assigned_to": []
}
</pre>
</div>
<pre data-enlighter-language="generic">
### 4.3. Chain-of-Thought制約型プロンプト
直接JSONを生成させるのではなく、まず思考プロセスを言語化させ、その後でその思考に基づいたJSONを生成させる手法です。複雑な推論や、複数のステップを要する抽出に適しています。
```text
System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
まず、タスクの各要素(タイトル、優先度、期限、担当者)を識別し、その思考プロセスをステップバイステップで記述してください。
その後、最終的な結論としてJSON形式で出力してください。
User:
「ウェブサイトのデザイン修正、急ぎでお願いします。できれば明後日までに、担当は佐藤さんと鈴木さん。」
Assistant:
思考プロセス:
1. **タイトル**: 「ウェブサイトのデザイン修正」と識別。
2. **優先度**: 「急ぎでお願いします」から「高」と判断。
3. **期限**: 「明後日までに」をJST基準の2024年7月28日と判断。
4. **担当者**: 「佐藤さんと鈴木さん」を抽出。
結論:
```json
{
"title": "ウェブサイトのデザイン修正",
"priority": "高",
"due_date": "2024-07-28",
"assigned_to": ["佐藤", "鈴木"]
}
</pre>
<pre data-enlighter-language="generic">
## 5. 評価
LLMのJSON出力の品質を評価するには、複数の側面から検証が必要です。
### 5.1. 評価シナリオ
* **正例 (Happy Path)**: 明確な指示と十分な情報を含む入力。モデルが意図通りにJSONを生成できるかを確認。
* **難例 (Edge Cases)**:
* **情報不足**: 必要な情報が入力テキストにない場合、nullやデフォルト値が適切に設定されるか。
* **曖昧な指示**: 複数の解釈が可能な指示に対して、どの程度妥当なJSONを生成できるか。
* **競合する情報**: 矛盾する情報が含まれる場合、どのように解決または報告されるか。
* **コーナーケース (Adversarial Cases)**:
* **特殊文字**: JSON値にカンマ、引用符、改行などの特殊文字が含まれる場合の処理。
* **ネストの深さ**: 複雑なJSON Schemaに対する対応。
* **空文字列/空配列**: 特定のプロパティが空になる場合の適切な表現。
### 5.2. 自動評価の擬似コード
LLMのJSON出力評価は、手動レビューだけでなく、自動化されたテストが不可欠です。Pythonの`json`モジュールとPydanticライブラリ \[3] を組み合わせることで、堅牢な評価システムを構築できます。
```python
import json
from datetime import date
from typing import List, Optional
from pydantic import BaseModel, ValidationError, Field
# 評価に使用するPydanticモデル (JSON Schemaから生成可能)
class TaskInformation(BaseModel):
title: str = Field(max_length=50, description="タスクのタイトル")
priority: str = Field(..., pattern="^(高|中|低)$", description="タスクの優先度")
due_date: Optional2026/01/16 = Field(None, description="タスクの期限 (YYYY-MM-DD)")
assigned_to: List[str] = Field([], description="タスクを割り当てるユーザー名")
# カスタムバリデーション (オプション)
# @model_validator(mode='after')
# def check_due_date_priority_consistency(self) -> 'TaskInformation':
# if self.priority == '高' and self.due_date is None:
# raise ValueError("高優先度のタスクは期限が必要です。")
# return self
def evaluate_json_output(llm_output_str: str, expected_data: dict) -> dict:
"""
LLMのJSON出力を評価する関数。
Args:
llm_output_str: LLMから出力されたJSON文字列。
expected_data: 期待される正しい出力データ(意味的正確性の評価用)。
Returns:
評価結果を格納した辞書。
"""
results = {
"is_valid_json": False,
"schema_conforms": False,
"semantic_accuracy": False,
"error_details": None
}
# 1. JSON構文の検証
try:
parsed_json = json.loads(llm_output_str)
results["is_valid_json"] = True
except json.JSONDecodeError as e:
results["error_details"] = f"JSONDecodeError: {e}"
return results
# 2. スキーマ準拠の検証 (Pydanticモデルを使用)
try:
validated_task = TaskInformation(**parsed_json)
results["schema_conforms"] = True
except ValidationError as e:
results["error_details"] = f"ValidationError: {e}"
return results
except Exception as e:
results["error_details"] = f"Unexpected Schema Validation Error: {e}"
return results
# 3. 意味的正確性の評価
# 実際の実装では、ここでより複雑なロジック(例: キーごとの値比較、許容範囲のチェック、NLPベースの類似度評価)が必要。
# ここでは単純な部分一致で評価の概念を示す。
semantic_matches = 0
total_fields = 0
for key, expected_value in expected_data.items():
if key in parsed_json:
total_fields += 1
# Pydanticはdateオブジェクトに変換するので、比較もdateオブジェクトで行う
actual_value = getattr(validated_task, key)
if actual_value == expected_value:
semantic_matches += 1
if total_fields > 0 and semantic_matches == total_fields:
results["semantic_accuracy"] = True
else:
results["error_details"] = f"Semantic mismatch. Expected: {expected_data}, Got: {parsed_json}"
return results
# 使用例 (JST 2024年7月26日)
if __name__ == "__main__":
test_cases = [
{
"name": "正例",
"llm_output": '{"title": "月次レポート作成", "priority": "高", "due_date": "2024-08-02", "assigned_to": []}',
"expected": {"title": "月次レポート作成", "priority": "高", "due_date": date(2024, 8, 2), "assigned_to": []}
},
{
"name": "無効なJSON",
"llm_output": '{"title": "テスト", "priority": "中",',
"expected": {}
},
{
"name": "スキーマ不適合 (priorityの値が不正)",
"llm_output": '{"title": "緊急タスク", "priority": "最高", "due_date": "2024-07-27", "assigned_to": ["山田"]}',
"expected": {}
},
{
"name": "意味的に不正確",
"llm_output": '{"title": "ウェブサイトのデザイン修正", "priority": "高", "due_date": "2024-07-27", "assigned_to": ["佐藤", "鈴木"]}',
"expected": {"title": "ウェブサイトのデザイン修正", "priority": "高", "due_date": date(2024, 7, 28), "assigned_to": ["佐藤", "鈴木"]} # 期限が1日ずれている
}
]
for case in test_cases:
print(f"\n--- 評価ケース: {case['name']} ---")
result = evaluate_json_output(case["llm_output"], case["expected"])
print(json.dumps(result, indent=2, ensure_ascii=False, default=str)) # dateオブジェクトをstrに変換
</pre>
<h2 class="wp-block-heading">6. 失敗モードと抑制手法</h2>
<p>LLMがJSON出力に失敗する主なモードと、それらを抑制するための戦略を以下に示します。</p>
<h3 class="wp-block-heading">6.1. 失敗モード</h3>
<ul class="wp-block-list">
<li><p><strong>幻覚 (Hallucination)</strong>: 入力テキストに存在しない情報をJSONプロパティの値として生成する。</p></li>
<li><p><strong>様式崩れ (Malformed JSON)</strong>: JSONの構文規則に違反する出力を生成する(例: カンマの欠落、引用符の閉じ忘れ)。</p></li>
<li><p><strong>スキーマ逸脱 (Schema Deviation)</strong>: 有効なJSONではあるが、指定されたJSON Schemaの型、必須プロパティ、<code>enum</code>などの制約を満たさない。</p></li>
<li><p><strong>脱線 (Off-topic Output)</strong>: JSONの前後に追加の説明文や余計なテキストを生成してしまう。</p></li>
<li><p><strong>禁止事項違反</strong>: 指定された以外のプロパティを追加したり、本来生成すべきでない値を生成したりする。</p></li>
</ul>
<h3 class="wp-block-heading">6.2. 抑制手法</h3>
<ul class="wp-block-list">
<li><p><strong>System指示の厳格化</strong>: Systemプロンプトで、出力形式、スキーマ準拠、余計なテキストの禁止を極めて明確かつ繰り返し指示する。</p>
<ul>
<li>例:「<strong>いかなる場合もJSON以外のテキストは出力しないでください。</strong>」「<strong>提供されたJSONスキーマに厳密に準拠し、スキーマ外のフィールドは含めないでください。</strong>」</li>
</ul></li>
<li><p><strong>Few-shot例の活用</strong>: 失敗モードに対応する具体的な例(例: 「情報がない場合は<code>null</code>を設定する」)を含める。</p></li>
<li><p><strong>Function Calling/Tool Callingの利用</strong>: LLMが特定の関数呼び出しを生成するように促すことで、モデルの出力形式を強力に制約できます。これにより、JSON Schemaに準拠した引数のみを生成させることが可能になります [1, 2]。</p></li>
<li><p><strong>出力後の検証ステップ</strong>:</p>
<ul>
<li><p><strong>JSON構文検証</strong>: 生成された文字列が有効なJSONであるか<code>json.loads()</code>などでチェックする。</p></li>
<li><p><strong>スキーマバリデーション</strong>: Pydantic [3] や<code>jsonschema</code>ライブラリなどを用いて、定義されたJSON Schemaに準拠しているか検証する。PythonライブラリのOutlines [4] やMarvin.aiなども、Pydanticモデルとの統合を通じて、モデルの出力段階からスキーマ準拠を強制する機能を提供しています。</p></li>
</ul></li>
<li><p><strong>リトライ戦略</strong>:</p>
<ul>
<li><p>バリデーションエラーが発生した場合、エラーメッセージをLLMにフィードバックし、再生成を促す。</p></li>
<li><p>複数回のリトライ後も失敗する場合は、デフォルト値の適用、人間による介入、またはエラー通知に切り替える。</p></li>
</ul></li>
<li><p><strong>モデル選択と微調整 (Fine-tuning)</strong>: 構造化出力に特化したモデルや、特定のスキーマに合わせて微調整されたモデルは、より高い精度でJSONを生成できます。</p></li>
</ul>
<h2 class="wp-block-heading">7. 改良と再評価</h2>
<p>評価結果で特定された失敗モードや低いスコアは、プロンプトやシステム全体の改良点を示します。</p>
<ol class="wp-block-list">
<li><p><strong>プロンプトの修正</strong>: 曖昧な指示の明確化、制約の強化、Few-shot例の追加・修正など。</p></li>
<li><p><strong>JSON Schemaの調整</strong>: スキーマ自体が複雑すぎる、あるいは不十分な場合に、よりシンプルにするか、必要な制約を追加する。</p></li>
<li><p><strong>モデルの再選択</strong>: より高性能なモデルや、構造化出力に強いモデルへの切り替えを検討する。</p></li>
<li><p><strong>ファインチューニング</strong>: 大量の高品質な入出力ペアがある場合、特定のタスクに特化してモデルをファインチューニングすることで、JSON出力の精度を大幅に向上させることが可能。</p></li>
<li><p><strong>ツールの導入</strong>: OutlinesやGuidanceのような出力制御ライブラリを導入し、正規表現やCFGベースの文法制約を適用する。</p></li>
</ol>
<p>改良後は、同じ評価シナリオと自動評価スクリプトを用いて、必ず再評価を実施し、改善効果を定量的に測定します。このループを繰り返すことで、JSON出力の信頼性と堅牢性を高めます。</p>
<h2 class="wp-block-heading">8. まとめ</h2>
<p>LLMからのJSON出力の制御と評価は、AIアプリケーションの信頼性と実用性を高める上で極めて重要です。明確な入出力契約、厳密なJSON Schemaによる制約、そしてゼロショット、少数例、Chain-of-Thoughtなどのプロンプト設計を組み合わせることで、精度の高いJSON生成が期待できます。さらに、Pydanticなどを用いた自動評価と、失敗モードに対する具体的な抑制手法を適用し、継続的な改良サイクルを回すことが、堅牢なシステム構築の鍵となります。2024年7月26日現在、Function Callingや専用ライブラリの進化により、この分野は日々進化しています。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["要件定義"] --> |JSONスキーマ化| B{"プロンプト設計"};
B --> |プロンプト提示| C["LLMによるJSON生成"];
C --> |JSON出力| D["出力検証・評価"];
D -- 成功の場合 --> E["成果物利用"];
D -- 失敗の場合 --> F["誤り分析"];
F --> |改善案| G["プロンプト改良"];
G --> |再設計| B;
</pre></div>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
LLMのJSON出力制御と評価
大規模言語モデル(LLM)からのJSON形式での出力制御は、API連携、データ抽出、自動化ワークフローなど、多岐にわたるアプリケーションで不可欠です。本記事では、LLMに正確で構造化されたJSONを出力させるためのプロンプト設計、評価手法、そして一般的な失敗モードとその抑制戦略について解説します。
1. ユースケース定義
LLMのJSON出力制御は、以下のようなシナリオで特に重要となります。
API呼び出し引数の生成: ユーザーの自然言語によるリクエストを解析し、外部APIが期待するJSON形式の引数を生成する。
構造化データ抽出: 非構造化テキスト(例: 記事、レビュー)から特定のエンティティや情報を抽出し、JSONオブジェクトとして整形する。
レポート自動生成: 複数のデータポイントを結合し、特定のスキーマに準拠したサマリーレポートをJSON形式で出力する。
設定ファイル生成: アプリケーションの構成情報をユーザーの指示に基づきJSON形式で出力する。
2. 入出力契約の定義
LLMのJSON出力において、期待される挙動と制約を明確にする「入出力契約」を事前に定義することが重要です。
フォーマット
失敗時の挙動
無効なJSON: モデルが無効なJSONを生成した場合、アプリケーション側でバリデーションエラーを発生させ、リトライ戦略を適用する。
スキーマ逸脱: JSONは有効だがスキーマに準拠しない場合、特定のエラーコードを返したり、ログを記録したりする。
情報不足: 入力テキストから必要な情報を抽出できない場合、必須プロパティにnullまたは特定のデフォルト値を設定するか、ユーザーに情報不足を通知する特別なJSON構造を返す。
禁止事項
JSON以外の余計なテキスト(例: 説明文、謝辞)をJSONの前後に含めない。
指定されたJSON Schemaに含まれないプロパティを勝手に追加しない。
出力は常に単一のJSONオブジェクト(またはJSON配列)であること。
3. 制約付き仕様化
JSON Schemaは、期待されるJSON出力の構造、データ型、必須プロパティ、許容値などを厳密に定義するための強力なツールです。主要なLLMプラットフォームでは、Function Calling (OpenAI) [1] や Tool Calling (Google Gemini) [2] のメカニズムを通じて、このJSON Schemaを直接モデルに渡し、構造化出力を誘導できます。
例: ユーザーの問い合わせからタスク情報を抽出するJSON Schema
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TaskInformation",
"description": "ユーザーの問い合わせからタスクのタイトル、優先度、期限を抽出するスキーマ。",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "タスクのタイトル。50文字以内。"
},
"priority": {
"type": "string",
"description": "タスクの優先度。'高', '中', '低'のいずれか。",
"enum": ["高", "中", "低"]
},
"due_date": {
"type": "string",
"format": "date",
"description": "タスクの期限。YYYY-MM-DD形式。"
},
"assigned_to": {
"type": "array",
"items": {
"type": "string"
},
"description": "タスクを割り当てるユーザー名(複数可)。"
}
},
"required": ["title", "priority"]
}
4. プロンプト設計
LLMにJSON形式で出力させるためのプロンプトには、いくつかの戦略があります。
4.1. ゼロショットプロンプト
モデルに特別な例を与えず、指示のみでJSONを生成させる最もシンプルな方法です。Systemプロンプトで出力形式を厳しく指定することが重要です。
System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
JSONは以下のスキーマに厳密に従ってください。スキーマにないプロパティは追加しないでください。
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "TaskInformation",
"description": "ユーザーの問い合わせからタスクのタイトル、優先度、期限を抽出するスキーマ。",
"type": "object",
"properties": {
"title": { "type": "string", "description": "タスクのタイトル。50文字以内。" },
"priority": { "type": "string", "description": "タスクの優先度。'高', '中', '低'のいずれか。", "enum": ["高", "中", "低"] },
"due_date": { "type": "string", "format": "date", "description": "タスクの期限。YYYY-MM-DD形式。" },
"assigned_to": { "type": "array", "items": { "type": "string" }, "description": "タスクを割り当てるユーザー名(複数可)。" }
},
"required": ["title", "priority"]
}
User:
来週金曜までに「月次レポート作成」を完了してください。優先度は高めです。
Assistant:
{
"title": "月次レポート作成",
"priority": "高",
"due_date": "2024-08-02",
"assigned_to": []
}
### 4.2. 少数例プロンプト (Few-shot Prompting)
具体的な入出力例をいくつか示すことで、モデルが期待されるJSON構造と抽出ルールを学習しやすくなります。複雑な抽出や、曖昧な入力に対する挙動を定義するのに有効です。
```text
System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
以下の例を参考に、正確なJSONを生成してください。スキーマにないプロパティは追加しないでください。
---
Text: 「緊急!システム障害調査を本日中に。担当は田中さん。」
Output:
```json
{
"title": "システム障害調査",
"priority": "高",
"due_date": "2024-07-26",
"assigned_to": ["田中"]
}
Text: 「提案書ドラフト作成をお願いします。期限は来月末、優先度は中です。」
Output:
{
"title": "提案書ドラフト作成",
"priority": "中",
"due_date": "2024-08-31",
"assigned_to": []
}
Text: 「新規プロジェクトの企画書作成。優先度は低くていいです。」
Output:
{
"title": "新規プロジェクト企画書作成",
"priority": "低",
"due_date": null,
"assigned_to": []
}
User:
「請求書処理を進めてください。来週の火曜日までには。」
Assistant:
{
"title": "請求書処理",
"priority": "中",
"due_date": "2024-07-30",
"assigned_to": []
}
### 4.3. Chain-of-Thought制約型プロンプト
直接JSONを生成させるのではなく、まず思考プロセスを言語化させ、その後でその思考に基づいたJSONを生成させる手法です。複雑な推論や、複数のステップを要する抽出に適しています。
```text
System:
あなたは与えられたテキストからタスク情報を抽出し、JSON形式で出力するアシスタントです。
まず、タスクの各要素(タイトル、優先度、期限、担当者)を識別し、その思考プロセスをステップバイステップで記述してください。
その後、最終的な結論としてJSON形式で出力してください。
User:
「ウェブサイトのデザイン修正、急ぎでお願いします。できれば明後日までに、担当は佐藤さんと鈴木さん。」
Assistant:
思考プロセス:
1. **タイトル**: 「ウェブサイトのデザイン修正」と識別。
2. **優先度**: 「急ぎでお願いします」から「高」と判断。
3. **期限**: 「明後日までに」をJST基準の2024年7月28日と判断。
4. **担当者**: 「佐藤さんと鈴木さん」を抽出。
結論:
```json
{
"title": "ウェブサイトのデザイン修正",
"priority": "高",
"due_date": "2024-07-28",
"assigned_to": ["佐藤", "鈴木"]
}
## 5. 評価
LLMのJSON出力の品質を評価するには、複数の側面から検証が必要です。
### 5.1. 評価シナリオ
* **正例 (Happy Path)**: 明確な指示と十分な情報を含む入力。モデルが意図通りにJSONを生成できるかを確認。
* **難例 (Edge Cases)**:
* **情報不足**: 必要な情報が入力テキストにない場合、nullやデフォルト値が適切に設定されるか。
* **曖昧な指示**: 複数の解釈が可能な指示に対して、どの程度妥当なJSONを生成できるか。
* **競合する情報**: 矛盾する情報が含まれる場合、どのように解決または報告されるか。
* **コーナーケース (Adversarial Cases)**:
* **特殊文字**: JSON値にカンマ、引用符、改行などの特殊文字が含まれる場合の処理。
* **ネストの深さ**: 複雑なJSON Schemaに対する対応。
* **空文字列/空配列**: 特定のプロパティが空になる場合の適切な表現。
### 5.2. 自動評価の擬似コード
LLMのJSON出力評価は、手動レビューだけでなく、自動化されたテストが不可欠です。Pythonの`json`モジュールとPydanticライブラリ \[3] を組み合わせることで、堅牢な評価システムを構築できます。
```python
import json
from datetime import date
from typing import List, Optional
from pydantic import BaseModel, ValidationError, Field
# 評価に使用するPydanticモデル (JSON Schemaから生成可能)
class TaskInformation(BaseModel):
title: str = Field(max_length=50, description="タスクのタイトル")
priority: str = Field(..., pattern="^(高|中|低)$", description="タスクの優先度")
due_date: Optional2026/01/16 = Field(None, description="タスクの期限 (YYYY-MM-DD)")
assigned_to: List[str] = Field([], description="タスクを割り当てるユーザー名")
# カスタムバリデーション (オプション)
# @model_validator(mode='after')
# def check_due_date_priority_consistency(self) -> 'TaskInformation':
# if self.priority == '高' and self.due_date is None:
# raise ValueError("高優先度のタスクは期限が必要です。")
# return self
def evaluate_json_output(llm_output_str: str, expected_data: dict) -> dict:
"""
LLMのJSON出力を評価する関数。
Args:
llm_output_str: LLMから出力されたJSON文字列。
expected_data: 期待される正しい出力データ(意味的正確性の評価用)。
Returns:
評価結果を格納した辞書。
"""
results = {
"is_valid_json": False,
"schema_conforms": False,
"semantic_accuracy": False,
"error_details": None
}
# 1. JSON構文の検証
try:
parsed_json = json.loads(llm_output_str)
results["is_valid_json"] = True
except json.JSONDecodeError as e:
results["error_details"] = f"JSONDecodeError: {e}"
return results
# 2. スキーマ準拠の検証 (Pydanticモデルを使用)
try:
validated_task = TaskInformation(**parsed_json)
results["schema_conforms"] = True
except ValidationError as e:
results["error_details"] = f"ValidationError: {e}"
return results
except Exception as e:
results["error_details"] = f"Unexpected Schema Validation Error: {e}"
return results
# 3. 意味的正確性の評価
# 実際の実装では、ここでより複雑なロジック(例: キーごとの値比較、許容範囲のチェック、NLPベースの類似度評価)が必要。
# ここでは単純な部分一致で評価の概念を示す。
semantic_matches = 0
total_fields = 0
for key, expected_value in expected_data.items():
if key in parsed_json:
total_fields += 1
# Pydanticはdateオブジェクトに変換するので、比較もdateオブジェクトで行う
actual_value = getattr(validated_task, key)
if actual_value == expected_value:
semantic_matches += 1
if total_fields > 0 and semantic_matches == total_fields:
results["semantic_accuracy"] = True
else:
results["error_details"] = f"Semantic mismatch. Expected: {expected_data}, Got: {parsed_json}"
return results
# 使用例 (JST 2024年7月26日)
if __name__ == "__main__":
test_cases = [
{
"name": "正例",
"llm_output": '{"title": "月次レポート作成", "priority": "高", "due_date": "2024-08-02", "assigned_to": []}',
"expected": {"title": "月次レポート作成", "priority": "高", "due_date": date(2024, 8, 2), "assigned_to": []}
},
{
"name": "無効なJSON",
"llm_output": '{"title": "テスト", "priority": "中",',
"expected": {}
},
{
"name": "スキーマ不適合 (priorityの値が不正)",
"llm_output": '{"title": "緊急タスク", "priority": "最高", "due_date": "2024-07-27", "assigned_to": ["山田"]}',
"expected": {}
},
{
"name": "意味的に不正確",
"llm_output": '{"title": "ウェブサイトのデザイン修正", "priority": "高", "due_date": "2024-07-27", "assigned_to": ["佐藤", "鈴木"]}',
"expected": {"title": "ウェブサイトのデザイン修正", "priority": "高", "due_date": date(2024, 7, 28), "assigned_to": ["佐藤", "鈴木"]} # 期限が1日ずれている
}
]
for case in test_cases:
print(f"\n--- 評価ケース: {case['name']} ---")
result = evaluate_json_output(case["llm_output"], case["expected"])
print(json.dumps(result, indent=2, ensure_ascii=False, default=str)) # dateオブジェクトをstrに変換
6. 失敗モードと抑制手法
LLMがJSON出力に失敗する主なモードと、それらを抑制するための戦略を以下に示します。
6.1. 失敗モード
幻覚 (Hallucination): 入力テキストに存在しない情報をJSONプロパティの値として生成する。
様式崩れ (Malformed JSON): JSONの構文規則に違反する出力を生成する(例: カンマの欠落、引用符の閉じ忘れ)。
スキーマ逸脱 (Schema Deviation): 有効なJSONではあるが、指定されたJSON Schemaの型、必須プロパティ、enumなどの制約を満たさない。
脱線 (Off-topic Output): JSONの前後に追加の説明文や余計なテキストを生成してしまう。
禁止事項違反: 指定された以外のプロパティを追加したり、本来生成すべきでない値を生成したりする。
6.2. 抑制手法
System指示の厳格化: Systemプロンプトで、出力形式、スキーマ準拠、余計なテキストの禁止を極めて明確かつ繰り返し指示する。
- 例:「いかなる場合もJSON以外のテキストは出力しないでください。」「提供されたJSONスキーマに厳密に準拠し、スキーマ外のフィールドは含めないでください。」
Few-shot例の活用: 失敗モードに対応する具体的な例(例: 「情報がない場合はnullを設定する」)を含める。
Function Calling/Tool Callingの利用: LLMが特定の関数呼び出しを生成するように促すことで、モデルの出力形式を強力に制約できます。これにより、JSON Schemaに準拠した引数のみを生成させることが可能になります [1, 2]。
出力後の検証ステップ:
JSON構文検証: 生成された文字列が有効なJSONであるかjson.loads()などでチェックする。
スキーマバリデーション: Pydantic [3] やjsonschemaライブラリなどを用いて、定義されたJSON Schemaに準拠しているか検証する。PythonライブラリのOutlines [4] やMarvin.aiなども、Pydanticモデルとの統合を通じて、モデルの出力段階からスキーマ準拠を強制する機能を提供しています。
リトライ戦略:
モデル選択と微調整 (Fine-tuning): 構造化出力に特化したモデルや、特定のスキーマに合わせて微調整されたモデルは、より高い精度でJSONを生成できます。
7. 改良と再評価
評価結果で特定された失敗モードや低いスコアは、プロンプトやシステム全体の改良点を示します。
プロンプトの修正: 曖昧な指示の明確化、制約の強化、Few-shot例の追加・修正など。
JSON Schemaの調整: スキーマ自体が複雑すぎる、あるいは不十分な場合に、よりシンプルにするか、必要な制約を追加する。
モデルの再選択: より高性能なモデルや、構造化出力に強いモデルへの切り替えを検討する。
ファインチューニング: 大量の高品質な入出力ペアがある場合、特定のタスクに特化してモデルをファインチューニングすることで、JSON出力の精度を大幅に向上させることが可能。
ツールの導入: OutlinesやGuidanceのような出力制御ライブラリを導入し、正規表現やCFGベースの文法制約を適用する。
改良後は、同じ評価シナリオと自動評価スクリプトを用いて、必ず再評価を実施し、改善効果を定量的に測定します。このループを繰り返すことで、JSON出力の信頼性と堅牢性を高めます。
8. まとめ
LLMからのJSON出力の制御と評価は、AIアプリケーションの信頼性と実用性を高める上で極めて重要です。明確な入出力契約、厳密なJSON Schemaによる制約、そしてゼロショット、少数例、Chain-of-Thoughtなどのプロンプト設計を組み合わせることで、精度の高いJSON生成が期待できます。さらに、Pydanticなどを用いた自動評価と、失敗モードに対する具体的な抑制手法を適用し、継続的な改良サイクルを回すことが、堅牢なシステム構築の鍵となります。2024年7月26日現在、Function Callingや専用ライブラリの進化により、この分野は日々進化しています。
graph TD
A["要件定義"] --> |JSONスキーマ化| B{"プロンプト設計"};
B --> |プロンプト提示| C["LLMによるJSON生成"];
C --> |JSON出力| D["出力検証・評価"];
D -- 成功の場合 --> E["成果物利用"];
D -- 失敗の場合 --> F["誤り分析"];
F --> |改善案| G["プロンプト改良"];
G --> |再設計| B;
コメント