RAGプロンプト最適化戦略:設計、評価、改良の反復アプローチ

Tech

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

RAGプロンプト最適化戦略:設計、評価、改良の反復アプローチ

ユースケース定義

本稿では、RAG(Retrieval Augmented Generation)システムにおけるプロンプト最適化戦略に焦点を当てます。具体的には、ユーザーからの自然言語クエリと、外部データベースから取得された関連文書(コンテキスト)を入力として、LLMが正確かつ信頼性の高い回答を生成するシナリオを対象とします。目的は、RAGシステムが幻覚(Hallucination)を抑制し、提供されたコンテキストのみに基づいて、ユーザーの意図に沿った回答を生成する能力を最大化することです。

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

入力契約

  • フォーマット: JSONオブジェクト。

    {
      "query": "ユーザーからの質問文",
      "context": [
        "関連文書1の内容",
        "関連文書2の内容",
        "..."
      ],
      "language": "回答を生成する言語(例: ja, en)"
    }
    
  • 制約:

    • query: 最大256文字。

    • context: 最大5件の文書。各文書は最大2048トークン。文書は関連度順にソート済みを前提。

    • language: “ja”または”en”のみをサポート。

  • 失敗時の挙動: 入力フォーマットが不正な場合、または必須フィールドが欠落している場合は、HTTP 400 Bad Request を返す。

出力契約

  • フォーマット: JSONオブジェクト。

    {
      "answer": "LLMによって生成された回答文",
      "confidence_score": 0.0,
      "sources": [
        {"document_id": "関連文書1のID", "snippet": "回答に利用した文書1からの抜粋"},
        {"document_id": "関連文書2のID", "snippet": "回答に利用した文書2からの抜粋"}
      ],
      "status": "success"
    }
    
  • 制約:

    • answer: 提供されたcontext内の情報のみに基づいて生成されていること。回答がcontext内に存在しない場合は、その旨を明確に伝えること(例:「提供された情報には回答が見つかりませんでした。」)。

    • confidence_score: 0.0(低)から1.0(高)の範囲で、回答の確信度を表現する。

    • sources: 回答の根拠となったcontext内の文書IDと、具体的な抜粋箇所(snippet)をリストで提示すること。contextを使用しなかった場合は空リスト。

    • status: 成功時は “success”、回答生成に失敗した場合は “failure” とし、追加でerror_messageフィールドを含める。

  • 禁止事項:

    • contextにない情報の生成(幻覚)。

    • ユーザーの個人情報や機密情報の推測・生成。

    • 倫理に反する内容の生成。

  • 失敗時の挙動: 上記の禁止事項に該当する場合、または回答生成中にシステムエラーが発生した場合は、status: "failure"とし、answerは空文字列、error_messageに詳細なエラー内容を記述する。

プロンプト設計

RAGプロンプトの最適化では、LLMにコンテキストの利用方法、出力形式、そして逸脱時の挙動を明確に指示することが重要です。ここでは、3種類のプロンプト案を提示します。

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

基本的なRAGプロンプト。明確な指示でコンテキスト利用を促します。

あなたは情報検索と要約に特化したアシスタントです。
以下の「コンテキスト」のみに基づいて、「クエリ」に対する正確な回答を生成してください。
コンテキスト内に回答が存在しない場合は、「提供された情報には回答が見つかりませんでした。」と回答してください。

# コンテキスト

{{context}}

# クエリ

{{query}}

# 回答

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

具体的な入出力例を示すことで、LLMの挙動を誘導します。特に回答形式や、情報がない場合の振る舞いを明確化します。

あなたは情報検索と要約に特化したアシスタントです。
以下の「コンテキスト」のみに基づいて、「クエリ」に対する正確な回答を生成してください。
コンテキスト内に回答が存在しない場合は、「提供された情報には回答が見つかりませんでした。」と回答してください。
回答は、提供された情報源を明確にするために、関連する文書からの抜粋を含めるようにしてください。

# 例1

## コンテキスト

文書1: 「RAG(Retrieval Augmented Generation)は、大規模言語モデル(LLM)が外部知識源から情報を取得し、その情報に基づいて回答を生成するAI技術です。これにより、モデルの幻覚を減少させ、最新の情報を利用できます。」
文書2: 「2020年にFacebook AIの研究者によって提案されました。」
## クエリ

RAGはいつ提案されましたか?
## 回答

RAGは2020年にFacebook AIの研究者によって提案されました。
(ソース: 文書2より「2020年にFacebook AIの研究者によって提案されました。」)

# 例2

## コンテキスト

