6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RAGの品質、なんとなくで判断していませんか? ― Amazon BedrockとRagasで始めるLLM-as-a-Judge評価パイプライン

6
Last updated at Posted at 2026-03-23

はじめに

RAGを構築して社内ドキュメント検索やFAQボットをリリースした。ユーザーからは「まあまあ使える」という声もあれば「的外れな回答が返ってくる」という声もある。

プロンプトを修正してみた。チャンク分割の方法を変えてみた。Embeddingモデルを変えてみた。でも、改善されたのかどうかが分からない

こうした「なんとなく良さそう」「なんとなく悪くなった気がする」という状態でRAGを運用していませんか?

本記事では、LLM-as-a-Judgeという手法とRagasというOSSフレームワークを使い、Amazon Bedrock上のRAGパイプラインを定量的に評価する方法を解説します。

なぜRAGの評価が難しいのか

従来のNLP評価手法(BLEU, ROUGEなど)は、正解文と生成文の単語レベルの一致度を測るものです。
しかしRAGの回答は自然言語であり、同じ意味でも表現が異なることが多いため、これらの指標では正確に品質を測れません。

さらにRAGには検索と生成の2つのステップがあり、問題の切り分けが困難です。

回答の品質が低い
  ├── 検索が悪い? → 関連文書を取得できていない
  └── 生成が悪い? → 文書はあるのに回答に活かせていない

「回答がイマイチ」という事象だけでは、チャンク分割を見直すべきなのか、プロンプトを改善すべきなのか判断できません。

LLM-as-a-Judgeとは

LLM-as-a-Judgeは、LLM自身に回答の品質を採点させる手法です。
人間の評価者の代わりにLLMが「この回答は質問に対して適切か」「根拠に基づいているか」を判定します。

従来の評価手法との比較

手法 メリット デメリット
人手評価 最も正確 コストが高い、スケールしない
BLEU/ROUGE 自動化できる 意味的な一致を測れない
LLM-as-a-Judge 意味レベルで自動評価 Judge自体のバイアスに注意が必要

LLM-as-a-Judgeは人手評価との相関が高いことが複数の研究で報告されており、RAG評価の実用的なアプローチとして注目されています。

注意点

  • Judgeモデルの品質が評価精度に直結する(高性能なモデルを使うべき)
  • 自身が生成した回答を高く評価しやすいバイアスがある(Self-Enhancement Bias)
  • 回答の順番によるバイアスがある(Position Bias)

これらのバイアスを理解した上で活用することが重要です。

Ragasとは ― RAG評価を「分解」するフレームワーク

Ragas(Retrieval Augmented Generation Assessment)は、RAGパイプラインの品質をコンポーネント単位で評価するPython OSSフレームワークです。

Ragasの最大の強みは、検索の品質と生成の品質を分離して評価できる点です。

主要メトリクス

メトリクス 評価対象 何を測るか 値の意味
Faithfulness 生成 回答が検索された文書に基づいているか 低い→ハルシネーションの可能性
Answer Relevancy 生成 回答が質問に対して的確か 低い→質問と無関係な回答
Context Precision 検索 関連性の高い文書が上位に来ているか 低い→ランキングの問題
Context Recall 検索 正解に必要な情報がすべて検索できているか 低い→検索漏れ

この4つのメトリクスにより、「検索が悪いのか、生成が悪いのか」を定量的に切り分けられます。

評価結果から改善アクションへ

スコアパターン 推定される問題 改善アクション
Faithfulnessだけ低い 生成時のハルシネーション プロンプト改善、temperatureの調整
Context Recallが低い 検索漏れ チャンク戦略の見直し、Embeddingモデル変更
Context Precisionが低い 不要な文書が混入 top_kの調整、リランキングの導入
Answer Relevancyが低い 回答が質問とずれている プロンプトの指示を明確化

構成概要 ― BedrockとRagasの組み合わせ

アーキテクチャ

なぜこの組み合わせか?

  • Bedrock Knowledge Baseだけでは「検索品質」と「生成品質」の切り分けができない
  • RagasがBedrockの回答とコンテキストを受け取り、コンポーネント別に評価を行う
  • 評価のJudgeモデルとしてもBedrock上のClaudeを利用できるため、AWS内で完結する

ハンズオン ― Bedrock Knowledge BaseのRAGをRagasで評価する

前提条件

  • AWSアカウントとクレデンシャル設定済み
  • Bedrock Knowledge Baseが構築済み(未構築の場合は公式ガイドを参照)
  • Bedrockモデルアクセスが有効(Claude, Titanなど)
  • Python 3.10以上

