LLMプロンプトの失敗モード抑制戦略

Tech

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

LLMプロンプトの失敗モード抑制戦略

ユースケース定義

本稿では、カスタマーサポート向けFAQ自動生成システムにおけるLLMのプロンプト設計を想定します。ユーザーからの質問内容(例:「支払い方法について教えてください」)を入力として受け取り、関連するFAQ項目とその回答をJSON形式で出力するシステムです。出力は後続システムで利用されるため、正確性、形式の一貫性、関連性の高さが極めて重要となります。

制約付き仕様化と入出力契約

入力契約

  • フォーマット: ユーザーの質問はプレーンテキストです。

  • 内容: 質問は日本のカスタマーサポートドメインに関するものです。

  • 失敗時の挙動: 無効な入力(非日本語、不適切表現など)の場合、LLMは空のJSONを返却し、error_messageフィールドに適切なエラーメッセージを出力します。

出力契約

  • フォーマット: 以下のJSON形式を厳守します。

    {
      "query": "ユーザーの質問内容",
      "relevant_faqs": [
        {
          "id": "FAQ-001",
          "question": "関連するFAQの質問文",
          "answer": "FAQの回答文"
        },
        {
          "id": "FAQ-002",
          "question": "関連するFAQの質問文",
          "answer": "FAQの回答文"
        }
      ],
      "error_message": "エラーが発生した場合のメッセージ、成功時はnull"
    }
    
  • 失敗時の挙動: JSON形式が崩れた場合、後続システムはパースエラーを返し、デフォルトの回答を表示します。関連性が低い、または幻覚による情報が含まれる場合、ユーザーの信頼を損ないます。

  • 禁止事項: ユーザーの個人情報、機密情報、不適切表現、特定の政治的見解、倫理に反する内容を生成してはなりません。

プロンプト設計

プロンプトはSystem指示とUser指示で構成し、後続のLLMモデル(例:Gemini)が期待する出力を生成するよう誘導します。以下に3つのプロンプト案を示します。

1. ゼロショットプロンプト

基本的な指示のみで、モデルの汎化能力に依存します。

あなたはカスタマーサポートの専門家です。ユーザーからの質問に対し、関連するFAQ項目を2つ抽出し、JSON形式で回答してください。

システムプロンプト:
質問に対するFAQをJSON形式で提供します。
JSON形式:
{
  "query": "ユーザーの質問内容",
  "relevant_faqs": [
    {
      "id": "FAQ-XXX",
      "question": "関連するFAQの質問文",
      "answer": "FAQの回答文"
    }
  ],
  "error_message": "エラーメッセージ、成功時はnull"
}
ユーザーの質問:
支払い方法について教えてください。

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

具体例を複数提示することで、出力形式や期待される内容をより明確にします。

あなたはカスタマーサポートの専門家です。ユーザーからの質問に対し、関連するFAQ項目を2つ抽出し、JSON形式で回答してください。

システムプロンプト:
質問に対するFAQをJSON形式で提供します。
JSON形式:
{
  "query": "ユーザーの質問内容",
  "relevant_faqs": [
    {
      "id": "FAQ-XXX",
      "question": "関連するFAQの質問文",
      "answer": "FAQの回答文"
    }
  ],
  "error_message": "エラーメッセージ、成功時はnull"
}

---
例1:
ユーザーの質問: 配送状況を知りたいです。
モデルの出力:
{
  "query": "配送状況を知りたいです。",
  "relevant_faqs": [
    {
      "id": "FAQ-010",
      "question": "注文した商品の配送状況はどこで確認できますか?",
      "answer": "ご注文履歴ページから、各注文の配送状況をご確認いただけます。追跡番号もそちらに記載されております。"
    },
    {
      "id": "FAQ-011",
      "question": "配送予定日を教えてください。",
      "answer": "通常、ご注文確定から3営業日以内に発送されます。配送先地域により異なりますが、発送から1〜3日でお届けとなります。"
    }
  ],
  "error_message": null
}
---
例2:
ユーザーの質問: 返品は可能ですか?
モデルの出力:
{
  "query": "返品は可能ですか?",
  "relevant_faqs": [
    {
      "id": "FAQ-020",
      "question": "商品の返品・交換はできますか?",
      "answer": "未使用かつ未開封の商品に限り、商品到着後8日以内であれば返品・交換を承ります。詳細はお客様サポートまでお問い合わせください。"
    },
    {
      "id": "FAQ-021",
      "question": "返品時の送料はかかりますか?",
      "answer": "お客様都合による返品の場合、返送料はお客様のご負担となります。商品不良の場合は当社が負担いたします。"
    }
  ],
  "error_message": null
}
---