文書1: 「LLMのトレーニングには大量のデータと計算リソースが必要です。」
## クエリ

RAGのモデルサイズは?
## 回答

提供された情報には回答が見つかりませんでした。

# あなたのタスク

## コンテキスト

{{context}}
## クエリ

{{query}}
## 回答

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

LLMに思考プロセスを段階的に出力させ、その思考に基づいて最終回答を生成させることで、透明性と正確性を高めます。特に複雑なクエリや複数のコンテキスト統合が必要な場合に有効です。

あなたは情報検索と要約に特化したアシスタントです。
以下の手順に従って、与えられた「コンテキスト」のみに基づいて「クエリ」に対する正確な回答を生成してください。

1.  **関連情報特定**: まず、クエリに直接関連するコンテキスト内の情報を特定し、その文書IDと内容を記述します。

2.  **情報統合**: 特定された情報が複数ある場合、それらを統合し、クエリへの回答を形成するための主要な事実を抽出します。

3.  **幻覚チェックと不足情報確認**: 統合された情報がクエリに完全に答えているか確認します。コンテキストにない情報を追加しようとしていないか確認します。もし、コンテキストのみでは回答できない場合は、その旨を明確に述べます。

4.  **最終回答生成**: 上記のステップに基づいて、ユーザーフレンドリーな形式で最終回答を生成します。回答は必ずコンテキスト内の情報に基づいている必要があります。

# コンテキスト

{{context}}

# クエリ

{{query}}

# 思考プロセスと最終回答


1.  **関連情報特定**:

    -   ...

2.  **情報統合**:

    -   ...

3.  **幻覚チェックと不足情報確認**:

    -   ...

4.  **最終回答生成**:

    -   {{回答}}

評価

RAGプロンプトの評価は、精度、忠実度、関連性を定量的に測ることが重要です。

評価シナリオ

  • 正例: クエリに対する回答がコンテキスト内に明確に存在するケース。

    • 例: クエリ「Gemini 1.5 Proのコンテキストウィンドウはどれくらいですか?」、コンテキスト「Gemini 1.5 Proは100万トークンのコンテキストウィンドウをサポートします。」
  • 難例:

    • 複数文書統合: 回答が複数のコンテキスト文書に散在しており、それらを統合する必要があるケース。

    • 曖昧なクエリ: クエリが曖昧で、コンテキストから最も適切な解釈を特定する必要があるケース。

    • 長いコンテキスト: 関連情報が大量の無関係な情報の中に埋もれているケース(「針の山から針を探す」)。

  • コーナーケース:

    • 情報不足: コンテキスト内にクエリに対する回答が全く存在しないケース。

    • 競合する情報: コンテキスト内にクエリに対する矛盾する情報が存在するケース。

    • 誤ったコンテキスト: 提供されたコンテキストがクエリに全く関係ないケース。

自動評価の擬似コード

自動評価は、指標(忠実度、関連性、コンテキスト利用率)に基づいて行います。

# 評価用擬似コード (Python)

import re
from typing import List, Dict

