LLMの失敗モード抑制と評価指標:プロンプト工学による堅牢化

Tech

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

LLMの失敗モード抑制と評価指標:プロンプト工学による堅牢化

大規模言語モデル(LLM)のアプリケーション開発において、モデルの性能を最大化し、予測可能な挙動を実現するためには、プロンプト設計と評価の体系的なアプローチが不可欠です。本記事では、LLMの主要な失敗モードを抑制し、信頼性の高い評価指標を用いるためのプロンプト工学の手法について解説します。

1. ユースケース定義

本稿では、カスタマーサポートシステムにおける、顧客からの問い合わせ(フリーテキスト)を分析し、以下の情報をJSON形式で抽出するLLMアプリケーションを想定します。

  • 要約: 問い合わせ内容を簡潔に200字以内で要約。

  • 感情: 問い合わせの感情(Positive/Negative/Neutral)。

  • カテゴリ: 問い合わせのカテゴリ(製品不具合/請求関連/機能要望/その他)。

  • 緊急度: 問い合わせの緊急度(High/Medium/Low)。

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

LLMの出力がユースケースの要件を満たすよう、厳密な入出力契約を定義します。

入力契約

  • フォーマット: フリーテキスト。最大2000文字。

  • 前提: 日本語の自然言語。個人を特定できる情報(PII)は含まれないものとする。

出力契約

  • フォーマット: 以下のスキーマに厳密に従うJSON形式。

    {
      "summary": "string",  // 問い合わせの要約 (200文字以内)
      "sentiment": "string",// 感情 ("Positive", "Negative", "Neutral" のいずれか)
      "category": "string", // カテゴリ ("製品不具合", "請求関連", "機能要望", "その他" のいずれか)
      "urgency": "string"   // 緊急度 ("High", "Medium", "Low" のいずれか)
    }
    
  • 失敗時の挙動:

    • 出力がJSON形式でない、またはスキーマに準拠しない場合、以下のエラーJSONを返却する。

      {"error": "Invalid output format or schema violation.", "details": "string"}
      
    • 要約が200文字を超える、sentiment/category/urgencyが定義済みの値以外のものとなる場合も、上記のエラーJSONを返却する。

    • 問い合わせ内容が不明瞭で分析不能な場合も、エラーJSONを返却する。

  • 禁止事項:

    • 顧客の個人情報(氏名、電話番号、メールアドレスなど)を抽出または生成しない。

    • 事実と異なる内容(幻覚)を要約に含めない。

    • 指示されたJSON以外の余計な説明やプロンプトへの返答を含めない。

3. プロンプト設計

要求される出力品質を達成するため、プロンプト工学の異なる手法を適用します。

プロンプト案1: ゼロショットプロンプト

基本的な指示のみを含む最もシンプルな形式です。

  • 参考情報: Google AI Blog: “Best practices for prompt engineering with Gemini API” (2024年2月13日公開) [1]
あなたはカスタマーサポートの問い合わせ分析AIです。
以下の顧客からの問い合わせを分析し、要約、感情、カテゴリ、緊急度をJSON形式で出力してください。
要約は200文字以内とし、感情はPositive/Negative/Neutral、カテゴリは製品不具合/請求関連/機能要望/その他、緊急度はHigh/Medium/Lowのいずれかを選択してください。
JSON以外の出力は一切禁止します。

問い合わせ:
{{問い合わせ内容}}

プロンプト案2: 少数例(Few-shot)プロンプト

具体的な入出力例を提示することで、モデルに期待する挙動を示します。

  • 参考情報: Google AI Blog: “Best practices for prompt engineering with Gemini API” (2024年2月13日公開) [1]
あなたはカスタマーサポートの問い合わせ分析AIです。
以下の顧客からの問い合わせを分析し、要約、感情、カテゴリ、緊急度をJSON形式で出力してください。
要約は200文字以内とし、感情はPositive/Negative/Neutral、カテゴリは製品不具合/請求関連/機能要望/その他、緊急度はHigh/Medium/Lowのいずれかを選択してください。
JSON以外の出力は一切禁止します。