ユーザーの質問: 支払い方法について教えてください。

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

思考プロセスを明示させ、段階的に推論させることで、複雑な指示や制約の遵守を促します。出力形式の厳格化も図ります。

あなたはカスタマーサポートの専門家です。ユーザーからの質問に対し、以下のステップで関連するFAQ項目を2つ抽出し、JSON形式で回答してください。

システムプロンプト:
以下の思考プロセスを経て、質問に対するFAQをJSON形式で提供します。

- ステップ1: ユーザーの質問の意図を正確に理解する。

- ステップ2: 内部FAQデータベースから、質問意図に最も関連性の高いFAQを2つ検索する。

- ステップ3: 検索結果に基づき、以下のJSON形式で回答を生成する。

- ステップ4: もし質問が不適切であれば、relevant_faqsは空とし、error_messageに適切なメッセージを設定する。

JSON形式の厳守:
{
  "query": "ユーザーの質問内容",
  "relevant_faqs": [
    {
      "id": "FAQ-XXX",
      "question": "関連するFAQの質問文",
      "answer": "FAQの回答文"
    }
  ],
  "error_message": "エラーメッセージ、成功時はnull"
}
IDは必ず「FAQ-」から始まる3桁の数字とし、架空のもので構いません。

ユーザーの質問:
支払い方法について教えてください。

失敗モードと抑制戦略

LLMプロンプトには、いくつかの典型的な失敗モードが存在します。

  1. 幻覚(Hallucination): 事実に基づかない情報を生成する。

    • 抑制手法:

      • System指示による制約: 「事実に基づかない情報は生成しないこと」「内部データのみを参照すること」といった明確な指示をSystemプロンプトに含めます。

      • RAG (Retrieval-Augmented Generation): 外部知識ベース(FAQデータベース)から関連情報を取得し、それをプロンプトに含めてLLMに入力します。これにより、LLMは自身の内部知識だけでなく、提供された情報に基づいて回答を生成するよう促されます。

      • 検証ステップ: 出力されたFAQが実際に存在するか、または与えられた情報源と矛盾しないかを自動で検証します(Source 3: arXiv, 2024-03-10 JST)。

  2. 様式崩れ(Malformed Output): 期待されるJSON形式などを遵守できない。

    • 抑制手法:

      • System指示による厳格なフォーマット指定: JSONスキーマやXMLタグなど、厳密な出力形式をSystemプロンプトで繰り返し強調します(Source 2: Anthropic, 2024-03-28 JST)。

      • 少数例学習: 期待される形式の具体的な出力例を複数提示します(Source 4: OpenAI, 2024-04-01 JST)。

      • リトライ戦略: 出力されたJSONがパースできない場合、エラーメッセージとともに再生成を指示します。

      • JSONモード/Pydantic: 対応するモデルであれば、直接JSONモードを指定したり、Pydanticスキーマをプロンプトに埋め込んで出力形式を強制します(Source 5: LangChain, 2024-04-20 JST)。

  3. 脱線(Off-topic / Irrelevant): 質問の意図と異なる、または関連性の低い内容を生成する。

    • 抑制手法:

      • System指示によるタスク明確化: 「質問の意図にのみ集中し、それ以外の情報を含めないこと」と明示します(Source 1: Google Developers, 2024-04-15 JST)。

      • Chain-of-Thought: 質問意図の理解ステップを含めることで、関連性の高い情報に焦点を当てさせます。

      • 評価指標の明確化: 関連性を評価基準に含め、モデルを最適化します。

  4. 禁止事項違反(Prohibited Content): 不適切、有害、またはポリシーに反する内容を生成する。

    • 抑制手法:

      • System指示による禁止事項の明示: 生成してはならない内容を具体的に列挙します。

      • 安全性フィルタ: LLMの出力の前に、追加の安全性フィルタやモデレーションAPIを適用します。

      • レッドチーミング: 意図的に禁止事項を誘発するプロンプトを試行し、モデルの脆弱性を特定・修正します(Source 6: arXiv, 2024-04-05 JST)。

評価

プロンプトの有効性を検証するため、以下の評価シナリオと自動評価手法を用います。

評価シナリオ

  • 正例: 「支払い方法について教えてください」

    • 期待: JSON形式で支払い方法に関するFAQが2つ返り、error_messagenull
  • 難例: 「商品のパッケージが破損していたのですが、どうすればよいですか?」

    • 期待: JSON形式で返品・交換、またはサポート連絡に関するFAQが2つ返り、error_messagenull
  • コーナーケース: 「宇宙船の燃料補給について教えてください」

    • 期待: relevant_faqsは空リストで、error_messageに「この質問は対象外です」のようなメッセージが返る。

