Prompt Chainingによる複雑タスク分解とLLMの挙動評価

Mermaid

Prompt Chainingによる複雑タスク分解とLLMの挙動評価

ユースケース定義

本稿では、Prompt Chainingを用いて、顧客からの多岐にわたる製品レビューテキストから、製品の「ポジティブな側面」「ネガティブな側面」「具体的な改善提案」を抽出し、それらを統合して要約を生成するタスクを扱います。このタスクは、単一のプロンプトでは高い精度で完遂することが難しく、複数のLLM推論ステップを経て情報を段階的に精錬するPrompt Chainingが有効です。

入出力契約

入力フォーマット

単一のユーザーレビューテキスト(日本語)と、レビューを識別するためのreview_id

{
  "review_id": "REV-001",
  "review_text": "このスマートウォッチはバッテリー持ちが良く、デザインも気に入っています。しかし、通知機能が不安定で、たまに接続が切れます。アプリのUIももう少し直感的になると嬉しいです。フィットネス追跡は正確で助かっています。"
}

出力フォーマット

JSON形式で、以下のキーを持つオブジェクト。

{
  "review_id": "string",
  "summary": "string",
  "positive_aspects": ["string"],
  "negative_aspects": ["string"],
  "improvement_suggestions": ["string"]
}
  • review_id: 入力されたレビューID。
  • summary: 全体のレビュー内容を簡潔に要約したもの(50文字以内)。
  • positive_aspects: レビューから抽出されたポジティブな側面を箇条書きリストで。最大3点。
  • negative_aspects: レビューから抽出されたネガティブな側面を箇条書きリストで。最大3点。
  • improvement_suggestions: レビューから抽出された具体的な改善提案を箇条書きリストで。最大2点。

失敗時の挙動

  • 出力がJSON形式として不正な場合、または必須キー(summary, positive_aspects, negative_aspects, improvement_suggestions)のいずれかが欠落している場合、{"error": "processing_failed", "details": "reason"}を返します。
  • 抽出結果が指定された要素数(例: positive_aspectsが3点を超える)を超過した場合も、processing_failedとして理由を返します。

禁止事項

  • 生成された要約や抽出内容が、元のレビュー本文の直接的な引用のみで構成されること。必ず要約・言い換えを行うこと。
  • LLMがレビュー内容について自身の意見や感想、評価を追加すること。
  • 倫理的に問題のある内容、差別的な表現、個人を特定できる情報の生成。

制約付き仕様化

Prompt Chainingにより、以下の5つのステップでタスクを分解します。各ステップでLLMを呼び出し、前のステップの出力を次のステップの入力として使用します。

  1. ポジティブ側面抽出:
    • 入力: review_text
    • 出力: レビューのポジティブな側面を箇条書きのJSON配列形式で抽出。
    • 制約: 最大3点。各項目は20文字以内。レビュー本文から直接引用せず、簡潔に言い換える。
  2. ネガティブ側面抽出:
    • 入力: review_text
    • 出力: レビューのネガティブな側面を箇条書きのJSON配列形式で抽出。
    • 制約: 最大3点。各項目は20文字以内。レビュー本文から直接引用せず、簡潔に言い換える。
  3. 改善提案抽出:
    • 入力: review_text
    • 出力: レビューに含まれる具体的な改善提案を箇条書きのJSON配列形式で抽出。
    • 制約: 最大2点。各項目は30文字以内。レビュー本文から直接引用せず、簡潔に言い換える。提案がない場合は空の配列。
  4. 総合要約生成:
    • 入力: review_text, ステップ1~3で抽出されたポジティブ・ネガティブ側面、改善提案。
    • 出力: 上記情報を統合した50文字以内の簡潔な要約(文字列)。
    • 制約: 50文字以内。元のレビューの主要なポイントを網羅すること。
  5. 最終JSON形式結合:
    • 入力: review_id, ステップ1~4の出力。
    • 出力: 定義された最終JSONフォーマット。
    • 制約: 厳密に定義されたJSONスキーマに従うこと。

プロンプト設計

以下のプロンプトは、Prompt Chainingの各ステップでLLMに指示を与える際の設計思想を示します。ここでは、各ステップの最初のタスクである「ポジティブ側面抽出」を例に、異なるアプローチのプロンプトを提示します。共通のSystem指示は、各プロンプトの前に適用されるものとします。