### 例1

問い合わせ:
先日購入した掃除機が全く動きません。電源を入れても何の反応もありません。すぐに交換してほしいです。
出力:
```json
{
  "summary": "購入した掃除機が起動しないため、交換を希望する問い合わせ。",
  "sentiment": "Negative",
  "category": "製品不具合",
  "urgency": "High"
}

例2

問い合わせ: 先月の利用料金について確認したいのですが、請求書の内容に不明な点があります。詳細を教えてください。 出力:

{
  "summary": "先月の利用料金に関する請求書内容の確認依頼。",
  "sentiment": "Neutral",
  "category": "請求関連",
  "urgency": "Medium"
}

問い合わせ: {{問い合わせ内容}}

### プロンプト案3: Chain-of-Thought(CoT)制約型プロンプト

モデルに思考プロセスを段階的に出力させ、最終的な結論を導かせることで、より正確で堅牢な出力を促します。さらに、思考プロセスと最終出力の間に明確な区切りを設けます。

-   **参考情報**: Wei et al. "Chain-of-Thought Prompting Elicits Reasoning in Large Language Models." arXiv (2022年1月28日公開) \[2]

```text
あなたはカスタマーサポートの問い合わせ分析AIです。
以下の顧客からの問い合わせを分析し、要約、感情、カテゴリ、緊急度をJSON形式で出力してください。
思考ステップを明確にし、その後に最終的なJSON出力を提示してください。

思考ステップの制約:

1. まず、問い合わせ内容を簡潔に理解し、「要約ポイント」を箇条書きで抽出してください。

2. 次に、要約ポイントに基づき、感情、カテゴリ、緊急度をそれぞれ判断する「判断理由」を具体的に記述してください。

3. 最終的に、判断結果をJSON形式で出力してください。

出力JSONの制約:

- 要約は「要約ポイント」を基に200文字以内。

- 感情はPositive/Negative/Neutralのいずれか。

- カテゴリは製品不具合/請求関連/機能要望/その他いずれか。

- 緊急度はHigh/Medium/Lowのいずれか。

- JSON以外の余計な説明や思考ステップ以外の出力を一切禁止します。

問い合わせ:
{{問い合わせ内容}}

---
思考ステップ:

1. 要約ポイント:
-

2. 判断理由:

- 感情:

- カテゴリ:

- 緊急度:

---
最終出力JSON:
```json
{
  "summary": "string",
  "sentiment": "string",
  "category": "string",
  "urgency": "string"
}
## 4. 評価

プロンプト設計の成果を客観的に測るため、評価シナリオと自動評価手法を確立します。

-   **参考情報**: Google Cloud Blog: "Evaluating LLMs: A comprehensive guide" (2023年3月28日公開) \[3]

### 評価シナリオ


-   **正例**: 指示通りに処理可能な明確で標準的な問い合わせ。

    -   例: 「最近購入したスマートフォンの充電がすぐになくなります。不良品でしょうか。交換してください。」

-   **難例**: 曖昧、情報不足、複数の要求が含まれる複雑な問い合わせ。

    -   例: 「あの件について。以前から言っているが、どうにかしてほしい。もう待てない。」

-   **コーナーケース**: 意図的に失敗を誘発する、または禁止事項に触れる可能性のある問い合わせ。

    -   例1 (禁止事項): 「私の名前は山田太郎で、住所は東京都...です。請求書の間違いについて。」

    -   例2 (幻覚誘発): 「未来の製品『ホログラフィックスピーカーX』の不具合について。」

    -   例3 (様式崩れ): 「JSON形式で出力する必要はありません。ただの感想です。」

### 自動評価の擬似コード

Pythonを用いた自動評価の擬似コードを以下に示します。

```python
import json
import re

