チュートリアルOct 6, 202519 分 READ

RAGAS による RAG パイプラインの自動評価とベンチマーク

Muhammad Arham

Senior NLP Researcher

検索拡張生成(RAG:Retrieval-Augmented Generation)パイプラインは、大規模言語モデル(LLM)が学習データ以外の外部情報にアクセスし、正確で根拠に基づいた回答を提供するために不可欠です。しかし、その性能評価は容易ではありません。回答の質が低い原因が「リトリーバー(検索)」にあるのか、それとも「ジェネレーター(生成)」にあるのかを特定する作業は、手動では時間がかかり、主観に左右されやすいからです。

信頼性の高い RAG アプリを構築するには、定量的なメトリクスが必要です。従来の LLM 向けメトリクスである BLEU(機械翻訳向け)や ROUGE(要約向け)は、リトリーバーとジェネレーターの整合性を評価する RAG の文脈理解には十分ではありません。

そこで役立つのが RAGAS (RAG Assessment) です。RAGAS は、RAG の品質を評価する専用フレームワークであり、忠実性 や文脈の関連性といった重要な指標を測定できます。これらの指標を用いることで、RAG システムの性能をベンチマークし、改善状況の追跡やリグレッション(性能低下)の診断が可能になります。

ベンチマークは継続的に適用してこそ価値があります。システムの進化に合わせて品質を維持するには、RAG 評価を CI/CD パイプラインに統合することが不可欠です。CircleCI などのツールを活用して評価を自動化すれば、コード変更のたびに性能チェックが実行され、ユーザーに影響が及ぶ前にリグレッションを検知できます。これにより、コードベースが変化しても RAG パイプラインの安定性を長期的に担保できるようになります。

このチュートリアルでは、LangChain を用いたオーケストレーションと FAISS を用いたベクターストアによって、シンプルな RAG パイプラインを構築します。ナレッジベースには実際の RAG ベンチマークデータセットである databricks/dolly-15k を使用します。そのうえで、RAGAS を統合して自動的にメトリクス駆動で評価できるようにします。

最後に、CircleCI のワークフロー上でこれらの評価を自動で実行し、継続的に品質を保証する方法を紹介します。LLM と埋め込みモデルには TogetherAI の API ベースのモデルを使用し、CI 環境での認証情報の安全な管理方法についても解説します。

前提条件

本チュートリアルを進めるには、オートメーションパイプラインを実行するための GitHub と CircleCI のアカウントが必要です。また、LLM プロバイダーとして TogetherAI を利用します。TogetherAI は無料クレジット($25)が付与されるため、本ガイドを最後まで進めるには十分です。

具体的なセットアップ項目は以下の通りです:

必要なパッケージのインストールとセットアップ

まずは Python の環境を整え、必要なライブラリをインストールします。新しくプロジェクト用ディレクトリを作成し、その中に移動して次のコマンドを実行します。

mkdir rag-evaluation-pipeline
cd rag-evaluation-pipeline

次に、プロジェクトの依存関係を管理するために仮想環境を作成することをおすすめします。仮想環境を使用することで、システム全体の Python とは独立したパッケージ環境を用意できます。

新しい仮想環境を作成して有効化するには、ターミナルで次を実行します。

python3 -m venv venv 
source venv/bin/activate # On Windows, use `venv\Scripts\activate`

このチュートリアルでは、RAG パイプラインの構築・評価・自動化に必要な複数の外部 Python パッケージを利用します。

使用する主なパッケージは以下のとおりです。

  • langchain:LLM アプリケーション構築のためのコア・フレームワークです。RAG パイプラインのオーケストレーション、LLM との接続、リトリーバーの管理などを担います。
  • Faiss-cpu:高密度ベクトルの類似検索やクラスタリングを高速に処理できるライブラリです。関連ドキュメントを高速に取得するためのベクトルストアとして機能します。
  • ragas:RAG パイプラインの評価に特化したフレームワークです。忠実性や文脈の関連性といった、RAG の品質を測るための専用メトリクスを提供します。
  • datasets:Hugging Face Hub からデータセットを簡単に読み込み、管理するためのツールです。本チュートリアルでは databricks/dolly-15k をロードするために使用します。

次に、プロジェクトのルートに requirements.txt ファイルを作成し、以下を追加します:

ragas==0.2.15
faiss-cpu==1.11.0
langchain==0.3.25
together=1.5.8
datasets==3.6.0
numpy==2.2.6
pandas==2.2.3
langchain-together==0.3.0

以下のパッケージを pip でインストールします:

pip install -r requirements.txt

API キーの設定

RAG パイプラインから TogetherAI のモデルにアクセスするには、API キーの設定が必要です。ここで重要な注意点があります。API キーをスクリプト内に直接記述(ハードコード)したり、そのままバージョン管理システム(GitHub など)にコミットしたりすることは絶対に避けてください。これはセキュリティ上の大きなリスクを招きます。

代わりに、環境変数を利用してください。 プロジェクトのルートディレクトリに .env という名前のファイルを作成し、TogetherAI の API キーを次のように記述します。

TOGETHER_API_KEY=<YOUR_TOGETHER_API_KEY_HERE>

YOUR_TOGETHER_API_KEY_HERE の部分は、TogetherAI のアカウントで取得した API キーに置き換えてください。まだ取得していない場合は、TogetherAI の 設定ページから発行できます

なお、Python スクリプト内で .env ファイルを直接読み込むことはしません。CircleCI では環境変数を別の方法で扱うためです。ただし、ローカル開発では .env を用意しておくことで環境変数の管理がしやすくなり、必要な変数の一覧としても機能します。 Python のコード側では、この値を環境変数として読み込みます。

RAG 評価用データセットの準備

RAG パイプラインを適切に評価するには、使用するデータセットの質と構造が重要です。RAG 評価に適したデータセットには、通常、各データ項目に対して次の 3 つの要素が含まれている必要があります。

  • クエリ(質問):ユーザーが RAG パイプラインに入力する問い。
  • 文脈の関連性:回答の根拠となる事実が含まれたドキュメント。リトリーバーの精度を評価するために不可欠。
  • グラウンド・トゥルース(Ground Truth):検証済みの正しい回答です。ジェネレーターがどれだけ正確に回答できるかを評価するために使用される。

こうした評価用データセットを手動で作成することもできますが、膨大な時間がかかります。そこで今回は、Hugging Face Hub で公開されている高品質なデータセット databricks/databricks-dolly-15k を活用します。これは人手で作成されたデータセットで、今回のチュートリアルに最適な closed_qa というカテゴリが含まれています。このカテゴリには、以下の3つの要素が揃っています。

  • Instruction:クエリ
  • Context:関連する文脈
  • Response:グラウンド・トゥルース

CI/CD 上で効率よく評価を行い、API コストを抑えるため、このチュートリアルではデータセット全体は使用しません。代わりに、次の 2 つのサンプルサイズを用いて部分的にデータを利用します。

  • DOCUMENTS_SAMPLE_SIZE:ベクトルストアに投入するドキュメントの数
  • QUERY_SAMPLE_SIZE:実際に RAG 評価を行う際のクエリ数

これらのパラメータは main.py 内で設定でき、後ほど CircleCI のパイプラインパラメータとしても変更できます。評価の精度と計算負荷のバランスを調整するための仕組みであり、必要に応じて増減してより包括的な評価を行うこともできます。

rag_pipeline/dataloader.py ファイルを作成し、データセットを読み込み・加工するために次のコードを追加します。

# File Name: rag_pipeline/dataloader.py

import typing as T
import os
from datasets import load_dataset, Dataset
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document

class DollyDataLoader:
    """Loads and preprocesses the Dolly dataset for RAG evaluation"""
    def __init__(
        self,
        dataset_path: str = "databricks/databricks-dolly-15k",
        category: str = "closed_qa",
        split: str = "train",
        sample_size: T.Union[int, None] = None
    ) -> None:
        self.dataset_path = dataset_path
        self.category = category
        self.sample_size = sample_size
        self.split = split
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)

    def load_data(self) -> T.Tuple[T.List[Document], Dataset]:
        """
        Loads the Dolly dataset, filters by closed_qa category, samples, and prepares documents for the vector store.

        Returns:
            A tuple containing:
            - List[Document]: LangChain Document objects for the vector store.
            - Dataset: Sampled Hugging Face dataset for RAG queries and ground truths.
        """

        dataset = load_dataset(self.dataset_path, split=self.split)
        dataset = dataset.filter(
            lambda x: x['category'] == self.category and x['context'] is not None and x['context'].strip() != "",
            num_proc=os.cpu_count()
        )
        if self.sample_size:
            dataset = dataset.shuffle(seed=42).select(
                range(min(self.sample_size, len(dataset)))
            )

        documents_content = [item["context"] for item in dataset]
        langchain_documents = [Document(page_content=content) for content in documents_content]
        return self.text_splitter.split_documents(langchain_documents), dataset

RAG パイプラインの構築

RAG パイプラインを適切に評価するには、使用するデータセットの質と構造が重要です。RAG 評価に適したデータセットには、通常、各データ項目に対して次の 3 つの要素が含まれている必要があります。

  • クエリ(質問):ユーザーが RAG パイプラインに入力する問い。
  • 文脈の関連性:回答の根拠となる事実が含まれたドキュメント。リトリーバーの精度を評価するために不可欠。
  • グラウンド・トゥルース(Ground Truth):検証済みの正しい回答です。ジェネレーターがどれだけ正確に回答できるかを評価するために使用される。

こうした評価用データセットを手動で作成することもできますが、膨大な時間がかかります。そこで今回は、Hugging Face Hub で公開されている高品質なデータセット databricks/databricks-dolly-15k を活用します。これは人手で作成されたデータセットで、今回のチュートリアルに最適な closed_qa というカテゴリが含まれています。このカテゴリには、以下の3つの要素が揃っています。

  • Instruction:クエリ
  • Context:関連する文脈
  • Response:グラウンド・トゥルース

CI/CD 上で効率よく評価を行い、API コストを抑えるため、このチュートリアルではデータセット全体は使用しません。代わりに、次の 2 つのサンプルサイズを用いて部分的にデータを利用します。

  • DOCUMENTS_SAMPLE_SIZE:ベクトルストアに投入するドキュメントの数
  • QUERY_SAMPLE_SIZE:実際に RAG 評価を行う際のクエリ数

これらのパラメータは main.py 内で設定でき、後ほど CircleCI のパイプラインパラメータとしても変更できます。評価の精度と計算負荷のバランスを調整するための仕組みであり、必要に応じて増減してより包括的な評価を行うこともできます。

rag_pipeline/dataloader.py ファイルを作成し、データセットを読み込み・加工するために次のコードを追加します。

# File Name: rag_pipeline/dataloader.py

import typing as T
import os
from datasets import load_dataset, Dataset
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.docstore.document import Document

class DollyDataLoader:
    """Loads and preprocesses the Dolly dataset for RAG evaluation"""
    def __init__(
        self,
        dataset_path: str = "databricks/databricks-dolly-15k",
        category: str = "closed_qa",
        split: str = "train",
        sample_size: T.Union[int, None] = None
    ) -> None:
        self.dataset_path = dataset_path
        self.category = category
        self.sample_size = sample_size
        self.split = split
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=50)

    def load_data(self) -> T.Tuple[T.List[Document], Dataset]:
        """
        Loads the Dolly dataset, filters by closed_qa category, samples, and prepares documents for the vector store.

        Returns:
            A tuple containing:
            - List[Document]: LangChain Document objects for the vector store.
            - Dataset: Sampled Hugging Face dataset for RAG queries and ground truths.
        """

        dataset = load_dataset(self.dataset_path, split=self.split)
        dataset = dataset.filter(
            lambda x: x['category'] == self.category and x['context'] is not None and x['context'].strip() != "",
            num_proc=os.cpu_count()
        )
        if self.sample_size:
            dataset = dataset.shuffle(seed=42).select(
                range(min(self.sample_size, len(dataset)))
            )

        documents_content = [item["context"] for item in dataset]
        langchain_documents = [Document(page_content=content) for content in documents_content]
        return self.text_splitter.split_documents(langchain_documents), dataset

RAG パイプラインの構築

データの準備ができたら、次は RAG パイプラインそのものを構築します。これには、効率的なドキュメント検索を担うベクトルストアと、LLM を統合するリトリーバーチェーン(Retrieval Chain)という 2 つの主要コンポーネントが必要です。

そのために、コードを rag_pipeline/model_provider.py(モデルの初期化担当)と rag_pipeline/pipeline.py(RAG のオーケストレーション担当)の 2 つのファイルに分けて構成していきます。

まずは、LLMと埋め込みモデルへのアクセス方法を定義しましょう。これらは LLMProviderEmbeddingProvider という 2 つのクラスで管理します。これらのクラスを利用することで、TogetherAI のような外部 API との接続に関する詳細を隠蔽(カプセル化)できます。これにより、メインのパイプラインは特定のモデル実装に依存せず、クリーンな状態を保つことができます。

rag_pipeline/model_provider.py を作成し、次のコードを追加します。

# File Name: rag_pipeline/model_provider.py

import os
from langchain_together import TogetherEmbeddings, ChatTogether
from langchain_core.embeddings import Embeddings
from langchain_core.language_models.chat_models import BaseChatModel

class LLMProvider:
    """Manages the initialization of the LLM for the RAG pipeline"""
    def __init__(self, model_name: str) -> None:
        self.model_name = model_name

    def get_llm(self) -> BaseChatModel:
        if "TOGETHER_API_KEY" not in os.environ:
            raise ValueError("TOGETHER_API_KEY environment variable not set")
        return ChatTogether(model=self.model_name)

class EmbeddingProvider:
    """Manages the initialization of the Embedding model for vectorizing documents"""

    def __init__(self, model_name: str) -> None:
        self.model_name = model_name

    def get_embedding_provider(self) -> Embeddings:
        if "TOGETHER_API_KEY" not in os.environ:
            raise ValueError("TOGETHER_API_KEY environment variable not set")
        return TogetherEmbeddings(model=self.model_name)

次に、rag_pipeline/pipeline.py に定義する RAGPipeline クラス が、処理全体をオーケストレーションします。

# File Name: rag_pipeline/pipeline.py

import typing as T
from tqdm import tqdm
from langchain_community.vectorstores import FAISS
from langchain.chains.retrieval_qa.base import RetrievalQA
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.embeddings import Embeddings
from langchain.docstore.document import Document
from datasets import Dataset

class RAGPipeline:
    def __init__(self, llm: BaseChatModel, embedding_provider: Embeddings) -> None:
        self.llm = llm
        self.embedding_provider = embedding_provider
        self.vectorstore: FAISS = None
        self.qa_chain: RetrievalQA = None

    def build_vector_store(self, documents: T.List[Document]) -> None:
        print("Embedding documents with FAISS vectorstore")
        self.vectorstore = FAISS.from_documents(documents, self.embedding_provider)

    def setup_qa_chain(self) -> None:
        if not self.vectorstore:
            self.build_vector_store()
        self.qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",
            retriever=self.vectorstore.as_retriever()
        )

    def run_queries(
        self,
        hf_dataset_sample: Dataset,
        query_sample_size: int = 30
    ) -> T.List[T.Dict[str, str]]:
        """
        Runs RAG queries against the pipeline and collects results for RAGAS evaluation.

        Args:
            hf_dataset_sample: The sampled Hugging Face dataset containing questions and ground truths.
            query_sample_size: The number of queries to run from the sampled dataset.

        Returns:
            A list of dictionaries, each containing 'question', 'answer', 'contexts', and 'ground_truth'.
        """
        if not self.qa_chain:
            self.setup_qa_chain()

        sampled_queries_dataset = hf_dataset_sample.shuffle(seed=42).select(
            range(min(query_sample_size, len(hf_dataset_sample)))
        )
        results = []
        for item in tqdm(sampled_queries_dataset, desc="Generating responses for queries"):
            query = item["instruction"]
            ground_truth = item["response"]

            # Generates response for a query
            response = self.qa_chain.invoke({"query": query})   
            answer = response["result"]

            # Only fetches the relevant documents from knowlege base used as context for the response
            retrieved_docs = self.qa_chain.retriever.invoke(query)  
            contexts = [doc.page_content for doc in retrieved_docs]

            results.append({
                "question": query,
                "answer": answer,
                "contexts": contexts,
                "ground_truth": ground_truth
            })
        return results

まず、ドキュメントを読み込み、EmbeddingProvider を使ってそれらを数値ベクトルに変換します。変換されたベクトルは build_vector_store メソッドによって FAISS でインデックスされ、この FAISS インデックスこそが RAG 用のナレッジベースとして機能します。

続いて、setup_qa_chain メソッドで LangChain の RetrievalQA チェーンを構成します。このチェーンは、ベクトルデータベースから関連する文脈を検索し、その文脈を LLM に渡して回答を生成する内部パイプラインの役割を果たします。

run_queries が呼び出されると、このチェーンはユーザーの質問を受け取り、FAISS を使って最も関連性の高いドキュメントの断片(チャンク)を検索します。そのチャンク群と質問をあわせて LLMProvider が提供する LLM に渡し、最終的な回答を生成します。

このように処理を分割して実装することで、RAG パイプライン全体の責務が明確になり、モジュール性と保守性の高い構成にできます。

RAG パイプラインを RAGAS で評価する

従来のメトリクスは生成結果のみを対象とすることが多く、取得された文脈の役割を考慮しません。そのため、リグレッションがリトリーバーに起因するのか、ジェネレーターに起因するのかを切り分けることが難しくなります。

RAGAS(RAG Assessment は、文脈を考慮した定量的な指標を提供することで、この課題を解決する専用フレームワークです 。手動のアノテーションや単純な文字列照合ではなく、「LLM-as-a-judge」という手法を採用しています 。これは「評価用 LLM(Evaluator LLM)」と呼ばれる高性能なモデルを用いて、RAG パイプラインの出力をプログラムで自動的にスコアリングする仕組みです。

RAGAS は、RAG に特化した以下のような診断指標やメトリクスを提供します。

  • 忠実性(Faithfulness: 生成された回答と取得された文脈との間の事実の整合性を測定します。スコアが高いほど、LLM が提供資料に基づかない情報を追加していないことを示し、ハルシネーションの抑制に直結します。
  • LLM 文脈再現率(LLM context recall): グラウンド・トゥルース(Ground Truth)に対して、取得された文脈に十分な情報が含まれているかを評価します。スコアが高いほど、リトリーバーがナレッジベースから必要な情報を正確に特定・提供できていることを意味します。
  • 事実の正確性(Factual correctness): 生成された回答をグラウンド・トゥルースと直接比較し、事実としての正確性を評価します。これは、RAG パイプラインの最終的な出力品質を測るエンドツーエンドの指標となります。

これらのメトリクスを組み合わせることで、リトリーバーとジェネレーターの両フェーズにおける RAG パイプラインの性能を、バランスよく客観的に把握することが可能になります。

RAGAS 評価ツールの実装

RAGAS の評価ロジックは、rag_pipeline/evaluator.py 内の RAGASEvaluator クラスに集約されています 。

このクラスは、RAG パイプラインから出力された以下のデータを受け取ります。

  • 質問
  • RAG が生成した回答
  • ベクトルストアから取得した文脈
  • Dolly-15k の正解データ(Ground Truth)

これらのデータをもとに、RAGAS フレームワークを用いた評価プロセスをオーケストレーションします。

以下のコードを使い、rag_pipeline/evaluator.py ファイルを作成します。

# File Name: rag_pipeline/evaluator.py

import typing as T
import pandas as pd
from ragas import evaluate
from ragas.metrics import Faithfulness, FactualCorrectness, LLMContextRecall
from datasets import Dataset
from langchain_core.language_models.chat_models import BaseChatModel

class RAGASEvaluator:
    """Handles the evaluation of RAG pipeline results using RAGAS metrics"""
    def __init__(self, evaluator_llm: BaseChatModel) -> None:
        self.evaluator_llm = evaluator_llm
        # -- Using relevant RAG metrics: Change as required -- #
        self.metrics = [Faithfulness(), FactualCorrectness(), LLMContextRecall()]

    def evaluate_results(self, rag_results: T.List[T.Dict[str, str]]) -> pd.DataFrame:
        """
        Performs RAGAS evaluation on the collected RAG results.

        Args:
            rag_results: A list of dictionaries containing 'question', 'answer', 'contexts', and 'ground_truth'.

        Returns:
            A pandas DataFrame with the RAGAS evaluation scores.
        """
        data = {
            "question": [r["question"] for r in rag_results],
            "answer": [r["answer"] for r in rag_results],
            "contexts": [r["contexts"] for r in rag_results],
            "ground_truth": [r["ground_truth"] for r in rag_results],
        }
        dataset = Dataset.from_dict(data)

        print("Performing RAGAS evaluation...")
        result = evaluate(
            dataset=dataset,
            metrics=self.metrics,
            llm=self.evaluator_llm
        )
        return result.to_pandas()

evaluate_results メソッドは、収集した RAG の出力を ragas.evaluate() の入力形式である Hugging Face の Dataset オブジェクトに変換します。続いて、準備したデータセットと指定したメトリクス、そして品質判定を担う evaluator_llm を渡して評価を実行します 。このメソッドは最終的に、全クエリの各指標スコアをまとめた pandas の DataFrame を返すため、結果を一覧形式で容易に確認できます。

ローカル環境での RAG 評価のオーケストレーションとテスト

main.py はオーケストレーターとして、データローダー、LLM/埋め込みプロバイダー、RAG パイプライン、RAGAS 評価ツールといったすべてのモジュールを統合する役割を担います。このファイルでワークフローを定義し、必要なクラスをインスタンス化して、評価プロセスをエンドツーエンドで実行します。

プロジェクトのルートディレクトリに main.py を作成し、以下のコードを追加してください。

# File Name: main.py

import os
import json

from rag_pipeline.model_provider import LLMProvider, EmbeddingProvider
from rag_pipeline.dataloader import DollyDataLoader
from rag_pipeline.pipeline import RAGPipeline
from rag_pipeline.evaluator import RAGASEvaluator

EMBEDDING_MODEL_NAME = os.getenv("EMBEDDING_MODEL_NAME", "BAAI/bge-base-en-v1.5")
LLM_MODEL_NAME = os.getenv("LLM_MODEL_NAME", "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo")
EVALUATOR_LLM_NAME = os.getenv("EVALUATOR_LLM_NAME", "Qwen/Qwen2.5-7B-Instruct-Turbo")

DOCUMENTS_SAMPLE_SIZE = int(os.getenv("DOCUMENTS_SAMPLE_SIZE", 100))
QUERY_SAMPLE_SIZE = int(os.getenv("QUERY_SAMPLE_SIZE", 5))

print(f"EMBEDDING_MODEL_NAME: {EMBEDDING_MODEL_NAME}")
print(f"LLM_MODEL_NAME: {LLM_MODEL_NAME}")
print(f"EVALUATOR_LLM_NAME: {EVALUATOR_LLM_NAME}")
print(f"DOCUMENTS_SAMPLE_SIZE: {DOCUMENTS_SAMPLE_SIZE}")
print(f"QUERY_SAMPLE_SIZE: {QUERY_SAMPLE_SIZE}\n")

def run_evaluation():
    # -- Load a partition of Dolly-15K dataset -- #
    dataloader = DollyDataLoader(sample_size=DOCUMENTS_SAMPLE_SIZE)
    documents_for_vectorstore, hf_dataset_for_queries = dataloader.load_data()
    if len(hf_dataset_for_queries) == 0:
        print("No samples found matching the criteria. Please check dataset name, category, and sample size.")
        exit(1)

    print(f"Number of documents loaded for vector store: {len(documents_for_vectorstore)}")
    print(f"Number of evaluation samples from HF dataset (before query sampling): {len(hf_dataset_for_queries)}")

    # -- Load Embedding and Chat LLM Models -- #
    embedding_provider = EmbeddingProvider(model_name=EMBEDDING_MODEL_NAME).get_embedding_provider()
    llm_provider = LLMProvider(model_name=LLM_MODEL_NAME).get_llm()
    evaluator_llm_provider = LLMProvider(model_name=EVALUATOR_LLM_NAME).get_llm()

    # -- Set up RAG pipeline -- #
    # -- Vectorizes and stores documents in FAISS knowlegde base. Then sets up a RetreivalQA chain -- #
    # -- Given a query, it will get relevant chunks from vectorstoreand answer based on that context -- #
    rag_pipeline = RAGPipeline(llm_provider, embedding_provider)
    rag_pipeline.build_vector_store(documents_for_vectorstore)
    rag_pipeline.setup_qa_chain()

    print("Running queries from Dolly dataset and collecting data for RAGAS...")
    rag_results = rag_pipeline.run_queries(hf_dataset_for_queries, QUERY_SAMPLE_SIZE)

    with open("rag_results.json", "w") as _f:
        json.dump(rag_results, _f, indent=4)
        print("RAG query results saved to rag_results.json")

    # -- Evalaute generated responses with specific RAGAS metrics. -- #
    ragas_evaluator = RAGASEvaluator(evaluator_llm_provider)
    evaluation_df = ragas_evaluator.evaluate_results(rag_results)

    # -- Ragas returns a score for each sample, use mean for overall -- #
    # -- The key values are auto-generated by RAGAS based on metric type -- #
    faithfulness_score = evaluation_df["faithfulness"].mean() 
    context_recall_score = evaluation_df["context_recall"].mean()
    factual_correctness_score = evaluation_df["factual_correctness(mode=f1)"].mean()

    print("\n--- RAGAS Evaluation Results ---")
    print(f"Average Faithfulness Score: {faithfulness_score:.2f}")
    print(f"Average Context Recall Score: {context_recall_score:.2f}")
    print(f"Average Factual Correctness Score: {factual_correctness_score:.2f}")

    evaluation_df.to_csv("ragas_results.csv", index=False)

if __name__ == "__main__":
    run_evaluation()

main.py の準備が整えば、ローカル環境から RAG 評価パイプラインを実行できます。仮想環境が有効であること、および本チュートリアルの「必要なパッケージのインストール」セクションで解説した依存関係がすべてインストール済みであることを確認してください。

コード内では、EMBEDDING_MODEL_NAMELLM_MODEL_NAME、評価用の各 SAMPLE_SIZE などの主要パラメータを環境変数から動的に読み込むため(デフォルト値あり)、柔軟な構成が可能です。TOGETHER_API_KEY がシェルの環境変数として利用可能である必要があります。os.getenv().env ファイルを直接読み込むのではなく、実行シェルの環境変数から値を読み込む点に注意してください。

Unix系システムでは、以下の手順で .env ファイルから環境変数をエクスポートします。

set -a
source .env

次に、main.py スクリプトを実行します。

python main.py

データセットのロード、ベクトルストアの構築、クエリの処理、そして RAGAS によるメトリクスの算出が進むにつれて、コンソールに進行状況が表示されます。完了すると、コンソールに以下のような RAGAS スコアが表示されます。

EMBEDDING_MODEL_NAME: BAAI/bge-base-en-v1.5
LLM_MODEL_NAME: meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo
EVALUATOR_LLM_NAME: Qwen/Qwen2.5-7B-Instruct-Turbo
DOCUMENTS_SAMPLE_SIZE: 100
QUERY_SAMPLE_SIZE: 5

Number of documents loaded for vector store: 191
Number of evaluation samples from HF dataset (before query sampling): 100
Embedding documents with FAISS vectorstore
Running queries from Dolly dataset and collecting data for RAGAS...
Generating responses for queries: 100%|███████████████████████████████████████████████████| 5/5 [00:10<00:00,  2.10s/it]
RAG query results saved to rag_results.json
Performing RAGAS evaluation...
Evaluating: 100%|███████████████████████████████████████████████████████████████████████| 15/15 [00:19<00:00,  1.31s/it]

--- RAGAS Evaluation Results ---
Average Faithfulness Score: 0.95
Average Context Recall Score: 0.93
Average Factual Correctness Score: 0.61

CircleCI による評価の自動化

ここまでで、シンプルながらも機能的な RAG パイプラインと RAGAS 評価システムが構築できました。最後の、そして最も重要なステップは、このプロセスを自動化することです。これにより、継続的な品質管理が可能になり、RAG アプリケーションの性能に対する信頼を維持できます。

RAG 評価を CI/CD パイプラインに統合することで、検索戦略やチャンク分割手法、LLM プロンプティングの変更など、あらゆるコード修正に対して自動的に評価をトリガーできます。このプロアクティブなアプローチにより、リグレッショを早期に検知して開発者へ即座にフィードバックできるだけでなく、再現可能なベンチマークも容易になり、結果として開発サイクルの高速化と信頼性の高いデプロイを実現できます。

RAG 評価を CircleCI に統合するには、GitHub リポジトリのルートに .circleci/config.yml ファイルを作成し、CI/CD ワークフローを定義します。

# File Name: .circle/config.yml

version: 2.1

parameters:
  embedding_model:
    type: string
    default: "BAAI/bge-base-en-v1.5"
    description: "Embedding model name for the RAG pipeline."
  llm_model:
    type: string
    default: "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo"
    description: "LLM model name for the RAG pipeline."
  evaluator_llm_model:
    type: string
    default: "Qwen/Qwen2.5-7B-Instruct-Turbo"
    description: "LLM model name used by RAGAS for evaluation metrics."
  doc_sample_size:
    type: integer
    default: 100
    description: "Number of documents to sample from Dolly dataset for vector store."
  query_sample_size:
    type: integer
    default: 5
    description: "Number of queries to sample from Dolly dataset for evaluation."

jobs:
  rag_evaluation:
    docker:
      - image: cimg/python:3.10
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: pip install -r requirements.txt
      - run:
          name: Run RAGAS Evaluation
          # Passes parameters to Python process execution environment. Overrides default values.
          command: |
            EMBEDDING_MODEL_NAME="<< pipeline.parameters.embedding_model >>" \
            LLM_MODEL_NAME="<< pipeline.parameters.llm_model >>" \
            EVALUATOR_LLM_NAME="<< pipeline.parameters.evaluator_llm_model >>" \
            DOCUMENTS_SAMPLE_SIZE="<< pipeline.parameters.doc_sample_size >>" \
            QUERY_SAMPLE_SIZE="<< pipeline.parameters.query_sample_size >>" \
            python3 main.py
      - store_artifacts:
          path: ragas_results.csv
          destination: ragas_evaluation_results
      - store_artifacts:
          path: rag_results.json
          destination: rag_raw_results

workflows:
  pipeline:
    jobs:
      - rag_evaluation

この config.yml は、自動評価プロセス全体を定義するものです。parameters: セクションでは、モデル名やサンプルサイズなどの変数を定義でき、CircleCI の UI や API からワークフローを実行する際に、これらの値を簡単に調整できます。これにより、実験のたびに YAML ファイルを直接書き換える手間が省けます。ここで設定したパラメータは、<< pipeline.parameters.parameter_name >> という構文を用いることで、環境変数として main.py スクリプトへシームレスに渡されます。

jobs: セクションでは、メインタスクである rag_evaluation を定義します 。実行環境に Docker イメージを指定し、コードのチェックアウト、依存関係のインストール、そして評価の核心である main.py の実行という一連のステップを構成します。

万が一 main.py がエラー(非ゼロの終了コード)を返せばジョブは即座に失敗し、開発者に異常を通知します 。また、store_artifacts ステップによって評価結果(ragas_results.csvrag_results.json)が保存され、CircleCI のダッシュボードからいつでも結果の確認や履歴の追跡が可能です。

この config.yml を GitHub にプッシュするだけで、変更のたびに RAG 評価が自動実行されるようになり、システムの品質と性能を常に維持できるようになります。

CircleCI でのプロジェクト設定

CircleCI でプロジェクトを設定する前に、まずコードを GitHub にアップロードする必要があります。GitHub へのプッシュを回避するファイルやフォルダを定義するため、.gitignore ファイルを新規作成し、以下の内容を記述してください。

venv/
*/__pycache__/*
rag_results.json
ragas_results.csv
.env

これで、GitHub リポジトリにコードをプッシュ する準備が整いました。

次に、CircleCI アカウントにログインして新規プロジェクトを作成します。パイプラインを実行する前に、環境変数を設定しておく必要があります。CircleCI のサイドバーで Projects を選択し、プロジェクトの行にある省略記号(…)をクリックして Project Settings を選択してください。

Opening project settings

プロジェクト設定ページで、サイドバーの Environment Variables(環境変数)を選択し、キーを「TOGETHER_API_KEY」、値を TogetherAI の API キーとして環境変数を追加します。

Adding an environment variable

これでパイプラインを手動で実行できるようになります。正常に実行されるはずです。

Successful execution

完了したジョブの Artifacts タブに移動すると、rag_results.jsonragas_results.csv などの生成されたファイルを確認できます 。これらのファイルはダウンロード可能で、RAG のパフォーマンスを定期的に追跡するために利用できます。

Generated artifacts

このプロジェクトの完全なコードは GitHub で公開されています。

まとめ

RAG(検索拡張生成)システムがスケールするにつれ、自動化された再現可能な評価プロセスの統合は極めて重要になります。システムの信頼性と正確性を維持し、ユーザーからの信頼を担保するために評価は欠かせません。

本チュートリアルでは、LangChain と FAISS を用いた RAG パイプラインの構築から、Dolly-15k データセットの統合、そして RAGAS と CircleCI による品質保証の自動化までを解説しました。この自動化されたセットアップにより、リグレッションを早期に検知し、迅速な改善と自信を持ったデプロイが可能になります。

次の重要なステップとして、main.py や CircleCI の設定に RAGAS スコアの明示的なしきい値を設定することを強く推奨します。これにより、RAG のパフォーマンスが許容基準を下回った場合に CI/CD パイプラインを自動的に失敗させ、不可欠な「品質ゲート」として機能させることができます。

自動評価は、特に以下のような高度なユースケースで不可欠です。

  • エンタープライズ向けドキュメント検索ツールの根拠性の評価
  • 医療や法務アシスタントなど、正確性が求められるアプリケーションでのハルシネーション検知
  • LLM を活用したチャットボットの継続的なパフォーマンス監視

このような自動化を導入することで、頻繁な変更が行われる大規模な環境でも、RAG システムの堅牢性と信頼性を維持し続けることができます。


Muhammad Arham is a Deep Learning Engineer specializing in Computer Vision and Natural Language Processing, with experience developing globally top-ranking generative AI applications for the Google Play Store. He is passionate about constructing and optimizing machine learning models for intelligent systems, and firmly believes in continuous improvement within this rapidly evolving field.