はじめに

大規模言語モデル(LLM)は非常に強力ですが、「ハルシネーション(事実に基づかない情報を生成する現象)」や「学習データに含まれない最新情報に対応できない」といった課題を抱えています。これらの課題を解決する技術として、RAG(Retrieval-Augmented Generation / 検索拡張生成)が大きな注目を集めています。

RAGは、LLMが回答を生成する際に、外部の知識ソースから関連情報を検索し、その情報を基に回答を作成する仕組みです。これにより、LLMはより正確で、最新の、そして具体的な情報源に基づいた回答を生成できるようになります。本記事では、このRAGの基本的な仕組みから、代表的な実装パターン、そして実践で役立つ注意点までを、初中級者向けにわかりやすく解説していきます。

RAG(検索拡張生成)の基本的な仕組み

RAGのプロセスは、大きく分けて「検索(Retrieval)」と「生成(Generation)」の2つのフェーズで構成されます。このシンプルな連携が、LLMの能力を飛躍的に向上させます。

まず検索フェーズでは、ユーザーからの質問(クエリ)を受け取ると、事前に用意された知識ソース(ドキュメント、データベース、Webサイトなど)に対して検索を実行します。このとき、一般的には「ベクトル検索」という技術が用いられます。知識ソースはあらかじめ「チャンク」と呼ばれる小さな単位に分割され、それぞれが「エンベディング」という数値ベクトルに変換されて、ベクトルデータベースに保存されています。ユーザーのクエリも同様にベクトル化され、意味的に類似したチャンクを高速に検索します。

次に生成フェーズです。検索フェーズで得られた関連性の高い情報(コンテキスト)と、元のユーザーの質問を組み合わせ、LLMへの指示(プロンプト)を作成します。例えば、「以下の情報を参考にして、〇〇について答えてください。」といった形式です。この拡張されたプロンプトをLLMに渡すことで、LLMは提供された情報に基づいた、より信頼性の高い回答を生成することができるのです。

代表的なRAG実装パターン

RAGにはいくつかの実装パターンがあり、要件に応じて使い分けられます。ここでは代表的な3つのパターンを紹介します。

Naive RAG (基本的なRAG)

最もシンプルで基本的なパターンです。前述の通り、「検索 → 生成」という一直線のプロセスをたどります。実装が容易で、多くのユースケースで有効ですが、検索の精度が低いと回答の質も直接的に低下するという弱点があります。また、ドキュメントをどのように分割(チャンキング)するかが性能に大きく影響します。

Advanced RAG (高度なRAG)

Naive RAGの検索精度を向上させるためのパターンです。代表的な手法として、検索前にクエリを改善するQuery Transformationや、検索結果をさらに絞り込むRerankingがあります。Query Transformationでは、ユーザーの曖昧な質問をLLMがより検索に適した形に書き換えます。Rerankingでは、一度多めに情報を取得し、より軽量なモデルで関連性の高い順に並べ替えることで、最終的にLLMに渡す情報の質を高めます。

Agentic RAG (エージェント型RAG)

LLM自身が思考し、自律的に検索を繰り返す、より高度なパターンです。LLMは単に回答を生成するだけでなく、どの情報が足りないかを判断し、追加の検索クエリを生成して複数回の検索を実行できます。このアプローチにより、複雑で多面的な質問に対しても、必要な情報を段階的に収集しながら、深い洞察に基づいた回答を構築することが可能になります。

PythonによるシンプルなRAG実装例

ここでは、sentence-transformers、faiss-cpu、transformersといったライブラリを使って、シンプルなRAGのコンセプトを実装する例を示します。全体の流れを掴むことを目的としています。

まず、ドキュメントを準備し、ベクトル化してインデックスを作成します。


from sentence_transformers import SentenceTransformer
import faiss
import numpy as np

# 1. 知識ソースの準備
documents = [
    "RAGは検索拡張生成の略です。",
    "ベクトルデータベースにはFAISSやPineconeが使われます。",
    "LLMのハルシネーションを抑制する効果があります。"
]

# 2. エンベディングモデルのロードとドキュメントのベクトル化
model = SentenceTransformer('all-MiniLM-L6-v2')
doc_embeddings = model.encode(documents)

# 3. FAISSでインデックスを作成
dimension = doc_embeddings.shape[1]
index = faiss.IndexFlatL2(dimension)
index.add(doc_embeddings.astype('float32'))

次に、ユーザーの質問に対して検索を行い、LLMで回答を生成します。


from transformers import pipeline

# 4. ユーザーの質問と検索
query = "RAGとは何ですか?"
query_embedding = model.encode([query])
distances, indices = index.search(query_embedding.astype('float32'), k=1)

retrieved_doc = documents[indices[0][0]]

# 5. プロンプトの作成とLLMによる生成
prompt = f"""以下の情報を参考にして、質問に答えてください。

情報: {retrieved_doc}

質問: {query}
回答:"""

# ここではHugging Faceのシンプルなモデルを使用
generator = pipeline('text2text-generation', model='google/flan-t5-small')
response = generator(prompt)

print(response[0]['generated_text']) # 出力例: 検索拡張生成の略です

このコードは、RAGの基本的な「検索」と「生成」のフローを非常にシンプルに表現したものです。実際には、LangChainやLlamaIndexといったフレームワークを利用することで、より簡単に高機能なRAGシステムを構築できます。

RAGを導入する際の注意点

RAGシステムを実運用に投入する際には、いくつかの注意点があります。これらを考慮することで、より頑健で高性能なシステムを構築できます。

チャンキング戦略: ドキュメントをどのくらいのサイズ(チャンクサイズ)で分割し、チャンク間にどの程度の重複(オーバーラップ)を持たせるかは、検索精度に直結する重要なパラメータです。ドキュメントの構造(見出し、段落、リストなど)を考慮したセマンティックなチャンキングが効果的な場合もあります。単純な固定長での分割だけでなく、様々な戦略を試すことが重要です。

評価の難しさ: RAGシステムの性能評価は単純ではありません。検索部分の性能(検索された情報が質問と関連しているか)と、生成部分の性能(生成された回答が提供された情報に忠実か、かつ自然で分かりやすいか)を別々に、あるいは総合的に評価する必要があります。RAGAsなどの評価フレームワークを用いることで、コンテキストの再現率や適合率、回答の忠実性などを定量的に測定できます。

コストとレイテンシ: エンベディングモデルやLLMのAPIを呼び出すたびにコストが発生します。また、検索と生成のプロセスには時間がかかるため、リアルタイム性が求められるアプリケーションではレイテンシが問題になることがあります。効率的なモデルの選定や、キャッシュ戦略、インデックスの最適化など、パフォーマンスとコストのバランスを考慮した設計が不可欠です。

まとめ

本記事では、RAG(検索拡張生成)の基本的な仕組みから、より高度な実装パターン、そして導入時の注意点について解説しました。RAGは、LLMの知識を外部の信頼できる情報源で補強することにより、ハルシネーションを抑制し、回答の正確性と鮮度を向上させるための非常に強力な技術です。

シンプルなNaive RAGから、検索精度を高めたAdvanced RAG、自律的に思考するAgentic RAGまで、そのアーキテクチャは進化を続けています。RAGを使いこなすことで、単なるチャットボットを超えた、専門的な知識を持つAIアシスタントや、高精度な情報検索システムなど、LLMの応用範囲は大きく広がります。まずは本記事のコード例などを参考に、ぜひご自身の環境でRAGの世界に触れてみてください。