def evaluate_llm_output(output_text: str, expected_sentiment: str, expected_category: str, expected_urgency: str, expected_summary_keywords: list) -> dict:
    """
    LLMの出力を自動評価する関数。
    :param output_text: LLMが出力した文字列
    :param expected_sentiment: 期待される感情 ('Positive', 'Negative', 'Neutral')
    :param expected_category: 期待されるカテゴリ
    :param expected_urgency: 期待される緊急度
    :param expected_summary_keywords: 要約に含めるべきキーワードリスト
    :return: 評価結果とスコア
    """
    score = 0
    feedback = []

    # 1. JSON形式の検証

    try:

        # 入力: output_text (string)


        # 出力: parsed_json (dict)


        # 前提: output_textがJSON形式の文字列であること


        # 計算量: 文字列長に比例 (O(N))


        # メモリ条件: 文字列長に比例

        parsed_json = json.loads(output_text)
        score += 10
        feedback.append("JSON形式: OK")
    except json.JSONDecodeError:
        feedback.append("JSON形式: NG - JSONパースエラー")
        return {"score": score, "feedback": feedback, "details": "JSONDecodeError"}

    # 2. スキーマと値の検証

    required_keys = ["summary", "sentiment", "category", "urgency"]
    valid_sentiment = ["Positive", "Negative", "Neutral"]
    valid_category = ["製品不具合", "請求関連", "機能要望", "その他"]
    valid_urgency = ["High", "Medium", "Low"]

    # 入力: parsed_json (dict), required_keys (list)


    # 出力: True/False


    # 前提: parsed_jsonがdictであること


    # 計算量: キー数に比例 (O(K))


    # メモリ条件: 固定

    if all(key in parsed_json for key in required_keys):
        score += 5
        feedback.append("必須キー: OK")
    else:
        feedback.append(f"必須キー: NG - 欠落キー: {', '.join(set(required_keys) - set(parsed_json.keys()))}")
        return {"score": score, "feedback": feedback, "details": "Missing keys"}

    # 要約の文字数検証

    summary = parsed_json.get("summary", "")
    if 1 <= len(summary) <= 200:
        score += 5
        feedback.append("要約文字数: OK")
    else:
        feedback.append(f"要約文字数: NG - {len(summary)}文字 (200文字以内)")

    # 感情の検証

    sentiment = parsed_json.get("sentiment")
    if sentiment in valid_sentiment:
        score += 5
        feedback.append("感情値: OK")
        if sentiment == expected_sentiment:
            score += 10
            feedback.append("感情値一致: OK")
        else:
            feedback.append(f"感情値一致: NG - 期待値:{expected_sentiment}, 実際:{sentiment}")
    else:
        feedback.append(f"感情値: NG - 無効な値:{sentiment}")

    # カテゴリの検証

    category = parsed_json.get("category")
    if category in valid_category:
        score += 5
        feedback.append("カテゴリ値: OK")
        if category == expected_category:
            score += 10
            feedback.append("カテゴリ値一致: OK")
        else:
            feedback.append(f"カテゴリ値一致: NG - 期待値:{expected_category}, 実際:{category}")
    else:
        feedback.append(f"カテゴリ値: NG - 無効な値:{category}")

    # 緊急度の検証

    urgency = parsed_json.get("urgency")
    if urgency in valid_urgency:
        score += 5
        feedback.append("緊急度値: OK")
        if urgency == expected_urgency:
            score += 10
            feedback.append("緊急度値一致: OK")
        else:
            feedback.append(f"緊急度値一致: NG - 期待値:{expected_urgency}, 実際:{urgency}")
    else:
        feedback.append(f"緊急度値: NG - 無効な値:{urgency}")

    # 要約のキーワード検証 (コンテンツ評価)


    # 入力: summary (string), expected_summary_keywords (list)


    # 出力: True/False


    # 前提: summaryが文字列であること


    # 計算量: summary長とキーワード数に比例 (O(N*K))


    # メモリ条件: 固定

    keywords_found = [kw for kw in expected_summary_keywords if kw in summary]
    if len(keywords_found) == len(expected_summary_keywords):
        score += 20
        feedback.append("要約キーワード: OK")
    else:
        feedback.append(f"要約キーワード: NG - 欠落:{set(expected_summary_keywords) - set(keywords_found)}")

    # 禁止事項チェック (例: 個人情報)


    # 入力: summary (string)


    # 出力: True/False


    # 前提: summaryが文字列であること


    # 計算量: summary長に比例 (O(N))


    # メモリ条件: 固定

    pii_patterns = [r'\d{3}-\d{4}-\d{4}', r'\w+@\w+\.\w+'] # 電話番号、メールアドレスなど
    for pattern in pii_patterns:
        if re.search(pattern, summary):
            score -= 50 # 重大な減点
            feedback.append("禁止事項: NG - PIIが含まれる可能性があります")
            break

    return {"score": score, "feedback": feedback, "details": parsed_json}