共通System指示: あなたはLLMのプロンプトエンジニアです。与えられたユーザーレビューから要求された情報を抽出し、指定されたJSON形式で出力してください。自身の意見は追加せず、レビュー本文の内容のみに基づき判断してください。

1. ゼロショットプロンプト (ポジティブ側面抽出)

最も基本的な形式で、事前知識や例を与えずに指示のみで推論させます。

ユーザーレビューからポジティブな側面を最大3点、JSON配列で抽出してください。各項目は20文字以内とし、レビュー本文を直接引用せず簡潔に言い換えてください。

レビュー:
{{review_text}}

出力形式:
["ポジティブ側面1", "ポジティブ側面2", ...]

2. 少数例 (Few-shot) プロンプト (ポジティブ側面抽出)

いくつかの入出力例を示し、期待する挙動を具体的に示します。

ユーザーレビューからポジティブな側面を最大3点、JSON配列で抽出してください。各項目は20文字以内とし、レビュー本文を直接引用せず簡潔に言い換えてください。

---
レビュー:
このカフェのコーヒーは絶品で、スタッフもとても親切です。Wi-Fiも速くて作業がはかどります。ただ、席が少なくて混んでいることが多いのが難点ですね。
出力:
["コーヒーが絶品", "スタッフが親切", "Wi-Fiが高速"]
---
レビュー:
新しく買ったスマホはカメラ性能が素晴らしく、バッテリーも一日持ちます。指紋認証の感度も良い。しかし、たまにアプリが落ちるのが困ります。
出力:
["カメラ性能が良い", "バッテリー持続が良い", "指紋認証の感度が良い"]
---
レビュー:
{{review_text}}
出力:

3. Chain-of-Thought (CoT) 制約型プロンプト (ポジティブ側面抽出)

LLMに思考プロセスを明示させ、推論の透明性と正確性を向上させます。

ユーザーレビューからポジティブな側面を最大3点、JSON配列で抽出してください。各項目は20文字以内とし、レビュー本文を直接引用せず簡潔に言い換えてください。
まず、ポジティブな箇所をレビュー本文から特定し、その上で要約・言い換えを行い、指定された形式で出力してください。

レビュー:
{{review_text}}

思考プロセス:
1. レビュー本文中のポジティブな表現を特定する。
2. 特定した表現を20文字以内で簡潔に言い換える。
3. 最大3点に絞り、JSON配列で整形する。

出力:
["ポジティブ側面1", "ポジティブ側面2", ...]

(注: 他のステップのプロンプトも同様のアプローチで設計されます。例えば、ステップ4の総合要約生成プロンプトは、前のステップのJSON出力を入力として受け取り、それらを統合するよう指示します。)

評価

評価シナリオ

  1. 正例: 明確なポジティブ、ネガティブな側面、具体的な改善提案を含むレビュー。
    • 例: “新製品の性能は抜群でデザインも秀逸。バッテリーも長持ちするが、価格が少し高すぎる。次回はもう少し安価なモデルも出してほしい。”
  2. 難例: 曖昧な表現、複数の感情が混在、皮肉が含まれるレビュー。
    • 例: “使い勝手はまぁ普通。特に不満はないが、かと言って褒めるところもない。UIはもうちょっと頑張れるはず。価格相応って感じかな。”
  3. コーナーケース:
    • 特定の要素が全く含まれない(例: 改善提案がない)レビュー。
      • 例: “最高の製品!これ以上のものはない。完璧です。” (改善提案なし)
    • 極端に短い、または長いレビュー。
    • レビューIDが特殊な文字を含む場合。

自動評価の擬似コード

Pythonライクな擬似コードで採点ルーブリック、正規表現、関数評価を組み合わせます。

import json
import re

