LLM出力のJSON/YAML形式制御:確実な構造化データ生成のためのプロンプト戦略と評価

Tech

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

LLM出力のJSON/YAML形式制御:確実な構造化データ生成のためのプロンプト戦略と評価

大規模言語モデル(LLM)は、自然言語の生成だけでなく、構造化されたデータ形式であるJSONやYAMLの生成においても優れた能力を発揮します。これにより、LLMの応用範囲は大幅に広がり、外部APIの呼び出し、設定ファイルの動的生成、非構造化テキストからのデータ抽出など、多様な自動化ユースケースに利用可能になります。本記事では、LLMからJSON/YAML形式の構造化データを確実に出力させるためのプロンプト設計、評価、誤り分析、および改良戦略について解説します。

ユースケース定義

LLMがJSON/YAML形式で出力する主要なユースケースは以下の通りです。

  • API呼び出しパラメータの自動生成: ユーザーの自然言語指示を元に、外部APIに必要なJSON形式の引数を生成する。

  • 設定ファイルの動的作成: システム設定やアプリケーションのデプロイ設定(YAML形式など)を、特定の要件に基づいてLLMに生成させる。

  • 非構造化テキストからのデータ抽出: ニュース記事やドキュメントから特定のエンティティ(人物名、地名、日付、数値など)を抽出し、構造化されたJSONオブジェクトとして出力する。

  • 構造化されたレポートやログの生成: LLMの分析結果を、機械処理が容易なJSON形式で出力し、後続システムとの連携を可能にする。

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

LLMからの構造化出力の信頼性を高めるためには、厳格な入出力契約を定義することが不可欠です。

フォーマットの定義

  • JSON: RFC 8259準拠の有効なJSONオブジェクトまたはJSON配列とします。特定のJSONスキーマに従うことを必須とします。例えば、{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}} のようなスキーマ定義を出力指示に含めます。

  • YAML: YAML 1.2準拠の有効なドキュメントとします。JSONと同様に、特定のスキーマや構造に従うことを指示します。YAMLはJSONのスーパーセットであるため、JSONとほぼ同様の戦略が適用可能です [5]。

失敗時の挙動

  • モデルが指定された形式(JSON/YAML)に準拠できない場合、有効なJSON/YAMLの最小限の構造(例: {} または [])を返すか、エラーを示す特定の構造を返します。

  • Gemini APIでは response_mime_type="application/json" パラメータ [1] を、OpenAI APIでは response_format={"type": "json_object"} パラメータ [2] を設定することで、モデルが有効なJSONを出力しない場合にAPIがエラーを返すように強制できます。これにより、無効な出力をアプリケーション層でハンドリングする手間を省き、エラー応答に対するリトライ戦略を講じることが推奨されます。

禁止事項

  • JSON/YAMLのブロック前後に、モデルによる説明文やコメントを付加することを禁止します。

  • プロンプトで指示されていない、無関係な情報や幻覚を含むデータを生成することを禁止します。

  • 出力は、指定されたスキーマの範囲に厳密に限定されるべきであり、余計なフィールドを含めてはなりません。

プロンプト設計

確実なJSON/YAML出力を得るために、以下の3種類のプロンプト設計を検討します。特に、モデルの温度(temperature)は低めに設定(0.0~0.3程度)し、出力の一貫性を高めることが重要です。

1. ゼロショットプロンプト(Zero-shot Prompt)

出力形式とスキーマを明確に指示する最も基本的なアプローチです。

あなたはユーザーの指示に基づいて、厳格にJSON形式で情報を提供するアシスタントです。
JSONブロック以外に余計なテキストは含めないでください。
以下の情報をJSONオブジェクトとして出力してください。

対象テキスト: "東京の天気は晴れで、気温は28度です。明日は雨が降るでしょう。"
JSONスキーマ:
{
  "type": "object",
  "properties": {
    "city": {"type": "string", "description": "都市名"},
    "weather": {"type": "string", "description": "現在の天気"},
    "temperature": {"type": "integer", "description": "現在の気温(摂氏)"},
    "forecast_tomorrow": {"type": "string", "description": "明日の天気予報"}
  },
  "required": ["city", "weather", "temperature", "forecast_tomorrow"]
}