# 使用例


# output = '{"summary": "購入した掃除機が起動しないため、交換を希望する問い合わせ。", "sentiment": "Negative", "category": "製品不具合", "urgency": "High"}'


# result = evaluate_llm_output(output, "Negative", "製品不具合", "High", ["掃除機", "起動しない", "交換"])


# print(result)

5. 誤り分析

評価結果に基づき、以下の観点で誤り分析を行います。

  • 幻覚(Hallucination): 事実に基づかない情報を生成していないか。

    • 例: 問い合わせ内容にない製品名や機能を要約に含める。
  • 様式崩れ(Format Break): JSON形式やスキーマに準拠しない出力をしていないか。

    • 例: {が欠落、"sentiment": "Bad"のように不正な値を出力。
  • 脱線(Off-topic): 指示されたタスク以外の内容(例: 雑談、関連性のない情報)を生成していないか。

    • 例: 「この問い合わせは重要ですね。」のようなメタコメント。
  • 禁止事項違反(Safety Violation): 個人情報漏洩、ヘイトスピーチ、不適切な内容の生成をしていないか。

    • 例: 問い合わせに含まれる可能性のあるPIIをそのまま出力。
  • 意味的誤り: 感情、カテゴリ、緊急度の判断が文脈と異なっていないか。

    • 例: 「助けてくれてありがとう」をNegativeと判断。
  • 冗長性/簡潔性: 要約が長すぎる、または短すぎる。

6. 改良

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

  1. プロンプトの具体化: 曖昧な指示を具体化。特にCoTプロンプトで思考プロセスをより細かく指定する。

  2. Few-shot例の追加・改善: 失敗モードに対応する難例やコーナーケースのFew-shot例を追加する。

  3. システム指示の強化: 禁止事項や失敗時の挙動(エラーJSONの出力)をSystem Instructionで明確に指示する。

  4. 出力検証ステップの追加: LLMの出力後に、自動評価で用いたチェック機構を本番環境で実装し、不正な出力をリトライまたはエラーとして処理する。

  5. リトライ戦略: 特定の失敗モード(JSONパースエラー、スキーマ違反など)に対して、プロンプトを修正して再試行するロジックを実装する。

7. 再評価

改良したプロンプトやシステム設計を、再度多様な評価シナリオでテストし、期待される品質基準を満たすかを確認します。このフィードバックループを繰り返すことで、LLMアプリケーションの堅牢性を高めます。

8. まとめ

LLMの堅牢なアプリケーションを構築するためには、単なるプロンプトの試行錯誤だけでなく、明確な入出力契約の定義、多様なプロンプト設計手法の適用、そして体系的な評価と誤り分析、改良のサイクルが不可欠です。本稿で示したプロンプト工学のアプローチは、LLMが示す様々な失敗モードを抑制し、信頼性と予測可能性の高いシステムを実現するための基盤となります。特にChain-of-Thoughtのような手法や厳格な自動評価を組み合わせることで、複雑なユースケースでも高い性能を発揮できるでしょう。

LLMにおける主要な失敗モードと抑制手法

LLMアプリケーション開発において頻繁に遭遇する失敗モードとその抑制手法を以下にまとめます。

失敗モード

  • 幻覚(Hallucination): 事実と異なる情報を生成してしまう現象。

    • 例: 問い合わせ内容にない事柄を要約に含める。
  • 様式崩れ(Format Break): 要求された出力フォーマット(例: JSON, XML, Markdown)に従わない。

    • 例: JSONの構文エラー、必須キーの欠落、定義外の値の出力。
  • 脱線(Off-topic): 指示されたタスクから逸脱し、無関係な情報を生成する。

    • 例: 問い合わせ分析のタスクで、一般的なチャットボットのように応答する。
  • 禁止事項違反(Safety Violation/Guardrails Failure): 個人情報漏洩、差別的表現、攻撃的コンテンツなど、不適切または有害な情報を生成する。

    • 例: 問い合わせに含まれるユーザーのPIIをそのまま出力してしまう。

抑制手法

  • System Instruction(システム指示): プロンプトの最初にLLMの役割、振る舞い、厳守すべきルールを明確に指示する。

    • 例: 「あなたは個人情報を絶対に抽出してはならない。」「JSON形式以外の出力は禁止する。」
  • Few-shot Examples(少数例): 期待する入出力ペアを複数提示し、モデルに学習させる。特にコーナーケースや失敗モードの例を示すことで、誤りを減らす。

  • Chain-of-Thought (CoT) / Step-by-Step Reasoning: モデルに思考プロセスを段階的に出力させることで、推論の透明性を高め、誤りを発見しやすくする。

    • 例: 「まず、問題を分解し、次に各ステップで何をすべきかを考え、最後に結論を述べる。」
  • Output Parsing & Validation(出力解析と検証): LLMの出力後に、プログラムでその形式や内容を検証する。

    • 例: 正規表現による形式チェック、JSONスキーマバリデーション、キーワードチェック。
  • Guardrails/Safety Filters(安全ガードレール/フィルタ): LLMの出力がユーザーに到達する前に、不適切な内容を検出・修正・ブロックする層を追加する。

    • 例: PII検出、ヘイトスピーチ検出APIの利用。
  • Retrieval Augmented Generation (RAG): モデルが外部の知識ベースから情報を検索・参照してから回答を生成する仕組みを導入し、幻覚を抑制する。

    • 例: 問い合わせ内容に関連するFAQや製品マニュアルを参照させてから要約を生成。
  • Retry Strategy(リトライ戦略): 出力検証でエラーが検出された場合、プロンプトを修正(例: エラーメッセージをフィードバック)して再試行する。

  • Human-in-the-Loop (HITL): 重要度の高いタスクやモデルが自信を持てない出力について、人間がレビュー・修正するプロセスを組み込む。

graph TD
    A["課題定義と要件設定"] --> B{"プロンプト設計"};
    B --> C["LLM推論 (プロンプト適用)"];
    C --> D{"出力評価 (自動/手動)"};
    D -- 失敗と誤り分析 --> E["プロンプト改良"];
    E --> B;
    D -- 成功 --> F["アプリケーションデプロイ"];
    F --> G["監視と継続的改善"];
    G --> B;

    subgraph 失敗モードと抑制
        H["幻覚"] --> J[RAG|Fact-checking];
        I["様式崩れ"] --> K["System Instruction|Output Validation"];
        L["脱線"] --> M["CoT|厳密なタスク指示"];
        N["禁止事項違反"] --> O["Guardrails|Safety Filters"];
    end

    subgraph 評価シナリオ
        P["正例"] --> Q["基本機能確認"];
        R["難例"] --> S["堅牢性テスト"];
        T["コーナーケース"] --> U["失敗モード誘発"];
    end
ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

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