Few-shotプロンプティングを活用したLLMアプリケーションの設計と評価

Tech

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

Few-shotプロンプティングを活用したLLMアプリケーションの設計と評価

大規模言語モデル(LLM)の活用において、Few-shotプロンプティングは、限られた事例でモデルの振る舞いを効果的に誘導するための強力な手法です。特に、特定のタスクに対するモデルの性能と信頼性を向上させる上で重要な役割を果たします。本記事では、Few-shotプロンプティングを用いたLLMアプリケーションの設計、評価、そして反復的な改良サイクルについて、実践的なアプローチを解説します。

1. ユースケース定義

企業内の複雑なユーザーリクエストに対し、既存のFAQデータから最適な回答をJSONフォーマットで生成するシステムをユースケースとして扱います。特に、曖昧な質問や複数のFAQに関連する質問に対して、Few-shotプロンプティングとChain-of-Thought (CoT) を活用し、より正確で関連性の高い回答を、定められたJSONフォーマットで出力することを目指します。

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

LLMアプリケーションを設計する上で、入出力契約を明確に定義することは、モデルの期待される振る舞いを保証し、後の評価と改良プロセスを容易にします。

入力契約

  • user_query: ユーザーからの質問を表す文字列。

  • faq_data: 企業内のFAQリスト。各FAQは{"question": "...", "answer": "..."}形式のJSONオブジェクトの配列。例:[{"question": "パスワードを忘れた場合の対処法", "answer": "パスワードリセットページをご利用ください。"}, ...]

出力契約

  • フォーマット: 以下のJSON構造に従うこと。

    {
      "related_faqs": [
        {"question": "関連するFAQの質問文", "answer": "関連するFAQの回答文"}
      ],
      "reasoning": "モデルが関連FAQを選定した思考プロセス(Chain-of-Thoughtの場合)または結果の要約。"
    }
    
  • 失敗時の挙動:

    • 関連するFAQが見つからない場合、related_faqsは空の配列となり、reasoningには「関連するFAQは見つかりませんでした。」というメッセージを含める。

    • JSONフォーマットの逸脱、不適切なコンテンツの生成などが発生した場合、可能な限りエラーメッセージをreasoningに含め、related_faqsを空の配列とする。

  • 禁止事項:

    • ユーザーの個人情報や機密情報、またはfaq_dataに含まれない情報を出力すること。

    • 差別的、暴力的、その他不適切なコンテンツを生成すること。

    • 指定されたJSONフォーマット以外の形式で出力すること。

3. プロンプト設計

Few-shotプロンプティングは、モデルに数例の入出力ペアを示すことで、タスク理解を深めさせる手法です。ここでは、ユースケースに応じた3種類のプロンプト案を提示します。

3.1 ゼロショットプロンプト

モデルにタスクの指示のみを与え、具体的な例は含めません。汎用的なタスクや、モデルが既にその領域の知識を十分に持っている場合に有効です。

あなたはユーザーの質問に対し、提供されたFAQデータから最も関連性の高い回答をJSON形式で提供するAIアシスタントです。
FAQデータにない内容は回答せず、「関連するFAQは見つかりませんでした。」と回答してください。

<FAQ_DATA>
{{faq_data}}
</FAQ_DATA>

ユーザーの質問: {{user_query}}

出力:

3.2 少数例(Few-shot)プロンプト

タスクの指示に加え、数件の入出力例を提供します。モデルが期待されるフォーマットやタスクのニュアンスを理解しやすくなります。

あなたはユーザーの質問に対し、提供されたFAQデータから最も関連性の高い回答をJSON形式で提供するAIアシスタントです。
FAQデータにない内容は回答せず、「関連するFAQは見つかりませんでした。」と回答してください。

<FAQ_DATA>
{{faq_data}}
</FAQ_DATA>

