プロンプト評価と改善ループ: LLMの性能最大化戦略

Tech

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

プロンプト評価と改善ループ: LLMの性能最大化戦略

大規模言語モデル(LLM)の性能を最大限に引き出すためには、単にプロンプトを作成するだけでなく、その効果を体系的に評価し、継続的に改善していく「プロンプト評価と改善ループ」を確立することが不可欠です。本記事では、このループを構成する各要素について、具体的な手法とともに詳細に解説します。

ユースケース定義

本稿では、カスタマーサポートにおけるFAQ応答システムを例に、ユーザーからの自然言語による問い合わせに対し、LLMが社内ドキュメントや過去のQ&Aデータから正確かつ簡潔な回答を生成するユースケースを想定します。目標は、回答の正確性関連性を最大化し、ユーザーの課題解決を支援することです。

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

LLMとのやり取りにおいて、期待される入出力形式や失敗時の挙動を明確に定義することは、評価と改善の基盤となります。

入力契約

  • フォーマット: JSON形式。

  • 必須キー:

    • query (string): ユーザーからの質問テキスト。最大長200文字。

    • context (string): 回答生成に利用する情報源テキスト。最大長2000文字。

  • :

    {
      "query": "製品Aの保証期間について教えてください。",
      "context": "製品Aの保証期間は購入日から1年間です。保証対象外の損傷については有償修理となります。"
    }
    

出力契約

  • フォーマット: JSON形式。

  • 必須キー:

    • answer (string): 生成された回答テキスト。簡潔で、コンテキストに基づいていること。

    • confidence_score (float): 回答の信頼度(0.0〜1.0)。

  • :

    {
      "answer": "製品Aの保証期間は購入日から1年間です。",
      "confidence_score": 0.95
    }
    

失敗時の挙動

  • フォーマット: JSON形式。

  • 必須キー:

    • error (string): エラータイプ(例: “InsufficientContext”, “InputFormatError”)。

    • message (string): エラー詳細。

  • :

    {
      "error": "InsufficientContext",
      "message": "提供されたコンテキスト情報だけでは、質問に回答できません。"
    }
    

禁止事項

  • 政治的、差別的、または倫理的に問題のある内容の生成。

  • 個人を特定できる情報(PII)の漏洩。

  • 事実に基づかない誤情報の生成(幻覚)。

  • 指定されたJSONフォーマットからの逸脱。

プロンプト設計

以下に、FAQ応答システムにおける3種類のプロンプト案を示します。

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

モデルに具体的な例を与えず、一般的な指示のみでタスクを実行させます。

あなたはカスタマーサポートのAIアシスタントです。
以下の「情報源」に基づいて、「質問」に簡潔かつ正確に回答してください。
情報源に回答がない場合は、「情報源に回答がありません。」と回答してください。

情報源:
{{context}}

質問:
{{query}}

回答:

2. 少数例学習(Few-shot Learning)プロンプト

タスクの例をいくつか提示し、モデルがそのパターンを学習して新しい入力に適用できるようにします。

あなたはカスタマーサポートのAIアシスタントです。
以下の「情報源」に基づいて、「質問」に簡潔かつ正確に回答してください。
情報源に回答がない場合は、「情報源に回答がありません。」と回答してください。

### 例1

情報源: 製品Xの充電時間は2時間です。連続使用時間は約10時間です。
質問: 製品Xの充電時間は?
回答: 製品Xの充電時間は2時間です。

### 例2

情報源: 返品は購入後30日以内であれば可能です。未開封品に限ります。
質問: 製品Yの修理について教えてください。
回答: 情報源に回答がありません。

### 現在のタスク

情報源:
{{context}}

質問:
{{query}}

回答:

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

モデルに思考プロセスを段階的に出力させることで、複雑な推論を促し、回答の正確性を向上させます。特定の思考ステップを強制することで、出力の信頼性を高めます。

あなたはカスタマーサポートのAIアシスタントです。
以下の手順に従って、「情報源」に基づいて「質問」に簡潔かつ正確に回答してください。
情報源に回答がない場合は、「情報源に回答がありません。」と回答してください。

### 手順


1. 「情報源」から「質問」に直接関連する箇所を特定し、抽出する。

2. 抽出した情報から「質問」への直接的な回答を作成する。

3. 作成した回答が「情報源」に完全に含まれているか確認する。含まれていない場合は、「情報源に回答がありません。」と判断する。

情報源:
{{context}}

質問:
{{query}}

思考プロセス:

1. 情報源から質問に関連する箇所を特定します。

2. その情報に基づいて回答を作成します。

3. 回答が情報源に基づいているか検証します。
回答:

評価フレームワーク

プロンプトの性能を客観的に測定するための評価シナリオと自動評価の擬似コードを定義します。

評価シナリオ

  • 正例: 明確な質問で、回答が情報源内に明示されているケース。

    • 例: 「製品Aの保証期間は?」

    • コンテキスト: 「製品Aの保証期間は購入日から1年間です。」

    • 期待される回答: 「製品Aの保証期間は購入日から1年間です。」

  • 難例: 質問が曖昧である、または情報源が限定的で複数の解釈が可能なケース。

    • 例: 「製品Aはいつから使えますか?」

    • コンテキスト: 「製品Aは {{jst_today}} に発売されました。」

    • 期待される回答: 「情報源に回答がありません。」または「製品Aは {{jst_today}} に発売されましたが、使用開始日については情報源に記載がありません。」

  • コーナーケース:

    • 情報源に矛盾する情報が含まれるケース。

    • 質問が情報源と全く無関係なケース。

    • 情報源が空の場合。

自動評価の擬似コード

採点ルーブリックに基づき、Pythonのような言語で自動評価関数を実装します。

import re

def evaluate_response(llm_output: dict, expected_answer: str, context: str, criteria: dict) -> dict:
    """
    LLMの回答を自動評価する擬似コード。

    Args:
        llm_output (dict): LLMから返されたJSON形式の出力。
        expected_answer (str): 期待される正解テキスト。
        context (str): 回答生成に利用した情報源。
        criteria (dict): 評価基準と重み付け。

    Returns:
        dict: 評価結果(スコア、各項目評価)。
    """
    score = 0
    feedback = {}

    # 1. JSONフォーマットの遵守 (20点)

    if "answer" in llm_output and "confidence_score" in llm_output:
        score += criteria["json_format"]
        feedback["json_format"] = True
    else:
        feedback["json_format"] = False
        return {"score": 0, "feedback": feedback} # フォーマット崩れは致命的

    answer = llm_output.get("answer", "")
    confidence = llm_output.get("confidence_score", 0.0)

    # 2. 回答の正確性 (40点) - 正規表現または類似度で評価

    if expected_answer == "情報源に回答がありません。":
        if answer == expected_answer:
            score += criteria["accuracy"]
            feedback["accuracy"] = "Correctly identified no answer."
        else:
            feedback["accuracy"] = f"Expected no answer, but got: {answer}"
    elif re.search(re.escape(expected_answer), answer, re.IGNORECASE):
        score += criteria["accuracy"]
        feedback["accuracy"] = "Highly accurate."
    elif any(word in answer for word in expected_answer.split()): # 部分一致
        score += criteria["accuracy"] * 0.5
        feedback["accuracy"] = "Partially accurate."
    else:
        feedback["accuracy"] = "Incorrect."

    # 3. コンテキストからの逸脱(幻覚)の有無 (20点)


    # 回答がcontextに含まれるキーワードで構成されているか、新しい情報を含んでいないか

    is_hallucinating = False
    if expected_answer != "情報源に回答がありません。":
        if not all(word in context for word in answer.split() if len(word) > 2): # 簡略化されたチェック

            # より高度なチェックにはEmbeddingベースの類似度などが必要


            # is_hallucinating = check_hallucination_with_embedding(answer, context)

            pass # Placeholder for more advanced hallucination check

    if not is_hallucinating:
        score += criteria["context_adherence"]
        feedback["context_adherence"] = "No obvious hallucination."
    else:
        feedback["context_adherence"] = "Potential hallucination detected."

    # 4. 回答の簡潔性 (10点) - 文字数や冗長性

    if len(answer) <= criteria["max_length"] and len(answer) > 0:
        score += criteria["conciseness"]
        feedback["conciseness"] = "Concise."
    else:
        feedback["conciseness"] = "Too long or empty."

    # 5. 信頼度スコアの妥当性 (10点) - 正確な回答には高い、不正確な回答には低いか

    if (feedback["accuracy"].startswith("Highly accurate") and confidence >= 0.8) or \
       (feedback["accuracy"].startswith("Incorrect") and confidence < 0.5):
        score += criteria["confidence_validity"]
        feedback["confidence_validity"] = "Confidence score is reasonable."
    else:
        feedback["confidence_validity"] = "Confidence score might be misleading."

    total_score = round(score / sum(criteria.values()) * 100, 2)
    return {"total_score": total_score, "feedback": feedback, "llm_output": llm_output}

# 採点ルーブリック例