Bedrockの一部モデルは推論プロファイルID(例: apac.anthropic.claude-3-5-sonnet-20240620-v1:0)での呼び出しが必要です。従来のモデルARN形式(arn:aws:bedrock:...)ではValidationExceptionが発生する場合があります。利用可能な推論プロファイルはaws bedrock list-inference-profilesで確認できます。

Step 1: 環境セットアップ

mkdir bedrock-ragas-eval
cd bedrock-ragas-eval
python3 -m venv .venv
source .venv/bin/activate

pip install ragas langchain-aws boto3 pandas

Step 2: テストデータセットを準備する

評価には「質問」「正解(Ground Truth)」のペアが必要です。実際のユースケースに合わせて作成してください。

# test_dataset.py
"""テストデータセットの定義"""

test_questions = [
    "Amazon S3のストレージクラスにはどのような種類がありますか?",
    "S3のバージョニング機能とは何ですか?",
    "S3のライフサイクルポリシーで何ができますか?",
    "S3のクロスリージョンレプリケーションの設定方法は?",
    "S3 Glacierからデータを復元する方法は?",
]

ground_truths = [
    "S3にはS3 Standard、S3 Intelligent-Tiering、S3 Standard-IA、S3 One Zone-IA、S3 Glacier Instant Retrieval、S3 Glacier Flexible Retrieval、S3 Glacier Deep Archiveの7つのストレージクラスがあります。",
    "バージョニングはオブジェクトの複数バージョンを同一バケットに保持する機能です。誤削除や上書きからの復旧が可能になります。",
    "ライフサイクルポリシーを使用すると、オブジェクトのストレージクラスの自動移行や、有効期限切れオブジェクトの自動削除ができます。",
    "レプリケーション元バケットでバージョニングを有効にし、レプリケーションルールで送信先バケットとIAMロールを指定して設定します。",
    "復元リクエストをAPIまたはコンソールから発行します。Glacier Flexible Retrievalでは迅速取り出し(1-5分)、標準取り出し(3-5時間)、大容量取り出し(5-12時間)の3つのオプションがあります。",
]

テストデータセットの品質が評価精度に直結します。実際のユーザーが投げそうな質問を10〜50件程度用意するのが実用的です。

Step 3: Bedrock Knowledge Baseから回答を取得する

# retrieve_answers.py
"""Bedrock Knowledge Baseから回答とコンテキストを取得"""

import boto3


def retrieve_and_generate(knowledge_base_id, model_arn, question, custom_prompt=None):
    """Knowledge Baseに質問してRAGの回答とコンテキストを取得する"""
    client = boto3.client("bedrock-agent-runtime", region_name="ap-northeast-1")

    kb_config = {
        "knowledgeBaseId": knowledge_base_id,
        "modelArn": model_arn,
        "retrievalConfiguration": {
            "vectorSearchConfiguration": {
                "numberOfResults": 5
            }
        },
    }

    # カスタムプロンプトが指定された場合は生成設定に追加
    if custom_prompt:
        kb_config["generationConfiguration"] = {
            "promptTemplate": {
                "textPromptTemplate": custom_prompt
            }
        }

    response = client.retrieve_and_generate(
        input={"text": question},
        retrieveAndGenerateConfiguration={
            "type": "KNOWLEDGE_BASE",
            "knowledgeBaseConfiguration": kb_config,
        },
    )

    # 回答テキスト
    answer = response["output"]["text"]

    # 検索されたコンテキスト(引用元)
    contexts = []
    for citation in response.get("citations", []):
        for ref in citation.get("retrievedReferences", []):
            text = ref.get("content", {}).get("text", "")
            if text:
                contexts.append(text)

    # カスタムプロンプト使用時はcitationsにcontextが含まれない場合がある
    # その場合はretrieve APIで別途取得する
    if not contexts:
        retrieve_response = client.retrieve(
            knowledgeBaseId=knowledge_base_id,
            retrievalQuery={"text": question},
            retrievalConfiguration={
                "vectorSearchConfiguration": {
                    "numberOfResults": 5
                }
            },
        )
        for result in retrieve_response.get("retrievalResults", []):
            text = result.get("content", {}).get("text", "")
            if text:
                contexts.append(text)

    return answer, contexts


def collect_rag_responses(knowledge_base_id, model_arn, questions, custom_prompt=None):
    """全テスト質問に対してRAGの回答を収集する"""
    answers = []
    contexts = []

    for i, question in enumerate(questions):
        print(f"[{i+1}/{len(questions)}] {question[:40]}...")
        answer, ctx = retrieve_and_generate(
            knowledge_base_id, model_arn, question, custom_prompt
        )
        answers.append(answer)
        contexts.append(ctx)

    return answers, contexts

