Communityコーディング&開発github.com

LLM評価&テスト

プロンプトテスト、幻覚検出、ベンチマーク作成、品質指標を含むLLMアプリケーションの評価とテストパターン。LLMアプリケーションのテスト、プロンプト品質の検証、体系的な評価の実装、LLMパフォーマンスの測定に使用します。

LLM評価&テスト とは?

このスキルは、LLMアプリケーションの評価とテストに関する包括的なガイドを提供します。開発者は、プロンプトテスト、出力検証、幻覚検出、ベンチマーク作成、A/Bテスト、品質指標など、様々な評価手法を学ぶことができます。従来の指標(BLEU、ROUGE、BERTScore、パープレキシティ)から、LLM-as-Judge(GPT-4評価、ルーブリックスコアリング)やタスク固有の指標(完全一致、F1、正確性)まで、幅広い評価手法をカバーしています。特に、幻覚検出ではグラウンディングチェック、外部検証、自己整合性チェックの3つの方法を提供し、RAGシステム向けの検索品質評価も含まれています。このスキルは、Claude CodeやCursor、Codexなどの主要なAIエージェントプラットフォームで使用でき、CI/CDパイプラインへの継続的評価の統合もサポートしています。

対応~Claude Code✓Codex CLI~Cursor
npx skills add https://github.com/applied-artificial-intelligence/claude-code-toolkit/tree/main/skills/llm-evaluation

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

LLM評価&テスト は何をしますか?

LLMアプリケーションの評価とテストに関する包括的なガイド。プロンプトテスト、出力検証、幻覚検出、ベンチマーク作成、A/Bテスト、品質指標をカバーします。


クイックリファレンス

このスキルを使用するタイミング:

  • LLMアプリケーションの出力テスト
  • プロンプトの品質と一貫性の検証
  • 幻覚や事実誤りの検出
  • 評価ベンチマークの作成
  • プロンプトまたはモデルのA/Bテスト
  • 継続的評価(CI/CD)の実装
  • 検索品質の測定(RAG向け)
  • 予期しないLLM動作のデバッグ

カバーする指標:

  • 従来型: BLEU、ROUGE、BERTScore、パープレキシティ
  • LLM-as-Judge: GPT-4評価、ルーブリックベースのスコアリング
  • タスク固有: 完全一致、F1、精度、再現率
  • 品質: 有害性、バイアス、首尾一貫性、関連性

パート1: 評価の基礎

LLM評価の難しさ

LLM評価が難しい理由:

  1. 主観的な品質 - 「良い」出力はユースケースによって異なる
  2. 単一の正解がない - 複数の正当な回答が存在する
  3. 文脈依存性 - 同じ出力でもシナリオによって良し悪しが変わる
  4. ラベル付けにコストがかかる - 人間による評価はスケールしない
  5. 敵対的な脆弱性 - 小さなプロンプト変更が大きな出力変化を引き起こす

解決策: 多層評価

レイヤー1: 自動指標(高速、スケーラブル)
  ↓
レイヤー2: LLM-as-Judge(柔軟、ニュアンス対応)
  ↓
レイヤー3: 人間によるレビュー(ゴールドスタンダード、高コスト)

評価データセットの構造

from dataclasses import dataclass
from typing import List, Optional

@dataclass
class EvalExample:
    """単一の評価サンプル。"""
    input: str  # ユーザー入力 / プロンプト
    expected_output: Optional[str]  # ゴールドスタンダード(存在する場合)
    context: Optional[str]  # 追加コンテキスト(RAG向け)
    metadata: dict  # カテゴリ、難易度など

@dataclass
class EvalResult:
    """1サンプル分の評価結果。"""
    example_id: str
    actual_output: str
    scores: dict  # {'metric_name': score}
    passed: bool
    failure_reason: Optional[str]

# データセットの例
eval_dataset = [
    EvalExample(
        input="フランスの首都は?",
        expected_output="パリ",
        context=None,
        metadata={'category': 'factual', 'difficulty': 'easy'}
    ),
    EvalExample(
        input="量子もつれを説明してください",
        expected_output=None,  # 単一の回答はない
        context=None,
        metadata={'category': 'explanation', 'difficulty': 'hard'}
    )
]

パート2: 従来型の指標

指標1: 完全一致(最もシンプル)

def exact_match(predicted: str, expected: str, case_sensitive: bool = False) -> float:
    """
    二値指標: 一致すれば1.0、そうでなければ0.0。

    使用方法: 分類、短い回答、構造化出力
    制限: 生成タスクには厳しすぎる
    """
    if not case_sensitive:
        predicted = predicted.lower().strip()
        expected = expected.lower().strip()

    return 1.0 if predicted == expected else 0.0

# 例
score = exact_match("Paris", "paris")  # 1.0
score = exact_match("The capital is Paris", "Paris")  # 0.0

指標2: ROUGE(再現率志向)

from rouge_score import rouge_scorer