def evaluate_rag_output(
    query: str,
    context: List[str],
    llm_output: Dict,
    ground_truth_answer: str = None
) -> Dict:
    """
    RAGシステムのLLM出力を自動評価する。

    Args:
        query (str): ユーザーの元のクエリ。
        context (List[str]): 提供されたコンテキスト文書のリスト。
        llm_output (Dict): LLMの出力(入出力契約に準拠)。
        ground_truth_answer (str, optional): 正解の回答。忠実度・関連性の評価に利用。

    Returns:
        Dict: 評価結果。
    """
    evaluation_results = {
        "faithfulness_score": 0.0,  # 忠実度: 回答がコンテキストにどの程度基づいているか
        "relevance_score": 0.0,     # 関連性: 回答がクエリにどの程度関連しているか
        "context_utilization": 0.0, # コンテキスト利用率: コンテキストがどの程度活用されたか
        "hallucination_detected": False, # 幻覚の検出
        "format_adherence": False,  # 出力フォーマットの遵守
        "error_message": None       # エラーメッセージ(あれば)
    }

    # 1. 出力フォーマットの遵守

    if not isinstance(llm_output, dict) or \
       "answer" not in llm_output or \
       "confidence_score" not in llm_output or \
       "sources" not in llm_output or \
       "status" not in llm_output:
        evaluation_results["error_message"] = "出力フォーマットが不正です。"
        return evaluation_results
    else:
        evaluation_results["format_adherence"] = True

    generated_answer = llm_output.get("answer", "")
    output_status = llm_output.get("status", "failure")

    if output_status == "failure":
        evaluation_results["error_message"] = llm_output.get("error_message", "LLM生成失敗")
        return evaluation_results

    # 2. 忠実度 (Faithfulness)


    # 生成された回答内の主要な事実が、提供されたコンテキスト内に存在するかをチェック


    # これはより高度なNLI (Natural Language Inference) モデルやキーワードマッチング、埋め込み類似度で実装されることが多い


    # 簡易的には、回答中の固有名詞や数値がコンテキストにあるかチェック

    faithfulness_keywords = re.findall(r'[A-Z][a-z0-9]+|\d+\.\d+|\d+', generated_answer)
    matched_facts = 0
    total_facts = len(faithfulness_keywords)

    if total_facts > 0:
        for keyword in faithfulness_keywords:
            for doc in context:
                if keyword in doc:
                    matched_facts += 1
                    break
        evaluation_results["faithfulness_score"] = matched_facts / total_facts

        # 幻覚の簡易検出: 生成された回答がコンテキストに全く基づいていない場合

        if evaluation_results["faithfulness_score"] < 0.1 and \
           "見つかりませんでした" not in generated_answer:
            evaluation_results["hallucination_detected"] = True

    # 3. 関連性 (Relevance)


    # クエリと生成回答間のセマンティック類似度を評価 (BERT, Sentence-BERT等の埋め込みモデルを使用)


    # ground_truth_answer があれば、それとの類似度も測る

    if ground_truth_answer:

        # この部分は埋め込みモデルへのAPI呼び出し等を想定


        # from sentence_transformers import SentenceTransformer


        # model = SentenceTransformer('all-MiniLM-L6-v2')


        # query_embedding = model.encode(query)


        # answer_embedding = model.encode(generated_answer)


        # gt_answer_embedding = model.encode(ground_truth_answer)


        # from sklearn.metrics.pairwise import cosine_similarity


        # evaluation_results["relevance_score"] = cosine_similarity([query_embedding], [answer_embedding])[0][0]


        # if gt_answer_embedding:


        #     evaluation_results["gt_relevance_score"] = cosine_similarity([gt_answer_embedding], [answer_embedding])[0][0]

        pass # Placeholder for actual embedding similarity calculation

    # 簡易的には、クエリのキーワードが回答に含まれているか

    query_keywords = re.findall(r'\b\w+\b', query.lower())
    matched_query_keywords = 0
    for qk in query_keywords:
        if qk in generated_answer.lower():
            matched_query_keywords += 1
    if len(query_keywords) > 0:
        evaluation_results["relevance_score"] = matched_query_keywords / len(query_keywords)
    else:
        evaluation_results["relevance_score"] = 1.0 # クエリが空の場合は常に高関連性

    # 4. コンテキスト利用率


    # LLMの回答が、提供されたどのコンテキスト文書から情報を使用しているか

    used_sources = llm_output.get("sources", [])
    if context and len(used_sources) > 0:
        evaluation_results["context_utilization"] = len(used_sources) / len(context)
    elif generated_answer and "見つかりませんでした" not in generated_answer and not context:

        # コンテキストがないのに回答がある場合、これも問題

        evaluation_results["context_utilization"] = 0.0
        evaluation_results["hallucination_detected"] = True


    # "提供された情報には回答が見つかりませんでした" の検出

    if "見つかりませんでした" in generated_answer and ground_truth_answer and "見つかりませんでした" in ground_truth_answer:
        evaluation_results["faithfulness_score"] = 1.0
        evaluation_results["relevance_score"] = 1.0
        evaluation_results["context_utilization"] = 0.0 # コンテキストは使われなかった

    return evaluation_results

# 使用例


# llm_output_example = {


#     "answer": "RAGは2020年にFacebook AIの研究者によって提案されました。",


#     "confidence_score": 0.95,


#     "sources": [{"document_id": "文書2", "snippet": "2020年にFacebook AIの研究者によって提案されました。"}],


#     "status": "success"


# }


# context_example = ["文書1: ...", "文書2: 2020年にFacebook AIの研究者によって提案されました。"]


# query_example = "RAGはいつ提案されましたか?"


# gt_answer_example = "RAGは2020年にFacebook AIの研究者によって提案されました。"

#


# result = evaluate_rag_output(query_example, context_example, llm_output_example, gt_answer_example)


# print(result)

誤り分析

評価結果に基づいて、失敗モードを特定し、その根本原因を分析します。