Step 4: Ragasで評価を実行する

# evaluate_rag.py
"""RagasによるRAG評価の実行"""

from ragas import evaluate
from ragas.metrics import (
    Faithfulness,
    ResponseRelevancy,
    LLMContextPrecisionWithoutReference,
    LLMContextRecall,
)
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
from ragas.dataset_schema import SingleTurnSample, EvaluationDataset
from langchain_aws import ChatBedrock, BedrockEmbeddings

from test_dataset import test_questions, ground_truths
from retrieve_answers import collect_rag_responses


def main():
    # ===== 設定 =====
    knowledge_base_id = "<YOUR_KNOWLEDGE_BASE_ID>"
    model_arn = "<YOUR_INFERENCE_PROFILE_ID>"  # 例: apac.anthropic.claude-3-5-sonnet-20240620-v1:0
    region = "ap-northeast-1"

    # ===== Step 1: RAG回答を収集 =====
    print("RAG回答を収集中...")
    answers, contexts = collect_rag_responses(
        knowledge_base_id, model_arn, test_questions
    )

    # ===== Step 2: Ragasデータセットを構築 =====
    samples = []
    for q, a, c, gt in zip(test_questions, answers, contexts, ground_truths):
        sample = SingleTurnSample(
            user_input=q,
            response=a,
            retrieved_contexts=c,
            reference=gt,
        )
        samples.append(sample)

    eval_dataset = EvaluationDataset(samples=samples)

    # ===== Step 3: Judge用のLLMとEmbeddingを設定 =====
    judge_llm = LangchainLLMWrapper(
        ChatBedrock(
            model_id="<YOUR_INFERENCE_PROFILE_ID>",  # 例: apac.anthropic.claude-3-5-sonnet-20241022-v2:0
            region_name=region,
            model_kwargs={"max_tokens": 4096},
        )
    )
    judge_embeddings = LangchainEmbeddingsWrapper(
        BedrockEmbeddings(
            model_id="amazon.titan-embed-text-v2:0",
            region_name=region,
        )
    )

    # ===== Step 4: 評価メトリクスを定義 =====
    metrics = [
        Faithfulness(),
        ResponseRelevancy(),
        LLMContextPrecisionWithoutReference(),
        LLMContextRecall(),
    ]

    # ===== Step 5: 評価実行 =====
    print("Ragas評価を実行中...")
    results = evaluate(
        dataset=eval_dataset,
        metrics=metrics,
        llm=judge_llm,
        embeddings=judge_embeddings,
    )

    # ===== 結果表示 =====
    print("\n" + "=" * 50)
    print("評価結果")
    print("=" * 50)

    df = results.to_pandas()
    metric_columns = [
        "faithfulness",
        "answer_relevancy",
        "llm_context_precision_without_reference",
        "context_recall",
    ]
    for col in metric_columns:
        if col in df.columns:
            print(f"  {col}: {df[col].mean():.3f}")

    # 質問ごとの詳細
    print("\n質問ごとの詳細:")
    print(df[["user_input"] + metric_columns].to_string())

    # CSVに保存
    df.to_csv("evaluation_results.csv", index=False)
    print("\n結果をevaluation_results.csvに保存しました")


if __name__ == "__main__":
    main()

Step 5: 評価を実行する

python evaluate_rag.py

Ragas v0.4系ではragas.metricsからのimportやLangchainLLMWrapperに対してDeprecationWarningが表示されますが、動作に問題はありません。将来のv1.0でragas.metrics.collectionsへの移行が予定されています。

評価結果の例

==================================================
評価結果
==================================================
  faithfulness: 0.858
  answer_relevancy: 0.673
  llm_context_precision_without_reference: 0.700
  context_recall: 0.800

この結果から以下のことが読み取れます。

メトリクス スコア 解釈
Faithfulness 0.858 回答は概ね検索文書に基づいている(ハルシネーション少)
Answer Relevancy 0.673 回答が質問に対してやや冗長・不正確な傾向
Context Precision 0.700 検索結果の上位に関連文書が来ているが改善余地あり
Context Recall 0.800 必要な情報は概ね取得できている

→ この場合、検索よりも生成側に改善余地があると判断できます。特にAnswer Relevancyが0.673と低く、回答が冗長になっている可能性があります。またFaithfulnessも0.858で、検索結果にない情報を補足してしまうケースがありそうです。プロンプトを調整して改善を試みましょう。

Step 6: 改善して再評価する(評価サイクル)

生成側のスコア(Faithfulness, Answer Relevancy)を改善するため、プロンプトテンプレートをカスタマイズします。