出力JSON:

2. 少数例プロンプト(Few-shot Prompt)

期待する入出力ペアをいくつか提示し、モデルに学習させます。特に複雑な構造や曖昧な指示の場合に有効です。

あなたはユーザーの指示に基づいて、厳格にJSON形式で情報を提供するアシスタントです。
JSONブロック以外に余計なテキストは含めないでください。

---
対象テキスト: "大阪のイベント情報:来月10日にAIカンファレンスが開催されます。"
JSONスキーマ:
{
  "type": "object",
  "properties": {
    "event_name": {"type": "string"},
    "location": {"type": "string"},
    "date": {"type": "string", "format": "date"}
  },
  "required": ["event_name", "location", "date"]
}
出力JSON:
{
  "event_name": "AIカンファレンス",
  "location": "大阪",
  "date": "2024-08-10"
}
---
対象テキスト: "札幌の特産品はラーメンとビールです。"
JSONスキーマ:
{
  "type": "object",
  "properties": {
    "city": {"type": "string"},
    "specialties": {"type": "array", "items": {"type": "string"}}
  },
  "required": ["city", "specialties"]
}
出力JSON:
{
  "city": "札幌",
  "specialties": ["ラーメン", "ビール"]
}
---
対象テキスト: "京都の観光名所は金閣寺と清水寺、開催中の祭りは祇園祭です。"
JSONスキーマ:
{
  "type": "object",
  "properties": {
    "city": {"type": "string"},
    "attractions": {"type": "array", "items": {"type": "string"}},
    "current_festival": {"type": "string"}
  },
  "required": ["city", "attractions"]
}
出力JSON:

3. Chain-of-Thought制約型プロンプト(CoT-constrained Prompt)

モデルに思考プロセスを段階的に記述させ、最終的にJSON/YAMLを生成させることで、複雑な抽出や変換の精度を高めます。

あなたはユーザーの指示に基づいて、厳格にJSON形式で情報を提供するアシスタントです。
JSONブロック以外に余計なテキストは含めないでください。

以下の思考プロセスを経て、最終的にJSONを出力してください。

1. 対象テキストから必要な情報を特定します。

2. 特定した情報をJSONスキーマの各フィールドにマッピングします。

3. マッピングした情報でJSONオブジェクトを構築します。

対象テキスト: "プロジェクトAは現在進行中。担当は田中。期限は2024年12月末。"
JSONスキーマ:
{
  "type": "object",
  "properties": {
    "project_name": {"type": "string"},
    "status": {"type": "string", "enum": ["進行中", "完了", "保留"]},
    "assigned_to": {"type": "string"},
    "due_date": {"type": "string", "format": "date"}
  },
  "required": ["project_name", "status", "assigned_to", "due_date"]
}

思考プロセス:

1. 対象テキストから「プロジェクトA」「進行中」「田中」「2024年12月末」を特定。

2. project_name: プロジェクトA, status: 進行中, assigned_to: 田中, due_date: 2024-12-31 にマッピング。

3. 上記情報でJSONを構築。

出力JSON:

評価

LLMの構造化出力は、その後のシステム連携において非常に重要であるため、厳密な評価が必要です。

評価シナリオ

  • 正例: 簡単なデータ構造、一般的な入力。

    • 例: 人物名と年齢の抽出、単純な天気情報。
  • 難例: 複雑なネスト構造、エッジケース、特殊文字を含む入力。

    • 例: 複数のイベント情報を含むテキストからの配列抽出、ヌル値や空リストが期待されるケース、"\を含む文字列。
  • コーナーケース: 曖昧な入力、情報不足な入力。

    • 例: 必須フィールドがテキストに存在しない場合、複数の解釈が可能なテキスト。

自動評価の擬似コード

自動評価は、以下のステップで実施できます(Pythonを想定)。

import json
import jsonschema
import re
import yaml # YAMLの場合

