コンテキストと重要性:LLM評価が実際に重要な理由
AIの機能をリリースします。するとユーザーから、botが間違った回答をするという苦情が来始めます。ログを確認すると、モデルは実行され、200 OKが返り、レイテンシも問題なさそうです。しかし、回答の内容はひどいものでした。これが、LLM評価が埋めようとしているギャップです。
従来のソフトウェアテストは二値です:関数が正しい値を返すかどうか?LLMはそのようには機能しません。モデルは流暢で自信に満ちた口調でありながら、事実として誤っていたり、話題外だったり、有害な回答を返すことがあります。構造化された評価がなければ、本番環境で盲目的に飛び続けているようなものです。
私はカスタマーサポートボットでこれを痛感しました。レスポンスタイムは優秀で、稼働時間も完璧で、システムは安定していると思っていました。しかし、マネージャーから、ボットが顧客に「保証期限が切れた」と伝えているスクリーンショットが転送されてきました。実際にはまだ有効だったにもかかわらず。モデルがハルシネーションを起こしていたのです。私のモニタリングパイプラインは何も検知していませんでした。
評価レイヤーをゼロから再構築した後、この設定を約6ヶ月間本番環境で運用してきました。失敗したレスポンスは、サンプリングしたトラフィックの約8%から2%未満に減少しました。これらが実際に改善に貢献した要素です。
実際に何を測定しているのか?
LLM評価は2つの大きなカテゴリに分かれます:
- 参照ベース:既知の正解があり、モデルの出力をそれと比較します(完全一致、BLEU、ROUGE、意味的類似度)。
- 参照なし:正解データなし — 別のモデルをジャッジとして使用して、一貫性、関連性、忠実性、安全性などのプロパティを評価します(LLM-as-judge)。
ほとんどの本番アプリでは、両方が必要です。参照ベースは事実のリグレッションを検知し、参照なしはトーン、フォーマット、推論の品質を検知します。
インストール:評価スタックのセットアップ
私が最もよく使う2つのライブラリは、DeepEval(汎用LLMテスト)とRAGAS(RAGパイプライン専用)です。プロジェクトの仮想環境に両方をインストールします:
pip install deepeval ragas openai anthropic
DeepEvalはpytestと統合されているため、評価をCIパイプラインの一部として実行できます — まさにあるべき場所です。RAGASはフレームワーク非依存で、LangChain、LlamaIndex、またはプレーンPythonで動作します。
RAGアプリケーションの場合
アプリがRetrieval-Augmented Generationを使用している場合、RAGASはすぐに使える4つのコアメトリクスを提供します:
- Faithfulness(忠実性):回答は取得されたコンテキストに忠実ですか?
- Answer Relevancy(回答関連性):回答は実際に質問に関連していますか?
- Context Precision(コンテキスト精度):適切なチャンクが取得されましたか?
- Context Recall(コンテキスト再現率):必要なチャンクがすべて含まれていましたか?
pip install ragas datasets
設定:メトリクスとテストケースの定義
DeepEvalテストスイートの構築
最も重要なステップは、ゴールデンデータセットの構築です — 期待される出力または評価基準を持つ入力のセットです。これをスキップしてはいけません。50個の厳選されたテストケースでも、1,000個のランダムなものより多くのリグレッションを検知できます。
from deepeval import evaluate
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric, HallucinationMetric
from deepeval.test_case import LLMTestCase
# テストケースを定義する
test_case = LLMTestCase(
input="製品Xの保証期間はどのくらいですか?",
actual_output="製品Xには2年間の保証が付いています。",
expected_output="製品Xは購入日から2年間の保証があります。",
retrieval_context=[
"製品Xの保証:購入日から2年間、製造上の欠陥を対象とします。"
]
)
# メトリクスを設定する
answer_relevancy = AnswerRelevancyMetric(threshold=0.7)
faithfulness = FaithfulnessMetric(threshold=0.8)
hallucination = HallucinationMetric(threshold=0.2) # 低いほど良い
# 評価を実行する
evaluate([test_case], [answer_relevancy, faithfulness, hallucination])
閾値の選択は、ほとんどのチュートリアルが認めるよりも重要です。緩すぎると何も検知できません。厳しすぎると、デプロイのたびに誤検知が発生します — チームがCIの失敗を無視し始め、本来の目的が台無しになります。私はまず全体的に0.7から始め、どのスコア範囲が実際にユーザーの苦情を予測するかを学びながら、個々のメトリクスを厳しくしていきます。
RAGパイプラインでRAGASを実行する
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
# パイプラインの出力 — 実際の実行から収集する
data = {
"question": ["パスワードをリセットするにはどうすればよいですか?"],
"answer": ["設定 > セキュリティ > パスワードのリセット に移動し、手順に従ってください。"],
"contexts": [[
"パスワードをリセットするには:設定に移動し、セキュリティをクリックしてから、パスワードのリセットをクリックしてください。"
]],
"ground_truth": ["ユーザーは設定 > セキュリティ > パスワードのリセット からパスワードをリセットできます。"]
}
dataset = Dataset.from_dict(data)
result = evaluate(
dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall]
)
print(result)
# 出力例: {'faithfulness': 0.97, 'answer_relevancy': 0.89, ...}
オープンエンドな品質評価のためのLLM-as-Judge
数値メトリクスで測定しにくいものもあります — トーン、プロフェッショナリズム、文化的適切さ。これらについては、別のモデルをジャッジとして使用します。重要なのは、厳格で曖昧さのないルーブリックを書くことです:
import anthropic
client = anthropic.Anthropic()
def judge_response(question: str, answer: str) -> dict:
prompt = f"""あなたは厳格な品質評価者です。このAIの回答を1〜5のスケールで評価してください。
評価基準:
- 正確性(1〜5):情報は正確ですか?
- トーン(1〜5):プロフェッショナルで役立つ内容ですか?
- 完全性(1〜5):質問に完全に答えていますか?
質問:{question}
回答:{answer}
JSONオブジェクトのみを返してください:{{"accuracy": X, "tone": X, "completeness": X, "reasoning": "簡単な説明"}}"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=256,
messages=[{"role": "user", "content": prompt}]
)
import json
return json.loads(response.content[0].text)
# 使用例
scores = judge_response(
"返金ポリシーについて教えてください。",
"全商品に30日間の返金保証をご用意しています。"
)
print(scores)
注意点:LLMジャッジは完璧ではありません。短い回答の方が正確な場合でも、より長く自信に満ちた口調の回答を好む傾向があります。少なくとも月に1回、ジャッジの出力を人間の評価と照合して確認しましょう。
検証と監視:時間をかけて品質を安定させる
CI/CDへの評価の統合
DeepEvalにはpytestプラグインが付属しています。これをテストスイートに追加することで、閾値を下回るリグレッションが発生したデプロイをブロックできます:
# tests/test_llm_quality.py
import pytest
from deepeval import assert_test
from deepeval.metrics import AnswerRelevancyMetric
from deepeval.test_case import LLMTestCase
@pytest.mark.parametrize("test_case", load_golden_dataset())
def test_answer_quality(test_case):
metric = AnswerRelevancyMetric(threshold=0.75)
assert_test(test_case, [metric])
deepeval test run tests/test_llm_quality.py
プロンプトテンプレート、取得ロジック、またはモデルバージョンに触れるすべてのPRでこれを実行しています。ユーザーに届く前に、少なくとも十数件の微妙なリグレッションを検知しています。
サンプリングによる本番環境の監視
すべての本番リクエストで完全な評価を実行するのはコストがかかります。代わりに実際のトラフィックの一定割合をサンプリングし、非同期で評価します:
import random
import asyncio
async def maybe_evaluate(question: str, answer: str, context: list[str]):
# 本番トラフィックの5%を評価する
if random.random() > 0.05:
return
scores = await run_ragas_async(question, answer, context)
# モニタリングシステムにログを記録する
log_metric("llm.faithfulness", scores["faithfulness"])
log_metric("llm.answer_relevancy", scores["answer_relevancy"])
# スコアが閾値を下回った場合にアラートを送信する
if scores["faithfulness"] < 0.6:
send_alert(f"忠実性スコアが{scores['faithfulness']:.2f}に低下しました")
時間をかけて追跡するメトリクス
これらのシグナルを使った簡単なダッシュボードを構築しましょう — スプレッドシートでも最初は十分です:
- 忠実性スコア(週次平均)— ハルシネーション検知器
- 回答関連性スコア — トピックドリフトを測定
- ユーザーのいいね/よくないねの比率 — 実際のユーザーからの正解データ
- エスカレーション率 — ボットが失敗して人間に引き継ぐ頻度
- レイテンシP95 — モデルを切り替えると品質のリグレッションとレイテンシのスパイクが一緒に来ることが多い
自動化されたスコアとユーザーフィードバックは、どちらか単独よりも合わせた方が信頼性が高くなります。RAGASの忠実性スコアが0.9以上を維持している間に、よくないね評価が週を追うごとに増加しているケースを見たことがあります。実は、モデルが6ヶ月前のコンテキストを忠実に引用していたのです。両方のシグナルを使うことで1週間以内に問題を検知しました。どちらか一方だけでは発見できなかったでしょう。
スコアが悪化した場合のデバッグチェックリスト
- プロバイダーによってベースモデルが更新されていないか確認する — サイレントな変更は、ベンダーが認めるよりも頻繁に発生します。
- 最近のプロンプトテンプレートの変更を確認する。1文追加するだけでも、スコア分布の末尾を見るまでわからない形で動作が変化することがあります。
- 取得パイプラインを確認する — コンテキスト精度が下がっている場合、モデルが無関係なチャンクを受け取っています。
- 集計スコアだけでなく、実際に失敗したテストケースを確認する。失敗のパターンは、どんな要約メトリクスよりも根本原因を早く明らかにします。
評価は一度だけのセットアップではありません。アプリが成長し、モデルが変化するにつれて維持するフィードバックループです。小さく始めましょう — 2〜3のメトリクスを持つ20個の適切にラベル付けされた例の方が、実際には維持しない凝ったフレームワークよりも優れています。自分のコンテキストで実際のユーザー満足度を確実に予測するシグナルを特定した後にのみ、複雑さを追加してください。