def evaluate_llm_output(expected_output, actual_output, review_text):
    score = 0
    feedback = []

    # 1. JSON形式のチェック
    try:
        parsed_output = json.loads(actual_output)
        score += 10
    except json.JSONDecodeError:
        feedback.append("JSON形式が不正です。")
        return {"score": score, "feedback": feedback}

    # 2. 必須キーの存在チェック
    required_keys = ["review_id", "summary", "positive_aspects", "negative_aspects", "improvement_suggestions"]
    if all(key in parsed_output for key in required_keys):
        score += 10
    else:
        feedback.append("必須キーが不足しています。")

    # 3. review_idの一致チェック
    if parsed_output.get("review_id") == expected_output.get("review_id"):
        score += 5
    else:
        feedback.append("review_idが一致しません。")

    # 4. summaryの文字数チェック (50文字以内)
    summary = parsed_output.get("summary", "")
    if 1 <= len(summary) <= 50:
        score += 10
    else:
        feedback.append(f"要約の文字数が範囲外です ({len(summary)}文字)。")

    # 5. 各リストの要素数と内容の妥当性チェック
    # ルーブリック: 各項目に対して、要素数と内容の関連性を評価
    for key, max_count in [("positive_aspects", 3), ("negative_aspects", 3), ("improvement_suggestions", 2)]:
        items = parsed_output.get(key, [])
        if not isinstance(items, list):
            feedback.append(f"{key}がリスト形式ではありません。")
            continue

        # 要素数チェック
        if len(items) <= max_count:
            score += 5
        else:
            feedback.append(f"{key}の要素数が多すぎます ({len(items)} > {max_count})。")

        # 内容の妥当性 (簡易版: レビュー本文との関連性、幻覚チェック)
        # より高度な評価はEmbedding類似度などが必要だが、ここではキーワードマッチで擬似化
        for item in items:
            if not isinstance(item, str):
                feedback.append(f"{key}の要素が文字列ではありません。")
                continue
            if len(item) == 0:
                feedback.append(f"{key}に空の要素があります。")
                continue

            # 幻覚チェック (簡易版): 抽出された項目がレビュー本文と全く無関係でないか
            # より高度なチェックにはセマンティックな比較が必要
            if not any(re.search(re.escape(word), review_text, re.IGNORECASE) for word in item.split()):
                feedback.append(f"'{item}' はレビュー本文との関連性が薄い可能性があります。")
                # score -= 5 # 幻覚があった場合の減点

            # 禁止事項: 直接引用のチェック (簡易版: 抽出項目がレビュー本文に完全に一致するか)
            if item in review_text:
                feedback.append(f"'{item}' はレビュー本文からの直接引用の可能性があります。")
                score -= 5 # 禁止事項違反の減点

            # 文字数制約 (positive_aspects, negative_aspectsは20文字、improvement_suggestionsは30文字)
            if key in ["positive_aspects", "negative_aspects"] and len(item) > 20:
                feedback.append(f"'{item}' ({key}) が20文字を超過しています。")
                score -= 3
            if key == "improvement_suggestions" and len(item) > 30:
                feedback.append(f"'{item}' ({key}) が30文字を超過しています。")
                score -= 3

    # 総合スコアは最大100点として調整
    return {"score": min(score, 100), "feedback": feedback}

# --- 評価例 ---
# review_text = "このスマートウォッチはバッテリー持ちが良く、デザインも気に入っています。しかし、通知機能が不安定で、たまに接続が切れます。アプリのUIももう少し直感的になると嬉しいです。フィットネス追跡は正確で助かっています。"
# expected_output_example = {"review_id": "REV-001", ...} # 期待される出力の構造
# actual_output_example = """{"review_id": "REV-001", "summary": "バッテリー、デザイン、フィットネスが好評だが、通知とUIに改善の余地があるスマートウォッチ。", "positive_aspects": ["バッテリー持ちが良い", "デザインが良い", "フィットネス追跡が正確"], "negative_aspects": ["通知機能が不安定", "接続が切れる"], "improvement_suggestions": ["アプリUIの改善"]}"""
#
# result = evaluate_llm_output(expected_output_example, actual_output_example, review_text)
# print(result)

プロンプト→モデル→評価→改良のループ

graph TD
    A["プロンプト設計"] --> B("LLM推論");
    B --> C{"出力評価"};
    C -- 不合格 --> D["誤り分析"];
    D --> E["プロンプト改良"];
    E --> B;
    C -- 合格 --> F["完了"];

誤り分析