---
ユーザーの質問: 支払い方法について知りたいです。
出力:
```json
{
  "related_faqs": [
    {
      "question": "利用可能な支払い方法は何ですか?",
      "answer": "クレジットカード、銀行振込、コンビニ払いが利用可能です。"
    }
  ],
  "reasoning": "ユーザーの質問が支払い方法に関するもので、提供されたFAQデータに「利用可能な支払い方法」が直接関連するため。"
}

ユーザーの質問: 会員登録の手順を教えてください。 出力:

{
  "related_faqs": [
    {
      "question": "新規会員登録の方法",
      "answer": "トップページの「新規登録」ボタンから必要事項を入力し、登録を完了してください。"
    }
  ],
  "reasoning": "ユーザーの質問は会員登録の手順に関するもので、提供されたFAQデータに「新規会員登録の方法」が直接関連するため。"
}

ユーザーの質問: {{user_query}} 出力:

### 3.3 Chain-of-Thought (CoT) 制約型プロンプト

CoTは、モデルに思考プロセスを段階的に出力させることで、複雑な推論タスクの性能を向上させる手法です\[1]。ここでは、CoTと出力フォーマットの制約を組み合わせます。

```text
SYSTEM: あなたは企業内のFAQデータに基づき、ユーザーの質問に最も関連性の高い回答をJSON形式で提供する高度なAIアシスタントです。
以下の手順を厳守し、思考プロセス(Thought)を明確にした上で最終的なJSON出力を行ってください。

1. ユーザーの質問を分析し、主要なキーワードと意図を特定する。

2. 提供された<FAQ_DATA>から、特定したキーワードと意図に最も関連性の高いFAQを特定する。複数のFAQが関連する場合はすべて選定する。

3. FAQデータに完全に合致する回答がない場合や、質問がFAQの範囲外である場合は、その旨を伝える。

4. 最終的な出力は、必ずJSONフォーマットに従うこと。

<FAQ_DATA>
{{faq_data}}
</FAQ_DATA>

---
ユーザーの質問: アカウントのパスワードをリセットしたいのですが、どうすれば良いでしょうか?
Thought:

1. ユーザーの質問の意図は「パスワードのリセット方法」を知ること。キーワードは「パスワード」「リセット」。

2. FAQデータの中から「パスワード」と「リセット」に関連するものを探す。例えば、「パスワードを忘れた場合の対処法」や「パスワードの変更方法」などが関連する。ここでは「パスワードを忘れた場合の対処法」が最も直接的。

3. 該当するFAQとその回答を選定し、JSON形式で出力する。
Output:
```json
{
  "related_faqs": [
    {
      "question": "パスワードを忘れた場合の対処法",
      "answer": "パスワードリセットページをご利用ください。ご登録のメールアドレスを入力し、指示に従って新しいパスワードを設定できます。"
    }
  ],
  "reasoning": "ユーザーの質問はパスワードのリセット方法に関するもので、FAQデータ内の「パスワードを忘れた場合の対処法」が直接的に関連するため、これを抽出しました。"
}

ユーザーの質問: 配送状況を知りたいです。注文番号はAAA123です。 Thought:

  1. ユーザーの質問の意図は「配送状況の確認」。キーワードは「配送状況」「注文番号」。

  2. FAQデータの中から「配送状況」に関連するものを探す。例えば、「注文した商品の配送状況を確認するには?」が直接関連する。

  3. 該当するFAQとその回答を選定し、JSON形式で出力する。 Output:

{
  "related_faqs": [
    {
      "question": "注文した商品の配送状況を確認するには?",
      "answer": "ご注文履歴ページから、該当する注文の「配送状況を確認」ボタンをクリックしてください。注文番号AAA123の情報が表示されます。"
    }
  ],
  "reasoning": "ユーザーの質問は商品の配送状況確認に関するもので、注文番号も提供されています。FAQデータ内の「注文した商品の配送状況を確認するには?」が直接的に関連するため、これを抽出しました。"
}

ユーザーの質問: {{user_query}} Thought:

CoTプロンプティングは、モデルが複雑なタスクを段階的に処理する能力を引き出し、特に推論能力が求められる場面で有効です\[2]。Google AI Blogでも、CoTの有効性が紹介されています\[3]。

## 4. 評価

プロンプトの効果を客観的に測定し、改善点を見つけるためには、体系的な評価が不可欠です。

### 4.1 評価シナリオ

モデルの様々な挙動をテストするため、正例、難例、コーナーケースを網羅する評価シナリオを設定します。

-   **正例**: FAQに明確に合致する質問。

    -   例1: 「アカウント登録の方法を教えてください。」

    -   例2: 「支払い可能な方法は何がありますか?」

-   **難例**: 複数のFAQに関連する、または曖昧な質問、FAQの内容を統合して回答する必要がある質問。

    -   例1: 「注文をキャンセルしたいんだけど、手数料はかかるの?あと、いつまでに連絡すればいい?」

    -   例2: 「製品保証について詳しく知りたいです。故障時の対応と、保証期間は?」

-   **コーナーケース**: FAQに全く関連しない質問、不適切なコンテンツを含む質問。

    -   例1: 「今日の天気は?」

    -   例2: 「[攻撃的な内容を含む質問]」

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

自動評価は、迅速な反復開発を可能にします。以下の擬似コードは、Pythonでの評価ロジックの例です。

```python
import json
import re
from typing import Dict, List, Any

# 評価基準

SCORING_RUBRIC = {
    "json_format_valid": 3,  # JSON構造が正しいか
    "related_faqs_present": 2, # 関連FAQが抽出されたか(見つからない場合も正しい形式ならOK)
    "content_relevance": 4,  # 抽出されたFAQの質問がユーザー質問に適切か(キーワードマッチ/類似度)
    "answer_accuracy": 3,    # 抽出されたFAQの回答が元のFAQデータと一致するか
    "no_hallucination": 3,   # FAQにない内容を生成していないか
    "no_harmful_content": -5 # 有害なコンテンツがないか(ペナルティ)
}

def evaluate_llm_output(user_query: str, faq_data: List[Dict[str, str]], llm_output_str: str) -> Dict[str, Any]:
    score = 0
    feedback = []

    # 1. JSONフォーマットの検証

    try:
        output_json = json.loads(llm_output_str)
        feedback.append("JSONフォーマット: 正常")
        score += SCORING_RUBRIC["json_format_valid"]
    except json.JSONDecodeError:
        feedback.append("JSONフォーマット: エラー (パース失敗)")
        return {"score": score, "feedback": feedback} # フォーマットエラーで終了

    # 2. 必須キーの確認と構造チェック

    if not isinstance(output_json, dict) or "related_faqs" not in output_json or "reasoning" not in output_json:
        feedback.append("JSON構造: 必須キー(related_faqs, reasoning)が見つからない、またはルートが辞書でない")
        return {"score": score, "feedback": feedback}
    if not isinstance(output_json["related_faqs"], list):
        feedback.append("JSON構造: related_faqsがリストではない")
        return {"score": score, "feedback": feedback}

    # 3. 関連FAQの有無

    if output_json["related_faqs"]:
        feedback.append("関連FAQの抽出: あり")
        score += SCORING_RUBRIC["related_faqs_present"]
    elif "関連するFAQは見つかりませんでした。" in output_json.get("reasoning", ""):
        feedback.append("関連FAQの抽出: なし(適切に処理)")
        score += SCORING_RUBRIC["related_faqs_present"] # 正しく「なし」と判断した場合も評価

    extracted_faqs = output_json.get("related_faqs", [])

    # 4. コンテンツの関連性、正確性、幻覚チェック

    original_faq_map = {faq["question"]: faq["answer"] for faq in faq_data}

    for item in extracted_faqs:
        if not isinstance(item, dict) or "question" not in item or "answer" not in item:
            feedback.append(f"関連FAQアイテムの構造エラー: {item}")
            continue

        extracted_q = item["question"]
        extracted_a = item["answer"]

        # 関連性(簡易的なキーワードマッチ)

        if any(keyword in user_query.lower() for keyword in extracted_q.lower().split()):
            score += SCORING_RUBRIC["content_relevance"] / len(extracted_faqs) # 複数ある場合は均等割り振り
            feedback.append(f"内容の関連性(部分一致): '{extracted_q}'")
        else:
            feedback.append(f"内容の関連性(低): '{extracted_q}' とユーザー質問の関連性が低い可能性があります。")

        # 正確性&幻覚チェック

        if extracted_q in original_faq_map and original_faq_map[extracted_q] == extracted_a:
            score += SCORING_RUBRIC["answer_accuracy"] / len(extracted_faqs)
            score += SCORING_RUBRIC["no_hallucination"] / len(extracted_faqs)
            feedback.append(f"回答の正確性: '{extracted_q}' は元のFAQデータと一致。")
        elif extracted_q in original_faq_map and original_faq_map[extracted_q] != extracted_a:
            feedback.append(f"回答の不正確性: '{extracted_q}' の回答が元のFAQデータと異なります。")
        else:
            feedback.append(f"幻覚の可能性: 抽出されたFAQ質問 '{extracted_q}' は元のFAQデータに見つかりません。")

    # 5. 有害なコンテンツのチェック (簡易版)


    # 実際にはコンテンツモデレーションAPI (e.g., Google Cloud Content Safety) を利用

    harmful_keywords = ["暴力", "差別", "個人情報漏洩"] # 例
    if any(keyword in llm_output_str for keyword in harmful_keywords):
        score += SCORING_RUBRIC["no_harmful_content"]
        feedback.append("有害なコンテンツ: 検出されました。")

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