def compute_rouge(predicted: str, expected: str) -> dict:
    """
    テキスト重複のROUGE指標。

    ROUGE-1: ユニグラムの重複
    ROUGE-2: バイグラムの重複
    ROUGE-L: 最長共通部分列

    使用方法: 要約、翻訳
    制限: 意味を捉えられない
    """
    scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
    scores = scorer.score(expected, predicted)

    return {
        'rouge1': scores['rouge1'].fmeasure,
        'rouge2': scores['rouge2'].fmeasure,
        'rougeL': scores['rougeL'].fmeasure
    }

# 例
scores = compute_rouge(
    predicted="Paris is the capital of France",
    expected="The capital of France is Paris"
)
# {'rouge1': 0.82, 'rouge2': 0.67, 'rougeL': 0.82}

指標3: BERTScore(意味的類似性)

from bert_score import score as bert_score

def compute_bertscore(predicted: List[str], expected: List[str]) -> dict:
    """
    BERT埋め込みを使用した意味的類似性。

    ROUGEより優れている点:
    - パラフレーズ
    - 意味的等価性
    - 生成品質

    戻り値: Precision、Recall、F1
    """
    P, R, F1 = bert_score(predicted, expected, lang="en", verbose=False)

    return {
        'precision': P.mean().item(),
        'recall': R.mean().item(),
        'f1': F1.mean().item()
    }

# 例
scores = compute_bertscore(
    predicted=["The capital of France is Paris"],
    expected=["Paris is France's capital city"]
)
# {'precision': 0.94, 'recall': 0.91, 'f1': 0.92}

指標4: パープレキシティ(モデルの信頼度)

import torch
from transformers import GPT2LMHeadModel, GPT2Tokenizer

def compute_perplexity(text: str, model_name: str = "gpt2") -> float:
    """
    パープレキシティ: モデルがこのテキストにどれだけ「驚く」か?

    低い = より可能性が高い/流暢
    使用方法: 流暢性、自然さ
    制限: 正確性を測定しない
    """
    model = GPT2LMHeadModel.from_pretrained(model_name)
    tokenizer = GPT2Tokenizer.from_pretrained(model_name)

    inputs = tokenizer(text, return_tensors="pt")

    with torch.no_grad():
        outputs = model(**inputs, labels=inputs["input_ids"])
        loss = outputs.loss

    perplexity = torch.exp(loss).item()
    return perplexity

# 例
ppl = compute_perplexity("Paris is the capital of France")  # 低い(流暢)
ppl2 = compute_perplexity("Capital France the is Paris of")  # 高い(不自然)

パート3: LLM-as-Judge評価

パターン1: ルーブリックベースのスコアリング

from openai import OpenAI

client = OpenAI()

EVALUATION_PROMPT = """
あなたは専門の評価者です。各基準について、アシスタントの応答を1~5のスケールで採点してください。

**基準:**
1. **正確性**: 情報は事実に基づいていますか?
2. **完全性**: 質問に完全に答えていますか?
3. **明確さ**: 理解しやすいですか?
4. **簡潔さ**: 適切に簡潔ですか?

**評価する応答:**
{response}

**期待される回答(参考):**
{expected}

JSON形式でスコアを提供してください:
{{
  "accuracy": <1-5>,
  "completeness": <1-5>,
  "clarity": <1-5>,
  "conciseness": <1-5>,
  "reasoning": "簡単な説明"
}}
"""

def llm_judge_score(response: str, expected: str) -> dict:
    """
    GPT-4を審査員としてルーブリックスコアリングに使用。

    利点: 柔軟、ニュアンス対応、スケール良好
    欠点: コストがかかる、バイアスの可能性、低速
    """
    prompt = EVALUATION_PROMPT.format(response=response, expected=expected)

    completion = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )

    import json
    scores = json.loads(completion.choices[0].message.content)
    return scores

# 例
scores = llm_judge_score(
    response="パリはフランスの首都で、国の中北部に位置しています。",
    expected="パリ"
)
# {'accuracy': 5, 'completeness': 5, 'clarity': 5, 'conciseness': 3, 'reasoning': '...'}

パターン2: 二値の合格/不合格評価

PASS_FAIL_PROMPT = """
アシスタントの応答が許容可能かどうかを評価してください。

**質問:** {question}
**応答:** {response}
**基準:** {criteria}

"PASS" または "FAIL" のみを返し、その後に一文で理由を記述してください。
"""

def binary_eval(question: str, response: str, criteria: str) -> tuple[bool, str]:
    """
    シンプルな合格/不合格評価。

    使用方法: 単体テスト、回帰テスト、CI/CD
    """
    prompt = PASS_FAIL_PROMPT.format(
        question=question,
        response=response,
        criteria=criteria
    )

    completion = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.0  # 決定論的
    )

    result = completion.choices[0].message.content
    passed = result.startswith("PASS")
    reason = result.split(":", 1)[1].strip() if ":" in result else result

    return passed, reason

# 例
passed, reason = binary_eval(
    question="フランスの首都は?",
    response="首都はパリです",
    criteria="応答にパリが含まれている必要があります"
)
# (True, "応答はパリを首都として正しく特定しています")

パターン3: ペアワイズ比較(A/Bテスト)