評価プロセスで検出される主な失敗モードと、その分析手法は以下の通りです。

  1. 幻覚 (Hallucination):
    • 検出: 抽出された情報がレビュー本文に存在しない、または大きく逸脱している場合。自動評価の「内容の妥当性チェック」で検出。
    • 分析: LLMが生成を「創作」した原因を特定。プロンプトの指示の曖昧さ、またはモデルの知識混同が考えられます。
  2. 様式崩れ (Format Breakage):
    • 検出: JSON形式が不正、必須キーの欠落、リスト要素数の超過、文字列以外の型が含まれるなど。自動評価の「JSON形式のチェック」「必須キーの存在チェック」「各リストの要素数と内容の妥当性チェック」で検出。
    • 分析: プロンプトでの出力形式の指定が不明瞭だったか、LLMが形式制約を軽視した可能性があります。
  3. 脱線 (Off-topic/Irrelevant):
    • 検出: タスクのスコープ外の情報が生成される、または指定された側面とは異なる内容(例: ポジティブ側面抽出でネガティブな内容が出力される)。
    • 分析: プロンプトのタスク定義が広すぎたか、LLMが文脈を誤解した可能性があります。
  4. 禁止事項違反:
    • 検出: レビュー本文の直接引用、LLMの個人的意見の追加など。自動評価の「禁止事項チェック」で検出。
    • 分析: プロンプトで禁止事項が明確に伝わっていないか、あるいはLLMが制約を遵守する能力が低い可能性があります。

改良

上記誤り分析に基づき、プロンプトの改良と抑制手法を講じます。

  1. 幻覚の抑制手法:

    • System指示の強化: 「レビュー本文に明示的に記載されている情報のみを抽出してください。それ以外の情報は追加しないでください。」のように強調。
    • 検証ステップの追加: LLMの出力後に、人間または別のLLMを用いてレビュー本文との照合を徹底するステップを設ける。
    • CoTプロンプトの活用: LLMに「思考プロセス」を明示させ、レビュー本文のどの部分に基づいているかを具体的に記述させることで、幻覚を抑える。
  2. 様式崩れの抑制手法:

    • 厳密なフォーマット指定: プロンプト内でJSONスキーマの例を詳細に提示し、繰り返し強調する。
    • 出力検証とリトライ戦略: LLMからの出力を受け取った後、自動評価でJSONパースやスキーマバリデーションを行い、不正だった場合はエラーメッセージを添えてLLMに再試行させる。
    • Pydanticなどの利用: 実際のシステムでは、LLMの出力をPydanticなどのデータバリデーションライブラリで強制的にモデルに変換し、不正な場合は例外を発生させる。
  3. 脱線の抑制手法:

    • タスクとスコープの明確化: プロンプトの冒頭で、「このタスクの目的は[X]であり、[Y]は対象外です」と具体的に記述。
    • ネガティブ制約: 「〇〇に関する情報は抽出しないでください」といった禁止事項をプロンプトに含める。
    • CoTプロンプトの活用: LLMの思考プロセスをガイドし、与えられたタスクから逸脱しないよう強制する。
  4. 禁止事項違反の抑制手法:

    • System指示での厳格な明記: 禁止事項を箇条書きで分かりやすく提示し、その重要性を強調。
    • 特定のキーワードやパターンの監視: 出力検証ステップで、引用符の多用や、「私は思います」「個人的には」といったLLMの主観を表すフレーズを正規表現などでチェック。
    • 倫理フィルター: 出力後に、LLMや外部のモデレーションAPIを用いて不適切な内容をフィルタリングする。

再評価

改良されたプロンプト群を、初期評価で使用したシナリオ(正例、難例、コーナーケース)に加えて、特に問題が頻発した誤り分析のテストケースを用いて再度評価します。自動評価のスコア向上はもちろんのこと、フィードバックの質や具体的な失敗モードの発生頻度が減少しているかを確認します。特に、リトライ戦略を導入した場合は、リトライ後の成功率も重要な評価指標となります。

まとめ

Prompt Chainingは、複雑なタスクをLLMが処理可能な小さなステップに分解し、それぞれのステップでLLMの推論をガイドする強力な手法です。本稿では、レビューからの情報抽出と要約生成を例に、入出力契約の定義、制約付き仕様化、ゼロショット/少数例/CoT制約型プロンプト設計、および自動評価の擬似コードを示しました。 幻覚、様式崩れ、脱線、禁止事項といった主要な失敗モードに対し、System指示の強化、検証ステップの追加、リトライ戦略、CoTプロンプトの活用などの多様な抑制手法を適用することで、LLMの挙動をより堅牢かつ高精度に制御できることが示されました。プロンプト設計は一度で完結するものではなく、継続的な評価と改良のサイクルを通じて精度を高めていくプロセスが不可欠です。

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

コメント

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