LLMの「知識の壁」をどう超えるか
ChatGPTをはじめとする大規模言語モデル(LLM)は、膨大な学習データをもとに驚くほど流暢な回答を生成します。しかし、使い込んでいくと必ず壁に当たります。「学習後に起きた出来事は知らない」「社内の独自ドキュメントについては答えられない」「自信満々に誤った情報を答える(ハルシネーション)」——これらはLLMのアーキテクチャ上の根本的な制約です。
この制約を突破するために登場した実用的なアプローチがRAG(Retrieval-Augmented Generation:検索拡張生成)です。2020年にMetaの研究者が発表したこの手法は、「LLMが答える前に、外部の知識ベースから関連情報を検索して文脈として与える」というシンプルなアイデアです。RAGは現在、企業向けAIシステムの実装において事実上の標準パターンとなっています。
この記事では、RAGの仕組みをゼロから解説し、実装時の設計判断とよくある落とし穴まで、エンジニア視点で掘り下げます。
RAGの基本フロー:3つのステップ
RAGは大きく「インデックス作成フェーズ」と「クエリフェーズ」に分かれます。
インデックス作成フェーズ(事前準備)
まず、回答の根拠となるドキュメントを検索できる形に変換します。
- ドキュメントの収集と分割(Chunking):PDFや社内Wikiなどのテキストを一定のサイズに分割します。LLMのコンテキストウィンドウには限界があるため、ドキュメント全体を丸ごと渡すことはできません。適切なチャンクサイズ(一般的に500〜1000トークン程度)に分割します。
- Embeddingの生成:各チャンクをEmbeddingモデル(OpenAIの
text-embedding-ada-002やOSSのsentence-transformersなど)に通して、テキストを高次元のベクトルに変換します。意味的に近いテキストは、ベクトル空間でも近い位置に配置されます。
- ベクトルDBへの保存:生成したベクトルをベクトルデータベース(Pinecone、Weaviate、Qdrant、pgvectorなど)に保存します。
クエリフェーズ(検索と生成)
ユーザーから質問が来たときのフローは次のとおりです。
- クエリのEmbedding化:ユーザーの質問文を同じEmbeddingモデルでベクトルに変換します。
- 類似度検索:ベクトルDBでコサイン類似度を使って、質問ベクトルに最も近いチャンクを上位k件取得します。
- コンテキスト付きプロンプトの構築:取得したチャンクをコンテキストとしてプロンプトに組み込み、LLMに渡します。
- LLMによる回答生成:LLMはコンテキストを参照しながら回答を生成します。
Pythonの疑似コードで表すと、流れはシンプルです。
def rag_query(user_question: str) -> str:
# 1. クエリをEmbedding化
query_vector = embedding_model.encode(user_question)
# 2. ベクトルDBで類似チャンクを検索
relevant_chunks = vector_db.search(query_vector, top_k=5)
# 3. プロンプトを組み立て
context = "nn".join([chunk.text for chunk in relevant_chunks])
prompt = (
"以下のコンテキストを参照して質問に答えてください。nn"
f"コンテキスト:n{context}nn"
f"質問: {user_question}n回答:"
)
# 4. LLMで回答生成
return llm.generate(prompt)
この仕組みにより、LLMは学習していない社内ドキュメントの内容に基づいて回答できるようになります。
RAGの設計で重要な4つの判断ポイント
RAGを実装するときに設計品質を左右するポイントがいくつかあります。
チャンクサイズの選択
チャンクが小さすぎると文脈が失われ、大きすぎるとノイズが増えてLLMが混乱します。一般的なガイドラインは500〜1000トークンですが、ドキュメントの種類によって最適解は変わります。技術文書のように構造的なものはセクション単位で分割するのが有効です。また、前後のチャンクを少し重複させる「オーバーラップ」(通常50〜100トークン程度)を設けることで、チャンク境界で意味が途切れる問題を緩和できます。
検索方式の選択
ベクトル検索(セマンティック検索)は意味的に近い文章を見つけるのに優れていますが、固有名詞やコードのような完全一致が重要なケースでは精度が落ちることがあります。そこでBM25などのキーワード検索とベクトル検索を組み合わせたハイブリッド検索が実務では有効です。Reciprocal Rank Fusion(RRF)アルゴリズムで両者のスコアを統合するアプローチが広く使われています。
Reranking(再ランキング)の導入
ベクトル検索で上位k件を取得した後、さらに精度の高いクロスエンコーダーモデルで再スコアリングして順位を入れ替えるRerankingは、検索品質を大幅に向上させます。最初の検索(Bi-Encoder)は高速だが粗く、Reranking(Cross-Encoder)は低速だが精度が高い——この2段階構成が多くの本番システムで採用されています。CohereのrerankAPIやBGE-Rerankerなどがよく使われるRerankingの選択肢です。
引用・ソースの提示
RAGの大きなメリットのひとつは、「どのドキュメントに基づいて答えたか」をユーザーに提示できることです。ハルシネーションを完全になくすことはできませんが、ソースを示すことで回答の信頼性検証をユーザー自身が行えます。チャンクにドキュメント名・ページ番号・URLをメタデータとして付与し、回答と一緒に表示する設計が推奨されます。
よくある落とし穴と対処法
「検索できなければ答えられない」問題
RAGはあくまで「検索で見つかった情報」に基づいて答えます。ドキュメントに記載されていない情報、あるいは検索でヒットしなかった情報については、RAGをもってしても正確な回答はできません。「このシステムはドキュメントに載っていない質問には答えられない場合があります」というUX設計と、フォールバック処理が必要です。
チャンクの質の問題
OCRで抽出したPDFのテキストや、Markdownの変換が不完全なHTMLは、ノイズだらけのチャンクになります。データ前処理の品質がRAG全体の精度を直接左右します。「ゴミを入れればゴミが出てくる(Garbage In, Garbage Out)」の原則はRAGでも変わらず成立します。取り込む前にHTMLタグの除去・文字化け修正・重複排除を丁寧に行うことが、後々の精度向上につながります。
Lost in the Middle 問題
上位5〜10チャンクをプロンプトに詰め込みすぎると、LLMが重要な情報を見落とす「Lost in the Middle」問題が発生します。研究により、LLMはプロンプトの最初と最後に置かれた情報を優先的に参照し、中間部分を軽視しがちであることが示されています。関連性の高いチャンクを精度よく選別し、コンテキストに含める件数は必要最小限に抑える設計が重要です。
主要なRAGフレームワークと選定基準
| フレームワーク | 特徴 | 向いているユースケース |
|---|---|---|
| LangChain | 豊富なインテグレーション。モジュール組み合わせが柔軟 | プロトタイプ・多様なデータソースの統合 |
| LlamaIndex | ドキュメント処理とインデックスが強力 | 大量ドキュメントの検索・構造化データとの連携 |
| Haystack | エンタープライズ向け。Rerankingが充実 | 本番品質のQAシステム |
| 自作(フレームワークなし) | 依存が少なく制御しやすい | シンプルなユースケース・特殊要件 |
小規模なプロトタイプにはLangChainやLlamaIndexが素早く試せますが、本番運用では「フレームワークのアップデートで動かなくなる」リスクも考慮し、コアロジックを直接実装する選択も視野に入れます。どちらの場合も、EmbeddingモデルとベクトルDBは独立したコンポーネントとして差し替えられる設計にしておくと、将来の技術変化に柔軟に対応できます。
まとめ
RAGは「LLMに最新・専門知識を与える」最も実用的な手法として、企業のAI活用を一段引き上げています。基本的な仕組みはシンプルですが、チャンキング・ハイブリッド検索・Reranking・データ前処理の品質によって、最終的な回答精度は大きく変わります。
「とりあえずOpenAI APIを叩いてベクトルDBに入れれば動く」という段階から、「精度を本番水準まで引き上げる」段階への移行に、ここで紹介した設計判断が役立ちます。まずは小さなドキュメントセットで動くプロトタイプを作り、評価指標(Precision・Recall・Faithfulness)を計測しながら改善を重ねていくアプローチが、RAG開発の王道です。