# --- 使用例 ---


# faq_data = [{"question": "パスワードを忘れた場合の対処法", "answer": "パスワードリセットページをご利用ください。"}, ...]


# user_query = "パスワードをリセットしたいのですが?"


# llm_output = '{"related_faqs": [{"question": "パスワードを忘れた場合の対処法", "answer": "パスワードリセットページをご利用ください。"}], "reasoning": "ユーザーの質問はパスワードのリセット方法に関するもので、FAQデータ内の「パスワードを忘れた場合の対処法」が直接的に関連するため、これを抽出しました。"}'


# result = evaluate_llm_output(user_query, faq_data, llm_output)


# print(result)

この擬似コードは、JSONの整合性、抽出されたコンテンツの関連性、および幻覚の有無を評価します。より高度な評価には、セマンティック類似度(埋め込みベクトルを用いたコサイン類似度など)や、人間の評価者によるアノテーションが必要です。Hugging FaceのブログではLLM評価の多様な側面が紹介されています[4]。

5. 誤り分析と抑制手法

評価を通じて明らかになった失敗モードを分析し、適切な抑制手法を適用します。

5.1 失敗モード

LLMは多様な失敗モードを持ちます。

  1. 幻覚 (Hallucination): FAQデータに存在しない情報を捏造して回答する。

    • 例: 「パスワードリセットは、サポートセンターへの電話でのみ可能です。」(実際はWebサイトで可能)
  2. 様式崩れ (Format Deviation): JSONフォーマットの構造が崩れる、または必須フィールドが欠落する。

    • 例: { "related_faqs": [{ "question": "..." }] (answerキー欠落)
  3. 脱線 (Off-topic): ユーザーの質問とは関係のないFAQを抽出したり、無関係な情報を生成したりする。

    • 例: 「今日の天気は?」という質問に対し、無関係なFAQを回答する。
  4. 禁止事項 (Harmful Content): 差別的、暴力的、個人情報を含む不適切なコンテンツを生成する。

    • 例: 悪意のあるプロンプトに対し、不適切な回答を生成してしまう。

5.2 抑制手法

各失敗モードに対する具体的な抑制手法を適用します。

  1. System指示の強化: プロンプトのSystemロールを使い、モデルに役割、制約、倫理ガイドラインを厳密に指示します[5]。

    • FAQデータに含まれる情報のみに基づいて回答すること。

    • JSONフォーマットを厳守し、不明な場合は空の配列を返すこと。

    • ユーザーの個人情報や機密情報を決して出力しないこと。

  2. 出力検証ステップ: モデルの出力後、プログラム側で追加の検証を行います。

    • JSONスキーマ検証: 出力されたJSONが事前に定義したスキーマに準拠しているかを確認します。

    • キーワードチェック: 特定の禁止語句やパターンが出力に含まれていないかを確認します。

    • コンテンツモデレーションAPIの利用: Google Cloud Content SafetyなどのAPIを利用し、有害なコンテンツの有無をチェックします[6]。

  3. リトライ戦略: 初回の出力が失敗した場合(例: JSONパースエラー)、異なるプロンプトやパラメータで再試行するメカニズムを導入します。

    • 例: JSONパースエラーの場合、モデルにエラーメッセージと期待されるフォーマットを再提示し、再生成を促す。

6. 改良と再評価

評価と誤り分析の結果に基づき、プロンプトやモデルのパラメータを改良します。このプロセスは反復的であり、以下のMermaid図に示すようなループで進行します。

graph TD
    A["プロンプト設計"] --> |プロンプトを定義・更新| B["LLM推論"];
    B --> |モデルから出力生成| C["モデル出力"];
    C --> |自動評価・手動レビュー| D["評価と誤り分析"];
    D -- |改善フィードバックと洞察| --> A;
    D -- |品質基準達成| --> E["システムデプロイ"];

プロンプトの改良例:

  • CoTプロンプトの思考ステップをより具体的にする。

  • Few-shotの例を増やしたり、多様なシナリオを含むように調整する。

  • System指示で強調したい制約を追加・変更する。

改良後、再度評価シナリオを用いてモデルの性能を測定し、初期の性能と比較します。この反復を通じて、モデルの信頼性と性能を継続的に向上させることができます。

7. まとめ

Few-shotプロンプティングは、LLMアプリケーション開発において、モデルの振る舞いを効果的に制御し、特定のタスクへの適応性を高めるための重要な技術です。本記事では、ユースケース定義から始まり、入出力契約、ゼロショット、Few-shot、Chain-of-Thought制約型プロンプトの設計、そして自動評価を通じた誤り分析と抑制手法、さらに継続的な改良サイクルまでを網羅的に解説しました。このフレームワークを活用することで、より堅牢で信頼性の高いLLMアプリケーションを開発することができます。


参考文献 [1] Wei, J., Wang, X., Schuurmans, D., Bosma, M., Ichter, F., Ritter, M., … & Le, Q. V. (2022). Chain-of-Thought Prompting Elicits Reasoning in Large Language Models. arXiv preprint arXiv:2201.11903. (公開日: 2022年1月29日 JST) [2] Brown, T. B., Mann, B., Ryder, N., Subbiah, M., Kaplan, J., Dhariwal, P., … & Amodei, D. (2020). Language Models are Few-Shot Learners. arXiv preprint arXiv:2005.14165. (公開日: 2020年5月29日 JST) [3] Google AI Blog. (2022). Chain-of-Thought Prompting for Large Language Models. (公開日: 2022年5月11日 JST) [4] Hugging Face Blog. (2023). LLM Evaluation: Best Practices and Tips. (公開日: 2023年10月17日 JST) [5] Google Cloud. (2024). Responsible AI guidelines for Generative AI. (更新日: 2024年1月20日 JST) [6] Google Cloud. (2023). Content safety for generative AI. (更新日: 2023年12月5日 JST)

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

コメント

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