PAIRWISE_PROMPT = """
同じ質問に対する2つの応答を比較してください。どちらが優れていますか?

**質問:** {question}

**応答A:**
{response_a}

**応答B:**
{response_b}

**基準:** {criteria}

"A"、"B"、または "TIE" のみを返し、その後に一文で説明を記述してください。
"""

def pairwise_comparison(
    question: str,
    response_a: str,
    response_b: str,
    criteria: str = "全体的な品質、正確性、有用性"
) -> tuple[str, str]:
    """
    2つの応答をA/Bテストします。

    使用方法: プロンプトエンジニアリング、モデル比較
    """
    prompt = PAIRWISE_PROMPT.format(
        question=question,
        response_a=response_a,
        response_b=response_b,
        criteria=criteria
    )

    completion = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        temperature=0.0
    )

    result = completion.choices[0].message.content
    winner = result.split()[0]  # "A"、"B"、または "TIE"
    reason = result.split(":", 1)[1].strip() if ":" in result else result

    return winner, reason

# 例
winner, reason = pairwise_comparison(
    question="量子コンピューティングを説明してください",
    response_a="量子コンピュータはビットの代わりに量子ビットを使用します...",
    response_b="量子コンピューティングは複雑です。量子力学を使用します。"
)
# ("A", "応答Aはより詳細で説明が充実しています")

パート4: 幻覚検出

方法1: グラウンディングチェック

def check_grounding(response: str, context: str) -> dict:
    """
    応答が提供されたコンテキストに基づいていることを確認。

    RAGシステムでは重要。
    """
    GROUNDING_PROMPT = """
    コンテキスト: {context}

    応答: {response}

    応答はコンテキストによって完全にサポートされていますか?次のいずれかで回答してください:
    - "GROUNDED": すべての主張がサポートされている
    - "PARTIALLY_GROUNDED": 一部の主張がサポートされていない
    - "NOT_GROUNDED": サポートされていない主張が含まれている

    サポートされていない主張をリストアップしてください。
    """

    prompt = GROUNDING_PROMPT.format(context=context, response=response)

    completion = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}]
    )

    result = completion.choices[0].message.content
    status = result.split("\n")[0]
    unsupported = [line for line in result.split("\n")[1:] if line.strip()]

    return {
        'grounding_status': status,
        'unsupported_claims': unsupported,
        'is_hallucination': status != "GROUNDED"
    }

方法2: 事実性チェック(外部検証)

def check_factuality(claim: str, use_search: bool = True) -> dict:
    """
    外部ソースを使用して事実に基づく主張を検証。

    オプション:
    1. Web検索 + 検証
    2. 知識ベース検索
    3. 信頼できるソースとの相互参照
    """
    if use_search:
        # Web検索を使用して検証
        from tavily import TavilyClient
        tavily = TavilyClient(api_key="your-key")

        # 証拠を検索
        results = tavily.search(claim, max_results=3)

        # LLMに検索結果に基づいて検証させる
        VERIFY_PROMPT = """
        主張: {claim}

        検索結果:
        {results}

        この主張はこれらのソースによってサポートされていますか?TRUE、FALSE、またはUNCERTAINで回答してください。
        説明:
        """

        prompt = VERIFY_PROMPT.format(
            claim=claim,
            results="\n\n".join([r['content'] for r in results])
        )

        completion = client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}]
        )

        result = completion.choices[0].message.content
        is_factual = result.startswith("TRUE")

        return {
            'claim': claim,
            'factual': is_factual,
            'evidence': results,
            'explanation': result
        }

方法3: 自己整合性チェック

def self_consistency_check(question: str, num_samples: int = 5) -> dict:
    """
    複数の応答を生成し、一貫性をチェック。

    モデルが確信している場合、応答は一貫しているはず。
    一貫性の欠如は幻覚リスクを示唆。
    """
    responses = []

    for _ in range(num_samples):
        completion = client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": question}],
            temperature=0.7  # ある程度のランダム性
        )
        responses.append(completion.choices[0].message.content)

    # ペアワイズ類似度を計算
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.metrics.pairwise import cosine_similarity

    vectorizer = TfidfVectorizer()
    vectors = vectorizer.fit_transform(responses)
    similarities = cosine_similarity(vectors)

    # 平均ペアワイズ類似度
    avg_similarity = similarities.sum() / (len(responses) * (len(responses) - 1))

    return {
        'responses': responses,
        'avg_similarity': avg_similarity,
        'is_consistent': avg_similarity > 0.7,  # しきい値
        'confidence': 'high' if avg_similarity > 0.85 else 'medium' if avg_similarity > 0.7 else 'low'
    }

パート5: RAG固有の評価

検索品質指標

def evaluate_retrieval(query: str, retrieved_docs: List[dict], relevant_doc_ids: List[str]) -> dict:
    """
    IR指標を使用して検索品質を評価。

    Precision: 取得されたドキュメントのうち、関連するものの割合は?
    Recall: 関連するドキュメントのうち、取得されたものの割合は?
    MRR: 平均逆順位
    NDCG: 正規化割引累積利得
    """
    retrieved_ids = [doc['id']

...

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル