RAG でナレッジベース Q&A システムを構築
LLM はあなたのプライベートで最新の資料を知らず、ハルシネーションも起きがちです。RAG(検索拡張生成)は「まず検索、そして生成」により、信頼できるナレッジベースに基づいて回答させます。現在最も実用的な本番手法の一つです。
著者:AI Resource Hub
なぜ RAG が必要か
モデルを直接ファインチューニングして知識を注入するのは高コストで更新も遅いです。RAG は知識を外部データベースに置き、回答時に関連箇所を検索してモデルに渡すため、安価で常に最新に保てます。
パイプラインの概要
典型的な RAG システムは2段階です。オフラインの「インデックス段階」で文書を検索可能なベクトルに変換し、オンラインの「クエリ段階」でユーザーの質問に基づいて検索・生成します。
文書分割と Embedding
まず文書を適切な大きさのチャンクに分割します。大きすぎると検索が不正確に、小さすぎると文脈が失われます。一般的には数百トークンで一定の重複を持たせます。
次に Embedding モデルで各チャンクをベクトル化し、ベクトルデータベース(pgvector、Milvus、Qdrant など)に保存します。
ベクトル検索と Top-K
ユーザーが質問したら、質問もベクトル化してデータベースで類似度検索を行い、最も関連するチャンク(Top-K)を取得します。
キーワード検索と組み合わせた「ハイブリッド検索」や、リランキングで再現品質をさらに高められます。
コンテキストを組み立て回答を生成
検索したチャンクをプロンプトに挿入し、「提供された資料のみに基づいて回答し、資料にない場合はその旨を明記する」と指示することで、ハルシネーションを大幅に減らせます。
高度な最適化には、クエリ書き換え、出典引用、コンテキスト長の制御、検索結果の重複排除とフィルタリングがあります。
ベクトルデータベースの選択
ベクトルデータベースは RAG の中核です。プロトタイプにはゼロセットアップのメモリ内実行 Chroma が最適。本番ワークロードには Qdrant(Rust ベース)と Pinecone(フルマネージド)が本番導入向けに設計されています。現在の制限と拡張方法は各ドキュメントで確認してください。PostgreSQL を既に使っているなら pgvector で新インフラ追加を避けられます。Weaviate は組み込みベクトル化モジュールと GraphQL API に優れます。
RAG の品質評価
RAG システムの良し悪しは検索と生成の両方に依存します。検索は Recall@K(正しいチャンクが Top-K に含まれるか)で測定し、生成は忠実度(回答が検索コンテキストに根ざしているか)と関連性(質問に実際に答えているか)で測定します。RAGAS などのフレームワークがこれらの指標を自動化し、チャンキング戦略・Embedding モデル・プロンプトテンプレートの反復中に品質を追跡できます。
始める前に:前提条件
小さくても代表的な文書群、それを使う権限、きれいなテキストを抽出できるパーサーが必要です(PDF やスキャン文書は多くの場合 OCR が必要です)。自分の環境で動く Embedding モデルとベクトルストアを選び、次元数と言語対応を確認します。実際の質問を 20〜50 個用意し、それぞれに答えるべき文書や箇所をラベル付けします。最後に、文書の更新方法と、どのコレクションを誰が読めるかを決めます。権限は UI だけでなく検索時に強制する必要があります。
始めるのに巨大なコーパスは不要です。構造の整った数百ページあれば、規模を広げる前にパイプラインを検証できます。
検証:回答より先に検索を確認する
まず検索をテストします。ラベル付きの各質問で検索を実行し、上位結果を確認します。期待する箇所が Top-K に入り、無関係なチャンクが上位を占めていないこと。外れている場合は、生成プロンプトを触る前に、チャンクサイズ、オーバーラップ、メタデータフィルタ、Embedding モデルを調整します。次に回答を確認します。モデルは検索した箇所を引用または抜粋し、資料に答えがない場合はその旨を明示すべきです。固定の評価セットを保ち、変更のたびに再実行して劣化を把握します。さらに、答えが存在しない質問、似た文書が2つある質問、対応する各言語の質問も試します。
RAG のよくある失敗と対処
この順で調べます。検索結果が空なら、文書がインデックスされているか、クエリと文書の Embedding が同じモデル・次元か、フィルタがコレクションを除外していないかを確認します。正しいチャンクがあるのに順位が低いなら、チャンク境界を見直し、ハイブリッドなキーワード検索やリランキングを試します。回答が検索コンテキストを無視するなら、チャンクが実際にプロンプトへ入り、切り捨てられていないか確認し、「提供資料のみに基づいて回答し出典を示す」よう指示を強めます。回答が古いなら、文書更新後に再インデックス処理が走ったか確認します。重複が Top-K を埋めるなら、投入前に文書 ID か内容ハッシュで重複排除します。最後に、アクセス制御フィルタが生成後ではなく検索時に効いているか確認します。
RAG が適さない場合
ナレッジベース全体がモデルのコンテキストウィンドウに余裕をもって収まり、更新もまれなら、RAG は不向きです。文書をそのまま渡す方が簡単です。資料が信頼できない、矛盾している、または使えるテキストがない画像中心の場合も弱いです。計算、トランザクション、リアルタイムの状態が必要なら、それについての文章を検索するのではなくデータベースや API を呼びます。一度だけの権威ある照会なら、検索インデックスや構造化クエリの方が正確なことがあります。索引を保守する人がいないと検索システムはすぐ劣化します。小さく整理された文書の方がユーザーの役に立つこともあります。
チャンク分割の一歩
この関数は、埋め込みの前に文書を分割するインデックス段階を解きます。出発点の下書きであり、計測結果ではありません。失敗:overlap が 0 だと、切れ目に乗った事実はどちらのチャンクにも入らず、検索で戻せません。
def chunk_text(text, size=400, overlap=50):
chunks = []
start = 0
while start < len(text):
chunks.append(text[start:start + size])
if start + size >= len(text):
break
start += size - overlap
return chunks