失敗モード

  • 幻覚(Hallucination): コンテキストにない情報を生成する。

    • 原因: プロンプトの指示が不明瞭、コンテキストが少なすぎる、モデルの過剰な一般化能力。
  • 様式崩れ(Format Breakage): 出力契約で定義されたJSONなどのフォーマットが守られない。

    • 原因: プロンプト指示の不足、CoTプロンプトでの思考プロセスと最終回答の区別が曖昧、モデルの制約理解不足。
  • 脱線(Off-topic): クエリやコンテキストと無関係な内容を生成する。

    • 原因: クエリの曖昧さ、コンテキストが広範すぎる、プロンプトのスコープ定義不足。
  • 禁止事項違反: 倫理的・安全性のガイドラインに反する内容を生成する。

    • 原因: ユーザープロンプトの悪用、モデルの安全フィルタリングの不十分さ。
  • 情報不足による回答拒否の失敗: コンテキストに情報がないにも関わらず、「回答が見つかりませんでした」と返さずに幻覚を生成してしまう、または不適切な回答をしてしまう。

    • 原因: プロンプトでの代替応答指示の曖昧さ。

抑制手法

  • System指示の強化: プロンプトの先頭でLLMの役割、目的、制約(「提供されたコンテキストのみを使用する」「幻覚を避ける」)を明確に定義します。

  • 検証ステップの導入:

    • LLM内部検証: Chain-of-Thoughtプロンプトで「生成した回答がコンテキストに合致しているか確認せよ」のような自己検証ステップを追加。

    • 外部検証: モデルの出力後、別の小規模なLLMや正規表現、キーワードマッチングで幻覚、フォーマット、禁止事項をチェック。

  • リトライ戦略:

    • 検証ステップで失敗が検出された場合、エラーメッセージとともにモデルに再生成を指示する。

    • 例: 「出力フォーマットが不正です。JSON形式で再生成してください。」

  • コンテキストの最適化:

    • 再ランキング: クエリとコンテキストの関連度をさらに高めるために、検索結果を再ランキングする。

    • クエリ拡張: ユーザーのクエリを拡張し、より多くの関連文書を検索できるようにする。

  • Few-shot例の多様化: 成功例だけでなく、失敗例(例:「情報がない場合はこう回答する」)も示すことで、モデルのロバスト性を向上させる。

改良

誤り分析で特定された失敗モードと抑制手法に基づき、プロンプトやRAGパイプライン全体を改良します。

  1. プロンプト調整:

    • 指示の具体化: 幻覚が頻発する場合、「コンテキストにない情報を追加するな」とより強く指示。

    • 出力フォーマットの厳格化: JSONスキーマをプロンプトに含める、またはPydanticのようなライブラリを用いて出力構造を定義。

    • CoTステップの最適化: 思考ステップが多すぎる、または少なすぎる場合に調整。

  2. RAGパイプラインの調整:

    • 検索フェーズの改善:

      • 検索アルゴリズムの変更(BM25, セマンティック検索の組み合わせ)。

      • 文書チャンクサイズの最適化。

      • リランキングモデルの導入(例:ColBERT, LLMベースのリランカー)。

    • 前処理/後処理の追加:

      • 入力コンテキストのノイズ除去。

      • LLM出力のフォーマット変換、安全性チェック。

  3. モデル選定:

    • より大規模なモデルや特定のタスクに特化したモデルへの切り替え。

再評価

改良が適用されたら、再度同じ評価シナリオと自動評価スクリプトを用いて、システムのパフォーマンスを測定します。このサイクルを繰り返すことで、プロンプトとRAGシステムの継続的な最適化を図ります。

graph TD
    A["プロンプト設計"] -->|初期設計/改良提案| B("RAGシステム")
    B -->|入力: クエリ+コンテキスト| C["LLMによる回答生成"]
    C -->|出力: 回答+情報源| D["評価"]
    D -->|評価結果 (スコア/失敗モード)| E["誤り分析"]
    E -->|根本原因分析/改善策| F["改良計画"]
    F -->|プロンプト修正/パイプライン改善| A
    D -- 誤りが少ない --> G["本番デプロイ"]

まとめ

RAGプロンプトの最適化は、単一のプロンプトを調整するだけでなく、設計、評価、誤り分析、改良という反復的なプロセスを通じてRAGシステム全体の性能を向上させることを目指します。入出力契約の明確化、多様なプロンプト戦略の適用、堅牢な自動評価システムの構築、そして失敗モードに基づいた体系的な改善が、幻覚を抑制し、信頼性の高いLLMアプリケーションを実現する鍵となります。2024年7月30日現在、この反復アプローチは、RAGシステム開発における業界のベストプラクティスとして広く認識されています[Google Cloud Blog, 2024-06-15; NVIDIA Blog, 2024-07-10]。

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

コメント

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