RAGシステムをPoCから本番運用へ移行する際、「思ったような回答精度が出ない」「リリース後に品質が低下した」といった課題に直面し、頭を抱えるエンジニアは少なくありません。特に、RAGの品質維持を人力で行うのは非現実的であり、安定した運用には自動化が不可欠です。
この記事では、RAGシステムを本番運用する上での主要な課題を解決するために、評価指標に基づいた品質維持の自動化戦略と具体的な実装パターンを解説します。RAGの回答精度を安定させ、継続的に向上させるための実用的な知見とコードを提供します。
RAGの本番運用における課題と品質維持の重要性
RAG (Retrieval-Augmented Generation) は、大規模言語モデル (LLM) のハルシネーション(誤情報生成)を抑制し、特定の知識ベースに基づいた正確な回答を生成するために強力なアプローチです。しかし、PoC(概念実証)段階から本番運用へ移行する際には、回答精度の安定化、スケーラビリティ、レイテンシ、データ鮮度など、さまざまな課題に直面します。
特に重要なのは、RAGの「品質維持」です。データやユーザーの行動が進化するにつれて、検索品質は静かに低下する可能性があります。継続的な評価と再インデックスなしでは、システムは古くなった、または無関係な応答を生成し始めるでしょう。このセクションでは、RAGの本番運用でよくある課題と、その解決策としての品質維持の重要性について説明します。
RAGシステムの基本アーキテクチャ
RAGは以下の主要な4ステップで構成されます。
- ドキュメントの取り込み (Ingestion): ソースデータ(Webページ、PDF、データベースなど)を収集します。
- 埋め込みとインデックス作成 (Embedding & Indexing): 収集したドキュメントをチャンクに分割し、埋め込みモデルでベクトル表現に変換し、ベクトルデータベースにインデックス化します。
- 検索 (Retrieval): ユーザーの質問をベクトル化し、ベクトルデータベースから関連性の高いドキュメントチャンクを検索します。
- 生成 (Generation): 検索されたコンテキストとユーザーの質問をLLMに渡し、回答を生成させます。
各レイヤーには、応答品質、レイテンシ、コストに直接影響するトレードオフが存在するため、本番運用ではこれらのバランスを考慮した設計が求められます。
本番運用で直面する主な課題
-
回答精度の不安定性:
- 不適切なチャンキング: 意味のある情報が途中で分断され、コンテキストが失われる。
- データ品質の低さ: 汚れたデータや非構造化データが検索精度を低下させる。
- 検索の失敗 (Retrieval Failure): 関連性の低いドキュメントが取得されたり、重要なコンテキストが欠落したりする。RAGの失敗の73%は検索段階で発生すると言われています。
- 運用コストとレイテンシ: 高品質な埋め込みモデルやRerankerの使用はコストとレイテンシを増加させます。
- プロンプトのドリフト: プロンプトのわずかな変更が性能に影響を与え、品質が低下する可能性があります。
- データ鮮度と再インデックス: 基盤となる知識ベースが頻繁に更新される場合、ベクトルデータベースの鮮度を維持するための戦略が必要です。
これらの課題に対処し、RAGシステムの品質を安定的に維持するためには、自動化された評価と継続的な改善サイクルが不可欠です。
RAGの品質を定量化する評価指標「RAG Triad」
RAGシステムの品質を主観的な判断ではなく、客観的かつ定量的に評価するために「RAG Triad」という3つの補完的な指標が提唱されています。このセクションでは、それぞれの指標の意味と、なぜこれらが本番運用における自動評価に重要なのかを解説します。
RAG Triad の構成
-
Answer Relevance (回答の関連性)
- 定義: 生成された応答が、ユーザーの質問にどれだけ的確に答えているかを測定します。
- 重要性: ユーザーが求めている情報を提供できているかを直接的に評価する指標です。関連性の低い回答はユーザー体験を損ねます。
-
Context Relevance (コンテキストの関連性)
- 定義: 取得されたドキュメント(コンテキスト)が、ユーザーの質問に関連する情報を含んでいるかを評価します。
- 重要性: 検索ステップの品質を測る指標です。関連性の低いコンテキストがLLMに渡されると、不正確な回答やハルシネーションの原因となります。
-
Groundedness (Faithfulness) (根拠性/忠実性)
- 定義: 生成された回答が、取得されたコンテキストにどれだけ基づいているかを測定します。つまり、回答がコンテキストにない「幻覚」を含んでいないかを評価します。
- 重要性: RAGの最大の目的の一つである「ハルシネーションの抑制」を直接評価する指標です。回答が根拠に基づいていることは、信頼性の高いシステムを構築する上で不可欠です。
これらの指標を継続的にモニタリングし、閾値を設定することで、RAGシステムの品質変化を自動的に検出し、問題が発生した際に早期に対処することが可能になります。
RAGASによるRAG評価の自動化戦略
RAGシステムの品質維持を自動化するための強力なツールが「RAGAS」です。RAGASは、上記のRAG Triadを含む複数の指標を用いてRAGパイプラインの品質を自動評価できるフレームワークであり、CI/CDパイプラインへの統合により、継続的な品質保証を実現します。
RAGASの仕組みとCI/CDへの統合
RAGASは、評価対象となるRAGシステムからの出力(質問、生成された回答、取得されたコンテキスト、そして期待される正解データground_truths)を受け取り、内部でLLM(LLM-as-a-judge)や埋め込みモデルを活用して、各指標のスコアを算出します。
CI/CDパイプラインにRAGASを統合することで、以下のようなメリットが得られます。
- 品質回帰の早期検出: コード変更、データ更新、モデル更新などが発生した際に、RAGシステムの品質が低下していないかを自動でチェックできます。
- 継続的な改善: 評価結果をフィードバックループに組み込むことで、問題のある箇所を特定し、チャンキング戦略、埋め込みモデル、検索器、プロンプトなどを改善するサイクルを回せます。
- 客観的な意思決定: 主観的な判断に頼らず、定量的なデータに基づいてRAGシステムの改善やデプロイの可否を判断できます。
RAGASによる評価の実装例
ここでは、RAGASを使用してRAGシステムの評価データセットを準備し、評価を実行する基本的な流れを示します。RAGASはHugging Face datasetsライブラリのDatasetオブジェクトを評価データとして受け取ります。
# RAGASのインストール
# pip install ragas datasets
from ragas.metrics import (
faithfulness,
answer_relevance,
context_relevance,
context_recall, # 追加: 取得されたコンテキストがグラウンドトゥルースの回答をどれだけカバーしているか
context_precision, # 追加: 取得されたコンテキストのうち、関連性の高いものの割合
)
from ragas import evaluate
from datasets import Dataset
# 評価データセットの準備
# 実際のRAGシステムから生成されたデータを使用することが重要
# ここでは例としてシンプルなデータを作成しますが、本番では多様な質問と回答ペアを用意します
data = {
"question": ["RAGとは何ですか?", "LangChainの主な機能は?", "Ollamaの利点は?"],
"answer": [
"RAGはLLMのハルシネーションを減らし、より正確な回答を生成する技術です。",
"LangChainはLLMアプリケーション開発のためのフレームワークです。",
"Ollamaはローカルで様々なLLMを実行できるため、プライバシーとコストを抑えられます。"
],
"contexts": [
["RAGはRetrieval-Augmented Generationの略で、外部知識ソースから情報を取得し、LLMの生成能力を強化する技術です。"],
["LangChainは、LLMと外部コンポーネントを接続し、複雑なアプリケーションを構築するためのツールキットです。チェーン、エージェント、プロンプトテンプレートなどの機能を提供します。"],
["OllamaはオープンソースのLLMをローカル環境で簡単に実行できるツールです。API互換性があり、開発コストを削減できます。"]
],
"ground_truths": [
["RAGは大規模言語モデルのハルシネーションを減らし、より正確な回答を生成するために使われます。外部データソースと連携します。"],
["LangChainは、LLMアプリケーションを構築するためのフレームワークであり、データのロード、チャンキング、埋め込み、チェーンの構築などをサポートします。"],
["Ollamaは、ローカル環境で大規模言語モデルを効率的に実行するためのプラットフォームです。これにより、プライバシーの保護、オフラインアクセス、コスト削減が可能です。"]
]
}
dataset = Dataset.from_dict(data)
# RAGASで評価を実行
# RAGASが評価に使用するLLMと埋め込みモデルを指定します。
# 公式ドキュメント: https://docs.ragas.ai/en/latest/how_to_guides/integrations/langchain.html
# OllamaモデルをRAGASに直接渡す方法は進化中ですが、LangChainのOllamaクラスをラップして使用できます。
# (例としてOpenAIを使用しますが、必要に応じてOllamaで置き換えてください)
from langchain_community.llms import Ollama
from langchain_community.embeddings import OllamaEmbeddings
from ragas.llms import LangchainLLM
from ragas.embeddings import LangchainEmbeddings
# OllamaモデルをRAGASで使えるようにラップ
# モデル名はollama run <model_name>でダウンロードしたものと一致させる
ragas_llm = LangchainLLM(llm=Ollama(model="llama3"))
ragas_embeddings = LangchainEmbeddings(embeddings=OllamaEmbeddings(model="bge-base-en"))
# 評価の実行
# context_recallとcontext_precisionはground_truthsが必要なため追加
result = evaluate(
dataset,
metrics=[faithfulness, answer_relevance, context_relevance, context_recall, context_precision],
llm=ragas_llm,
embeddings=ragas_embeddings
)
print(result)
# CI/CDパイプラインでの統合例 (擬似コード)
# threshold_faithfulness = 0.8
# threshold_answer_relevance = 0.7
# threshold_context_relevance = 0.75
# if result["faithfulness"] < threshold_faithfulness or \
# result["answer_relevance"] < threshold_answer_relevance or \
# result["context_relevance"] < threshold_context_relevance:
# print("RAG品質が閾値を下回りました。デプロイを中止します。")
# exit(1)
# else:
# print("RAG品質は良好です。デプロイを続行します。")
この評価結果をCI/CDツール(GitHub Actions, GitLab CIなど)に組み込み、特定の指標が閾値を下回った場合にデプロイをブロックしたり、警告を発したりすることで、RAGシステムの品質を継続的に管理できます。
RAGシステム実装のベストプラクティスと具体的なコード例
RAGの本番運用を成功させるためには、評価だけでなく、その基盤となるシステムの実装品質も重要です。このセクションでは、LangChain (バージョン0.1.0以降) とOllamaを組み合わせたRAGシステムの具体的な実装例と、本番運用に耐えうる設計上のベストプラクティスを解説します。
前提・環境
- Python: 3.9以上
-
ライブラリ:
ollama,langchain,beautifulsoup4,chromadb -
Ollama: ローカルでLLMと埋め込みモデルを実行するために使用します。事前にインストールし、以下のモデルをダウンロードしておきます。
ollama run llama3ollama run bge-base-en
シンプルなRAGシステムの実装例 (LangChain + Ollama)
このコードは、Webページからドキュメントをロードし、チャンキング、埋め込み、ベクトルストアへの保存、そしてOllamaで実行されるLLMを用いた質問応答までの一連のRAGフローを構築します。LangChain Expression Language (LCEL) を使用しており、本番環境での堅牢性と柔軟性を考慮したモダンな書き方です。
# 必要なライブラリのインストール
# pip install ollama langchain beautifulsoup4 chromadb
# Ollamaでモデルをダウンロード (例: Llama-3, bge-base-en)
# コマンドラインで実行:
# ollama run llama3 # Llama-3の最新版をダウンロード
# ollama run bge-base-en # bge-base-enの最新版をダウンロード
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema import StrOutputParser
print("1. ドキュメントのロードを開始...")
# 例としてLangChainのRAGに関するドキュメントを使用
loader = WebBaseLoader("https://www.langchain.com/langchain-for-rag")
docs = loader.load()
print(f"ロードされたドキュメント数: {len(docs)}")
print("2. ドキュメントのチャンキングを開始...")
# 再帰的なチャンキング戦略。意味単位を破壊しにくいように設計されています。
# chunk_size: 各チャンクの最大サイズ (トークン数または文字数)
# chunk_overlap: チャンク間の重複部分。文脈の連続性を保つために重要。
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
print(f"分割されたチャンク数: {len(splits)}")
print("3. 埋め込みとインデックス作成 (ChromaDBとOllamaEmbeddings) を開始...")
# OllamaEmbeddingsのモデル名は、ollama run <model_name> でダウンロードしたモデル名と一致させる
embeddings = OllamaEmbeddings(model="bge-base-en")
# ベクトルストアの永続化パスを指定 (本番運用では永続化が重要)
vectorstore = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db" # ベクトルストアをディスクに保存
)
print("埋め込みとインデックス作成が完了しました。")
print("4. 検索器の作成...")
# ベクトルストアから検索器を作成。top_kで取得するチャンク数を制御します。
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
print("検索器が作成されました。")
print("5. LLMの初期化...")
# Ollamaのモデル名は、ollama run <model_name> でダウンロードしたモデル名と一致させる
llm = Ollama(model="llama3")
print("LLMが初期化されました。")
print("6. RAGチェーンの構築 (LangChain Expression Language (LCEL) を使用)...")
# LCELは、RAGフローをモジュール化し、柔軟かつ堅牢に構築するための推奨される方法です。
# {"context": retriever, "question": RunnablePassthrough()}
# - context: retrieverから取得したドキュメントが入力されます。
# - question: ユーザーの質問がそのまま入力されます。
template = """以下のコンテキストのみに基づいて質問に答えてください。
回答は日本語で、簡潔にまとめてください。
コンテキスト:
{context}
質問: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()} # 入力(質問)をretrieverとquestionにマッピング
| prompt # プロンプトテンプレートを適用
| llm # LLMで生成
| StrOutputParser() # 生成された出力を文字列にパース
)
print("RAGチェーンが構築されました。")
print("7. 質問応答を開始...")
question = "LangChain for RAGの主な利点は何ですか?"
response = rag_chain.invoke(question)
print("\n--- 質問 ---")
print(question)
print("\n--- 回答 ---")
print(response)
# 質問応答後、ベクトルストアを閉じる (永続化している場合)
vectorstore.persist()
print("ベクトルストアが永続化され、処理が終了しました。")
本番運用におけるベストプラクティス
上記の実装を基盤としつつ、本番運用では以下の点に注意し、RAGの品質維持と安定稼働を目指します。
-
チャンキング戦略の最適化:
- 問題: 固定サイズのチャンキングでは意味単位が破壊されやすい。
-
解決策:
RecursiveCharacterTextSplitterを活用し、意味的な切れ目を優先して分割します。エンタープライズドキュメントでは、ヘッダー、リスト、テーブルの境界を保持する構造認識チャンキングが最適です。各チャンクが単一の一貫したアイデアを表すように調整します。
-
データ品質の向上:
- 問題: 汚れたデータは "Garbage in, garbage out" を引き起こします。
- 解決策: 強固なデータクリーニングと前処理パイプラインを実装します。重複の削除、エラーの修正、テキストの正規化、無関係なコンテンツのフィルタリングを徹底します。Google Cloud Vertex AI Agent PlatformのRAG Engine APIのようなサービスも、データ取り込みを容易にします。
-
検索の失敗回避とRerankerの導入:
- 問題: 関連性の低いドキュメントが取得されると、LLMは正確な回答を生成できません。
-
解決策:
- ハイブリッド検索: ベクトル検索(意味的類似性)とキーワード検索(BM25など)を組み合わせることで、精度と網羅性の両方を向上させます。
- Rerankerの利用: 取得されたドキュメントをCohere Rerankerなどで再ランク付けし、より関連性の高い情報をLLMに渡すコンテキストの質を高めます。
- オブザーバビリティ: クエリ、取得されたチャンクID、適用されたフィルターをログに記録し、検索の失敗を診断できるようにします。
-
プロンプトのバージョン管理:
- 問題: プロンプトのわずかな変更がパフォーマンスに影響を与える可能性があります。
- 解決策: プロンプトテンプレートをコードベースで管理し、Gitなどのバージョン管理システムで変更を体系的に管理します。CI/CDプロセスに組み込み、RAGAS評価と連携させることで、プロンプト変更による品質低下を早期に検出します。
-
コンテキストの再導入:
- 問題: 取得したチャンクだけでは、元のドキュメントの全体像がLLMに伝わりにくい場合があります。
- 解決策: LLMに渡す前に、取得したチャンクに元のドキュメントのタイトルやセクションなどのメタデータを追加して、コンテキストを豊かにします。これにより、LLMがより正確な参照を行えるようになります。
-
継続的な評価と再インデックス:
- 問題: RAGは静的なものではなく、データやユーザーの行動が進化するにつれて検索品質が静かに低下する可能性があります。
- 解決策: RAGASなどのツールをCI/CDに統合し、定期的な評価と必要に応じたベクトルデータベースの再インデックスを自動化します。データ鮮度を維持するためのデプロイメントパイプラインを構築します。
-
監査可能性の確保:
- トレーサビリティ: 各応答に対してどのチャンクIDが取得されたかを保存し、回答を形成したコンテキストを再現できるようにします。
- レビューフック: 信頼度の低い回答やポリシーに特化したクエリを人間のレビューに回す仕組みを導入します。
設計上のトレードオフと考慮事項
RAGシステムを本番運用する際には、常にいくつかのトレードオフが存在します。これらを理解し、自社の要件に合わせて最適なバランスを見つけることが重要です。
精度 vs レイテンシ
- トレードオフ: 回答の精度を向上させるには、より深い検索、長いプロンプト、高品質な埋め込みが必要となり、結果としてレイテンシ(応答時間)が増加します。
- 考慮事項: ユーザー向けアプリケーションでは、わずかな遅延でもユーザーエクスペリエンスに大きく影響します。正確性か応答性か、どちらを優先するかを早期に決定し、検索やLLMの選択を調整する必要があります。セマンティックキャッシュの導入もレイテンシ削減に有効です。
コスト vs 検索品質
- トレードオフ: 高品質な埋め込みモデル、Rerankerの利用、大規模なベクトルデータベースの運用は、検索品質を向上させますが、API利用料や計算リソースのコストも増加します。
-
考慮事項: オープンソースの埋め込みモデル(例:
bge-base-en)やローカル実行可能なLLM(Ollama)を効果的に活用し、コストを抑えつつ品質を維持する方法を検討します。クラウドサービスを利用する場合は、使用量に応じた課金体系を理解し、最適化を図ります。
シンプルRAG vs 高度なRAG
- シンプルRAG: 10〜50Kドキュメントまでの均質なコンテンツや簡単なクエリで良好に機能し、回答精度は60〜70%程度、レイテンシは最小限で、インフラに専門知識は不要です。PoC段階や初期導入に適しています。
- 高度なRAG: シンプルRAGでは解決できない複雑な問題(多様なドキュメントタイプ、複雑な質問、高い精度要求)を解決します。ハイブリッド検索、Reranker、ガードレール、セマンティックキャッシュなどを導入しますが、レイテンシと運用上の複雑さが増します。
- 考慮事項: まずはシンプルRAGから始め、課題に応じて段階的に高度なRAG戦略を導入していくのが現実的です。最初から複雑なシステムを構築すると、開発・運用コストが増大します。
RAG vs 長いコンテキストウィンドウ
- RAG: 大規模で動的なデータセットに対してはRAGが有効です。インデックス作成時に一度処理コストを支払い、その後の検索は安価です。常に最新の情報を参照できます。
- 長いコンテキストウィンドウを持つLLM: スタックを簡素化し、ドキュメント全体の推論をRAGよりも適切に処理できる場合があります。しかし、無限のエンタープライズデータセットの問題は解決せず、同じドキュメントを繰り返し処理するコストがかかります。
- 考慮事項: データセットの規模、更新頻度、LLMのコンテキストウィンドウの限界とコストを総合的に判断します。多くの場合、RAGと長いコンテキストウィンドウのLLMは補完的な関係にあり、組み合わせることでより強力なシステムを構築できます。
まとめ
RAGシステムの本番運用は、単に動くプロトタイプを作るのとは全く異なる課題を伴います。特に回答精度の安定化と品質維持の自動化は、成功の鍵となります。
この記事では、RAGの品質を定量的に評価するための「RAG Triad」指標、そしてそれをCI/CDパイプラインに統合するためのRAGASの活用法、さらにLangChainとOllamaを用いた具体的な実装パターンと本番運用におけるベストプラクティスを解説しました。
RAGシステムは一度構築したら終わりではなく、データやユーザーのニーズの変化に合わせて継続的に改善していく必要があります。RAGASのような評価フレームワークと自動化されたデプロイメントパイプラインを組み合わせることで、品質の回帰を早期に検出し、より信頼性の高いRAGシステムを構築・運用できるでしょう。
次の一歩として、RAGASの公式ドキュメントを参照し、より複雑な評価シナリオや、ご自身のRAGシステムに合わせた評価データセットの作成に着手してみてください。