自動評価の擬似コード

import json
import re

def evaluate_llm_output(output_text: str, expected_query: str, domain_faqs: list):
    """
    LLMの出力を評価する擬似コード。
    Args:
        output_text (str): LLMから生成された出力テキスト。
        expected_query (str): ユーザーの質問(入力プロンプトのクエリ)。
        domain_faqs (list): 評価に用いる既知のFAQデータベース(辞書リスト)。
    Returns:
        tuple[int, list[str]]: 評価スコアとフィードバックメッセージのリスト。
    """

    # 採点ルーブリック

    score = 0
    feedback = []

    # 1. JSON形式の検証 (20点)

    try:
        output_json = json.loads(output_text)
        score += 20
    except json.JSONDecodeError:
        feedback.append("JSON形式が不正です。")
        return 0, feedback # JSON形式が不正ならこれ以上評価できない

    # 2. 必須キーの存在検証 (10点)

    required_keys = ["query", "relevant_faqs", "error_message"]
    if all(key in output_json for key in required_keys):
        score += 10
    else:
        feedback.append(f"必須キーが不足しています: {required_keys}")

    # 3. queryの検証 (10点)

    if output_json.get("query") == expected_query:
        score += 10
    else:
        feedback.append(f"queryが期待値と異なります。期待: '{expected_query}', 実際: '{output_json.get('query')}'")

    # 4. relevant_faqsの検証

    faqs = output_json.get("relevant_faqs", [])
    if isinstance(faqs, list):
        score += 10 # relevant_faqsがリストであること (10点)

        # 4.1. FAQ項目数の検証 (5点 or 10点)

        expected_faq_count = 2 # 基本的な期待値
        if expected_query == "宇宙船の燃料補給について教えてください": # コーナーケース
            if len(faqs) == 0 and output_json.get("error_message") is not None:
                score += 10 # エラーメッセージがありFAQが0件ならOK
            else:
                feedback.append("コーナーケースに対するFAQ項目数またはエラーハンドリングが不適切です。")
        elif len(faqs) == expected_faq_count:
            score += 5
        else:
            feedback.append(f"FAQ項目数が期待値と異なります。期待: {expected_faq_count}, 実際: {len(faqs)}")

        # 4.2. 各FAQ項目の構造とID形式検証 (各FAQにつき10点)

        for faq in faqs:
            if all(k in faq for k in ["id", "question", "answer"]):
                score += (5 / len(faqs)) if len(faqs) > 0 else 0 # 構造正しい (配分)
                if re.match(r"^FAQ-\d{3}$", faq["id"]): # IDが"FAQ-XXX"形式か
                    score += (5 / len(faqs)) if len(faqs) > 0 else 0 # ID形式正しい (配分)
                else:
                    feedback.append(f"FAQ IDの形式が不正です: {faq.get('id')}")
            else:
                feedback.append(f"FAQ項目に必要なキーが不足しています: {faq}")

            # 4.3. 幻覚の検出 (簡易版: 質問・回答が既知のFAQリストに含まれるか) (-10点/幻覚)


            # より厳密には、RAGのソースと照合。ここでは簡略化。

            is_hallucination = True
            for d_faq in domain_faqs:
                if d_faq["question"] == faq.get("question") and d_faq["answer"] == faq.get("answer"):
                    is_hallucination = False
                    break
            if is_hallucination and faq.get("question") is not None:
                feedback.append(f"FAQ質問または回答が既知のFAQデータベースにありません (幻覚の可能性): {faq.get('question')}")
                score -= 10 # 幻覚は減点
    else:
        feedback.append("relevant_faqsがリストではありません。")

    # 5. error_messageの検証 (20点)

    if expected_query == "宇宙船の燃料補給について教えてください":
        if output_json.get("relevant_faqs") == [] and output_json.get("error_message") is not None:
            score += 20 # コーナーケースでエラーハンドリングが適切
        else:
            feedback.append("コーナーケースに対するエラーハンドリングが不適切です。")
    elif output_json.get("error_message") is None:
        score += 5 # 通常ケースでエラーがない場合は加点 (5点)
    else:
        feedback.append("不要なエラーメッセージが生成されました。")

    return score, feedback

# 例: 内部FAQデータベース (簡略版 - 実際のID形式はプロンプトに従う)