Step 3で作成したcollect_rag_responsescustom_prompt引数を受け取れるようになっています。カスタムプロンプトを定義して渡すだけです。

IMPROVED_PROMPT = """あなたはAWSの技術ドキュメントに基づいて質問に回答するアシスタントです。
以下のルールに従って回答してください:

- 検索結果に含まれる情報のみを使用し、推測や補足は行わないでください
- 質問に対して簡潔かつ的確に回答してください
- 箇条書きを活用し、要点を明確にしてください

検索結果:
$search_results$

ユーザーの質問: $query$

上記の検索結果のみに基づいて、簡潔に回答してください。"""

# custom_prompt引数にプロンプトを渡して再実行
answers, contexts = collect_rag_responses(
    knowledge_base_id, model_arn, test_questions, custom_prompt=IMPROVED_PROMPT
)

同じスクリプトで再評価した結果:

メトリクス                                  改善前    改善後    差分
faithfulness                               0.858    0.906   +0.048 ↑
answer_relevancy                           0.673    0.689   +0.016 ↑
llm_context_precision_without_reference    0.700    0.800   +0.100 ↑
context_recall                             0.800    0.800    0.000 →

Faithfulnessが0.858→0.906に改善しました。「検索結果に含まれる情報のみを使用」という指示によりハルシネーションが減少したことが数値に表れています。Context Precisionも0.700→0.800に向上しました。

一方、Answer Relevancyは0.673→0.689とほぼ横ばいでした。回答の簡潔さはまだ改善の余地があります。この場合、次の改善サイクルとして「回答は3文以内で」といったより具体的な制約を加える、あるいはモデル自体を変更するなどのアプローチが考えられます。

このように、どの施策がどのメトリクスに効いたか(あるいは効かなかったか)を定量的に把握できるのがRagas評価の最大の価値です。

カスタムプロンプト使用時はretrieveAndGenerateのcitationsにコンテキストが含まれない場合があります。その際はretrieve APIで別途コンテキストを取得してください。

どこに導入できるか ― 実践ユースケース

1. 開発フェーズ: パラメータチューニングのA/Bテスト

  • チャンクサイズ、top_k、プロンプトなどのパラメータ変更の効果を定量比較
  • 「なんとなく良くなった」ではなく「Faithfulnessが0.85→0.92に改善」と説明できる

2. リリース判定: 品質ゲートとしての活用

CI/CDパイプラインに評価を組み込み、品質スコアが閾値を下回る場合はデプロイをブロックします。

# ci_quality_gate.py
"""CI/CDパイプラインでの品質ゲート(例)"""

THRESHOLDS = {
    "faithfulness": 0.80,
    "answer_relevancy": 0.80,
    "llm_context_precision_without_reference": 0.70,
    "context_recall": 0.70,
}

def check_quality_gate(results):
    """評価結果が閾値を満たすか判定する"""
    df = results.to_pandas()
    passed = True
    for metric, threshold in THRESHOLDS.items():
        if metric not in df.columns:
            continue
        score = df[metric].mean()
        status = "PASS" if score >= threshold else "FAIL"
        if score < threshold:
            passed = False
        print(f"  {metric}: {score:.3f} (threshold: {threshold}) [{status}]")
    return passed

3. 運用監視: 定期評価による品質劣化検知

ナレッジベースのドキュメントが追加・更新されると、検索品質が変動する可能性があります。週次や月次で評価を回し、スコアの推移をモニタリングします。

対象 検知できること
ドキュメント追加後 新規文書がノイズになっていないか
モデル更新後 生成品質が維持されているか
プロンプト変更後 意図した改善が実現されているか

4. 社内RAG: FAQボット・ナレッジ検索の継続改善

社内RAGではユーザーからのフィードバック(👍/👎)を収集し、低評価の質問をテストデータセットに追加していくことで、評価の精度と網羅性を継続的に高められます。

ユーザーフィードバック → テストデータセット追加 → Ragas評価 → 改善 → 再評価

まとめ

本記事では、Amazon BedrockのKnowledge BaseとRagasを組み合わせたRAG評価パイプラインを紹介しました。

ポイント:

  • LLM-as-a-JudgeはLLMが回答を自動採点する手法で、人手評価に近い精度を自動化できる
  • Ragasは検索と生成を分離して評価でき、問題の原因特定に直結する
  • Bedrock + Ragasの組み合わせにより、AWS内で完結する評価パイプラインが構築できる
  • 開発時のA/Bテスト、リリース判定、運用監視などRAGのライフサイクル全体で活用できる

RAGの品質管理を「なんとなく」から「定量的」に変えることで、改善サイクルが回り始めます。まずは小さなテストデータセットから始めてみてください。

参考リンク

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?