evaluation_criteria = {
    "json_format": 20,
    "accuracy": 40,
    "context_adherence": 20,
    "conciseness": 10,
    "confidence_validity": 10,
    "max_length": 150 # 簡潔性のための最大文字数
}

# --- 評価実行例 ---


# llm_response_ok = {"answer": "製品Aの保証期間は購入日から1年間です。", "confidence_score": 0.95}


# expected_ok = "製品Aの保証期間は購入日から1年間です。"


# context_ok = "製品Aの保証期間は購入日から1年間です。保証対象外の損傷については有償修理となります。"


# print(evaluate_response(llm_response_ok, expected_ok, context_ok, evaluation_criteria))

# llm_response_fail = {"answer": "製品Aの保証期間は2年間です。", "confidence_score": 0.7} # 誤情報


# print(evaluate_response(llm_response_fail, expected_ok, context_ok, evaluation_criteria))

# llm_response_no_answer = {"answer": "情報源に回答がありません。", "confidence_score": 0.8}


# expected_no_answer = "情報源に回答がありません。"


# context_no_answer = "製品Aは高性能ですが、保証期間は記載されていません。"


# print(evaluate_response(llm_response_no_answer, expected_no_answer, context_no_answer, evaluation_criteria))

誤り分析と失敗モード

評価結果に基づき、LLMが陥りやすい失敗パターンを特定し、その抑制手法を検討します。

失敗モードの例

  1. 幻覚(Hallucination): 情報源に存在しない情報を生成する。

    • 例: 情報源にない機能を製品Aに追加して回答する。
  2. 様式崩れ(Format Deviation): 定義された出力JSON形式を遵守しない。

    • 例: JSONではなくプレーンテキストで回答を返す、キー名が異なる。
  3. 脱線(Topic Drifting): 質問やコンテキストから逸脱した内容を生成する。

    • 例: 製品Aの質問に対し、競合製品Bの情報を混ぜて回答する。
  4. 禁止事項違反: 個人情報や不適切な内容を生成する。

    • 例: ユーザーの問い合わせから個人情報を推測し、回答に含める。

抑制手法

  1. System指示の強化: プロンプトのSystem部分で、LLMの役割、制約、禁止事項を具体的に指示します。

    • 例: 「あなたは情報源からのみ回答を生成してください。それ以外の情報を追加することは厳禁です。」
  2. 出力検証ステップ: LLMの出力後、別途ロジックでJSONスキーマ検証、キーワードチェック、安全フィルターを適用します。

    • 例: 生成されたJSONのanswerフィールドが空でないか、confidence_scoreが0-1の範囲内かなどを確認。
  3. リトライ戦略: 出力検証でエラーが検出された場合、エラーメッセージとともにプロンプトを再投入し、修正を促します。

    • 例: 「出力がJSON形式ではありません。正しいJSON形式で再度回答してください。」
  4. CoTの活用: モデルに思考プロセスを明示させ、情報源に基づいているかを自己検証させることで幻覚を抑制します。

改良と再評価

誤り分析で特定された問題点に基づき、プロンプトを改良します。

  • プロンプトの具体化: 曖昧な指示を明確にする。

  • 例の追加/改善: 少数例学習において、難例やコーナーケースの例を追加する。

  • CoTステップの調整: 思考プロセスをより細分化したり、特定の制約を追加したりする。

改良後、再度評価シナリオを用いて性能を測定し、初期プロンプトや以前の改良版と比較して改善が認められるかを確認します。このプロセスを繰り返すことで、プロンプトの品質を継続的に向上させます。

プロンプト改善ループの可視化

プロンプト評価と改善のサイクルをフローチャートで示します。

graph TD
    ID1["初期プロンプト設計"] --> ID2{"モデルによる回答生成"};
    ID2 --> ID3["回答の評価"];
    ID3 -- |誤り特定| --> ID4{"誤り分析"};
    ID4 -- |改善策立案| --> ID5["プロンプト改良"];
    ID5 -- |再評価| --> ID2;
    ID3 -- |成功| --> ID6["プロンプト完成"];
    style ID1 fill:#f9f,stroke:#333,stroke-width:2px
    style ID6 fill:#9f9,stroke:#333,stroke-width:2px

まとめ

LLMのプロンプトエンジニアリングは、一度きりの作業ではありません。本記事で解説したように、ユースケース定義、入出力契約の明確化、多様なプロンプト設計、体系的な評価、そして継続的な誤り分析と改良を通じて、プロンプト評価と改善のループを回すことが、LLMアプリケーションの性能と信頼性を最大化する鍵となります。この反復的なプロセスにより、開発者はより堅牢でユーザーフレンドリーなLLMシステムを構築できるでしょう。

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

コメント

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