def validate_json_output(output_string: str, json_schema: dict) -> dict:
    """
    LLMのJSON出力を検証する関数
    Args:
        output_string: LLMから得られたJSON文字列
        json_schema: 期待されるJSONスキーマ
    Returns:
        検証結果を含む辞書
        例: {"is_valid_format": True, "is_valid_schema": True, "details": "Success"}
    """
    result = {
        "is_valid_format": False,
        "is_valid_schema": False,
        "content_matches": {},
        "details": ""
    }

    # 1. JSON構文の検証

    try:
        parsed_json = json.loads(output_string)
        result["is_valid_format"] = True
    except json.JSONDecodeError as e:
        result["details"] = f"JSON形式が不正: {e}"
        return result

    # 2. JSONスキーマの検証

    try:
        jsonschema.validate(instance=parsed_json, schema=json_schema)
        result["is_valid_schema"] = True
    except jsonschema.exceptions.ValidationError as e:
        result["details"] = f"JSONスキーマ検証エラー: {e.message}"
        return result

    # 3. 特定のビジネスロジック/内容の検証(例: 特定のキーの有無、値の範囲、正規表現)


    # 例: "city"キーが存在し、かつ文字列であるか

    if "city" in parsed_json and isinstance(parsed_json["city"], str):
        result["content_matches"]["city_exists_and_is_string"] = True
    else:
        result["content_matches"]["city_exists_and_is_string"] = False
        result["details"] += " 'city'フィールドが期待通りではありません。"

    # 例: "temperature"が数値であり、-50から50の範囲内か

    if "temperature" in parsed_json and isinstance(parsed_json["temperature"], (int, float)) and -50 <= parsed_json["temperature"] <= 50:
        result["content_matches"]["temperature_in_range"] = True
    else:
        result["content_matches"]["temperature_in_range"] = False
        result["details"] += " 'temperature'フィールドが期待通りではありません。"

    if result["is_valid_format"] and result["is_valid_schema"] and all(result["content_matches"].values()):
        result["details"] = "すべての検証に成功しました。"

    return result

# YAMLの場合の例

def validate_yaml_output(output_string: str, yaml_schema: dict) -> dict:

    # YAMLの構文検証 (PyYAMLを使用)

    try:
        parsed_yaml = yaml.safe_load(output_string)

        # 以降はJSONスキーマ検証と同様のロジックを適用可能(YAMLスキーマバリデータも存在するが、ここでは省略)

    except yaml.YAMLError as e:
        pass # エラーハンドリング
    return {"is_valid_format": False, "is_valid_schema": False, "details": ""} # 仮の戻り値

# 評価の擬似ルーブリック


# 採点基準:


# - JSON構文の有効性: 40点


# - JSONスキーマへの準拠: 40点


# - 特定のキーの存在と値の妥当性(ビジネスロジック): 20点


# 合計100点
  • 入力: LLM出力文字列、期待されるJSON/YAMLスキーマ。

  • 前提: jsonモジュール、jsonschemaライブラリ(pip install jsonschema)、PyYAMLライブラリ(pip install PyYAML)。

  • 計算量: JSONパースとスキーマ検証は入力サイズに比例。ビジネスロジック検証は定数時間または入力サイズに比例。

  • メモリ条件: 入力JSON/YAMLのサイズに依存。

誤り分析と抑制手法

LLMからの構造化出力は、いくつかの失敗モードに陥る可能性があります。これらの分析と抑制手法を以下に示します。

失敗モード

  • 幻覚(Hallucination): プロンプトにない情報や事実に基づかない情報をJSON/YAMLに含める。

  • 様式崩れ(Format Deviation): 有効なJSON/YAML形式ではない文字列(例: 末尾のカンマ忘れ、括弧の不一致)を出力する。

  • 脱線(Off-topic Generation): JSON/YAMLブロックの前後に追加の説明文や無関係なテキストを生成する。

  • 禁止事項違反: 定義されたスキーマにないフィールドを含めたり、必須フィールドを欠落させたりする。

抑制手法

  • System指示の厳格化: Systemプロンプトで「JSON形式のみを出力すること。余計なテキストは一切含めないこと」など、厳格な制約を明示的に指示します。

  • JSONモードの利用: Google Gemini APIの response_mime_type="application/json" [1] や OpenAI APIの response_format={"type": "json_object"} [2] のように、モデル固有のJSON出力強制機能を利用します。これにより、モデルは有効なJSONのみを生成するように強く誘導されます。

  • 出力検証ステップ: 上記の自動評価擬似コードのように、LLMの出力を受け取った後、必ずJSON/YAML構文とスキーマの検証を実行します。

  • リトライ戦略: 検証に失敗した場合、エラーメッセージとともにプロンプトを再構築し、再度LLMに生成を要求するリトライ戦略を実装します。例えば、「無効なJSONが出力されました。[エラーメッセージ] に注意し、厳密にJSONスキーマに従って再出力してください。」

  • 構造化された少数例: 特に複雑な出力の場合、期待する正確なJSON/YAML構造を示す少数例を提供することで、モデルの理解を深めます。

  • より高性能なモデルの利用: JSON/YAML出力の品質はモデルの能力に依存するため、Gemini 1.5 ProやGPT-4oなど、より高性能なモデルの使用を検討します。

改良

誤り分析の結果に基づき、以下の点を中心にプロンプトやシステムを改良します。

  • プロンプトの具体性向上: 失敗したケースについて、プロンプトの指示をより具体的、かつ明確に修正します。特にスキーマ定義をより詳細に記述します。

  • 禁止事項の強調: 失敗モードで確認された禁止事項違反に対し、プロンプト内でその禁止事項を強く明示し、ペナルティを課すような指示を追加することも検討します。

  • モデル選択の調整: 特定のモデルで頻繁に形式崩れが発生する場合、別のモデルへの切り替えや、ファインチューニングの検討を行います。

  • 評価基準の洗練: 誤り分析で発見された新たな失敗パターンに対応するため、自動評価のルーブリックや正規表現、スキーマ定義を更新します。

再評価

改良されたプロンプトやシステムは、既存の評価シナリオ(正例、難例、コーナーケース)に加えて、特に改良対象となった失敗ケースを重点的に再評価します。この反復的なプロセスにより、LLMの構造化出力の信頼性と堅牢性を継続的に向上させます。

まとめ

LLMからのJSON/YAML形式制御は、高度な自動化とシステム連携を実現する上で不可欠です。本記事では、入出力契約の定義から、ゼロショット、少数例、Chain-of-Thought制約型プロンプトの設計、そして構文・スキーマ・内容検証を組み合わせた自動評価、さらには失敗モードの分析と抑制手法まで、一連のプロセスを詳細に解説しました。これらの戦略を組み合わせ、継続的な評価と改良のループを回すことで、LLMが常に期待通りの構造化データを出力し、その価値を最大限に引き出すことが可能になります。


プロンプト→モデル→評価→改良のループ

graph LR
    A["要件定義"] --> B("入出力契約定義");
    B --> C["プロンプト設計"];
    C --> D("LLMによる出力生成");
    D --> E["出力結果"];
    E --> F{"評価フェーズ"};
    F -- |検証成功| --> G["目的達成"];
    F -- |検証失敗| --> H["誤り分析"];
    H --> I["プロンプト改良"];
    I --> C;

参考文献 [1] Google Developers. “models.generateContent method”. https://developers.google.com/gemini/docs/reference/rest/v1beta/models/generateContent#response_mime_type (更新日: 2024-05-15, Google Developers) [2] OpenAI Platform. “JSON mode”. https://platform.openai.com/docs/guides/text-generation/json-mode (更新日: 2023-11-06, OpenAI Platform) [3] LlamaIndex Documentation. “JSON Output Parser”. https://docs.llamaindex.ai/en/stable/module_guides/prompting/output_parsing/json_output_parser/ (更新日: 2024-07-20, LlamaIndex Project) [4] LangChain Documentation. “JSON output parser”. https://python.langchain.com/docs/modules/model_io/output_parsers/json (更新日: 2024-07-18, LangChain Project) [5] TechTarget. “What is YAML? Understanding Yet Another Markup Language”. https://www.techtarget.com/whatis/definition/YAML (更新日: 2023-11-20, TechTarget)

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

コメント

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