mock_domain_faqs = [
    {"id": "FAQ-001", "question": "支払い方法について教えてください。", "answer": "クレジットカード、銀行振込、コンビニ決済がご利用いただけます。"},
    {"id": "FAQ-010", "question": "注文した商品の配送状況はどこで確認できますか?", "answer": "ご注文履歴ページから、各注文の配送状況をご確認いただけます。追跡番号もそちらに記載されております。"},
    {"id": "FAQ-011", "question": "配送予定日を教えてください。", "answer": "通常、ご注文確定から3営業日以内に発送されます。配送先地域により異なりますが、発送から1〜3日でお届けとなります。"},
    {"id": "FAQ-020", "question": "商品の返品・交換はできますか?", "answer": "未使用かつ未開封の商品に限り、商品到着後8日以内であれば返品・交換を承ります。詳細はお客様サポートまでお問い合わせください。"},
    {"id": "FAQ-021", "question": "返品時の送料はかかりますか?", "answer": "お客様都合による返品の場合、返送料はお客様のご負担となります。商品不良の場合は当社が負担いたします。"},
    {"id": "FAQ-030", "question": "商品のパッケージが破損していたのですが、どうすればよいですか?", "answer": "お手数ですが、商品到着後速やかにお客様サポートまでご連絡ください。代替品の発送または返品・交換を承ります。"}
]

# この評価関数は、生成された出力(`output_text`)がJSON形式であること、


# 必須フィールドが含まれていること、`relevant_faqs`の項目数と形式、


# `id`の正規表現マッチ、そして簡略的な幻覚チェックを行います。


# 計算量(Big-O): JSONパースはO(N) (Nは出力サイズ)。正規表現マッチはパターン長と文字列長に依存。


# リスト内包表記や検索は最悪O(K*M) (KはFAQ数、Mは各FAQの文字列長) となるが、通常は高速。


# メモリ条件: 出力テキストと内部FAQデータベースのサイズに依存します。

誤り分析と改良ループ

評価結果に基づき、以下のサイクルでプロンプトを改良します。

  1. 誤り分類:

    • 幻覚: 生成されたFAQが既存のFAQと一致しない、または事実と異なる。

    • 様式崩れ: JSONパースエラー、必須キーの欠落、ID形式の不一致など。

    • 脱線: 質問と関連性の低いFAQが生成される。

    • 禁止事項違反: 不適切コンテンツの生成。

  2. 根本原因分析:

    • 幻覚: RAGの失敗、LLMがプロンプトの制約を無視した、参照情報が不十分。

    • 様式崩れ: 指示が不明確、例が不足、複雑な形式をLLMが処理しきれない。

    • 脱線: 質問の意図を正確に捉えられていない、曖昧な指示。

    • 禁止事項違反: システムプロンプトの指示が不十分、モデルのファインチューニング不足。

  3. 改良戦略:

    • Systemプロンプトの強化: より具体的で厳格な指示を追加。「必ず〇〇形式で出力」「参照情報以外の生成禁止」。

    • Few-shot例の追加・修正: 失敗ケースをカバーする例や、より複雑な構造を持つ例を追加。

    • Chain-of-Thought導入: 推論ステップを強制し、思考経路を明確化。

    • RAGの実装: 質問に関連するFAQデータを事前に取得し、プロンプトに埋め込む。

    • 後処理フィルタ: LLM出力後に、正規表現やキーワードマッチで不適切コンテンツや様式崩れを検出し、修正または再生成を促す。

    • モデルのファインチューニング: 大量の高品質な入出力ペアでモデルを再学習させ、特定のタスクに特化させる。

再評価

改良したプロンプトは、再び上記の評価シナリオと自動評価スクリプトを用いて評価されます。これにより、改良が効果的であったか、新たな失敗モードが発生していないかを確認します。このプロセスを繰り返すことで、プロンプトの堅牢性を高めます。

プロンプト改良のループ

プロンプト改良のプロセスは、以下のフローチャートで可視化できます。

graph TD
    A["プロンプト設計"] --> |プロンプト入力| B{"LLMモデル"};
    B -- |生成出力| C["評価ツール"];
    C -- |評価結果| D{"誤り分析"};
    D -- |失敗モード・原因| E["改良戦略立案"];
    E -- |改良プロンプト・データ| A;
    C -- |合格| F["本番導入"];

まとめ

LLMプロンプトの設計においては、ユースケースと入出力契約を明確に定義し、幻覚、様式崩れ、脱線、禁止事項違反といった失敗モードを予期することが重要です。これらの失敗モードに対して、System指示の強化、Few-shot学習、Chain-of-Thought、RAGなどの多様な抑制戦略を適用します。さらに、正例、難例、コーナーケースを網羅する評価シナリオと自動評価ツールを組み合わせ、継続的な誤り分析とプロンプトの改良を行うことで、堅牢で信頼性の高いLLMアプリケーションを実現できます。この反復的なプロセスこそが、高品質なLLMプロンプトを構築するための鍵となります。

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

コメント

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