このページの目次

RAGと検索強化

モデルに丸ごとデータベースを突っ込んで自分で探させる賭けに出るな。RAGは「無限の知識」を「まず検索し、ヒットした数ページをコンテキストに読み込ませる」に分解する——回答の品質を決めるのはコンテキストの長さではなく、検索の精度である。

概要

コンテキストエンジニアリング は繰り返し強調している:⁠検索は無理やり詰め込むより優れている⁠。この章では、その導入部を実践的なエンジニアリングへと展開する——RAG(Retrieval-Augmented Generation、検索強化生成) である。

モデルのパラメータには学習時に見た知識が圧縮されているが、プライベートなドキュメント、今日のデータ、本プロジェクトの規約については知らない⁠。さらに、ウィンドウは有限であり、注意機構は中間部分が見えにくい(lost in the middle)。二つの行き詰まりがある:知識ベース全体をシステムプロンプトに突っ込む(ウィンドウが溢れ、シグナルが希釈され、高コスト)、あるいはファインチューニングで知識を重みに焼き付ける(遅く、高コスト、更新が困難)。RAG は三つの目の道である:⁠知識をウィンドウ外の検索データベースに置いたまま、毎回、現在の質問に関連する断片だけをコンテキストに読み込ませる⁠。

本質を一言で捉えるなら:RAG は「モデルがどれだけ知っているか」という問題を、「検索システムが正しい断片を前面に出せるか」という問題に変換する。 したがって、回答品質のボトルネックはモデルから検索へと移行する——だからこそ、この章の大部分は「どう生成するか」ではなく、「どう正確に検索するか」について語っているのである。

コアなループ: retrieve → augment → generate

flowchart LR
    Q["ユーザーの質問"] --> EMB["クエリのベクトル化"]
    EMB --> SEARCH["ベクトルデータベース検索<br/>top-k 類似断片"]
    DOCS["知識ベース<br/>(チャンク分割 + 事前embed)"] -.オフラインインデックス構築.-> SEARCH
    SEARCH --> RERANK["rerank<br/>(任意、精査ランク付け)"]
    RERANK --> AUG["コンテキストに結合<br/>ヒット断片 + 質問"]
    AUG --> LLM["モデル生成<br/>(引用付き)"]
    LLM --> A["回答"]

二段階に分かれる:⁠オフラインインデックス構築⁠(ドキュメントをチャンクに分割し、ベクトル化してベクトルデータベースに投入するのは一度きり)とオンライン検索生成⁠(リクエストごとにクエリをベクトル化し、検索し、結合し、生成する)。後者は エージェントループ に直接接続できる——検索は一つのツールとなり、モデルがいつ、何を検索するかを自律的に決定する。

embedding とベクトル検索: なぜ「意味」で探せるのか

キーワード検索は文字通りの一致に依存するため、「GPUのVRAMを節約する方法」を聞いても、「KV cache量子化」が書かれたセクションにはヒットしない。embedding はテキストの断片を高次元ベクトルにマッピングし、意味的に類似したテキストのベクトルも近くなるため、検索は「ベクトル空間内で最近傍を探す」ことになり、文字面ではなく意味で探すことができる。

  • 類似度⁠: 一般的にコサイン類似度(cos similarity)が用いられる。データベース内の各断片を事前に embed して保存し、クエリ時はクエリを一度だけ embed して、データベースベクトルとの類似度を計算し、top-k を取得する。
  • ローカル実行可能⁠: embedding はクラウドに頼る必要はない。llama.cpp は embedding モデル(Qwen3-Embedding、bge-m3 など)を直接実行でき、ローカルベクトルデータベース(sqlite-vec、Qdrant、Chroma)と組み合わせれば、オフラインの RAG 環境が構築できる——ローカルLLMのデプロイ を参照。embedding モデルは小さく、バッチ処理が速く、生成モデルに比べてVRAMの負荷は遥かに小さい。
  • 次元と規模⁠: 数千〜数万の断片であれば、ブルートフォースな最近傍探索で十分である。百万単位になれば、ANN インデックス(HNSW、IVF)を用いて近似検索の速度を取る。

embedding モデル ≠ 生成モデル。これらはそれぞれ独自のトークン化を行い、互いに汎用性がない⁠。検索データベース用の embed 用モデルと、クエリ時の embed 用モデルは同一でなければならない⁠——モデルを変えるとデータベース全体を再 embed する必要があり、そうしないとベクトルが異なる空間にあり、検索は完全に破綻する。

チャンク戦略: 分割が悪ければ、検索がどれだけ正確でも意味がない

ドキュメントはまずチャンク(塊)⁠に分割して embed する必要がある。チャンク分割は RAG において最も過小評価されがちだが、結果に最も影響を与えるステップの一つである:

次元トレードオフ
チャンクサイズ大きすぎると、一つのチャンクに複数のトピックが混在し、ベクトルが希釈され、ヒットしてもノイズが含まれる。小さすぎると意味が不完全になり、「それ」が何を指すのかが不明になる。一般的に 200〜500 トークン。
チャンクオーバーラップ隣接するチャンク間に 10〜20% のオーバーラップを持たせ、一文や一つの論点が途中で切れないようにする。
分割点意味的な境界(見出し、段落、Markdown セクション)で分割する方が、固定文字数で無理やり切るよりも遥かに優れている。コードは関数やクラス単位で切り、行単位では切らない。
メタデータ各チャンクにソース、タイトル、セクションパスを付与する——これによりフィルタリング(特定のドキュメントのみを検索)が可能になり、回答での引用にも使える。

経験則:⁠まずドキュメントの自然な構造で分割する(このサイトでは ## セクション単位)、その後、大きすぎるチャンク内で文字数で補完する。 チャンク分割の品質は、ベクトルデータベースの選定よりも遥かに重要である。

ヒット率向上: 検索のハイブリッド化と rerank

純粋なベクトル検索には盲点がある:正確な固有名詞、エラーコード、ID などは、文字通りの一致の方がむしろ信頼できる。精度を高める二つの手法:

  • ハイブリッド検索⁠: ベクトル検索(意味を扱う)+ キーワード検索 BM25(文字面を扱う)の二つの結果を融合する(例:RRF、reciprocal rank fusion)。「model_context_window_exceeded は何か」と尋ねた際、BM25 はその文字列を正確にヒットさせるが、ベクトル検索は必ずしもそうではない。
  • rerank(精査ランク付け): 検索でまず top-50 を粗く取得し、その後 cross-encoder reranker でクエリと断片のペアごとに精査してスコアリングし、本当に最も関連性の高い top-5 をウィンドウに読み込む。リコールは「漏らさない」ことを管理し、精査ランク付けは「過剰なものを除く」ことを管理する——二段階方式は、単純に k を大きくするよりもウィンドウを節約し、正確である。

k を大きくしてカバーしようとするな。 k が大きくなれば、ウィンドウに読み込まれるノイズが増え、context rot を引き起こし、トークンを無駄にする。むしろ広くリコールし、厳しく精査ランク付けを行い、最後に少数精鋭のページだけを置くべきである。

ウィンドウへの結合: コンテキストエンジニアリングへの復帰

断片を検索した後、それをコンテキストにどう配置するかは、コンテキストエンジニアリング の全結論をそのまま再利用する:

  • 位置⁠: ヒットした断片を注意機構が強く働く位置(system プロンプト直後、または質問の直前)に配置し、明確な区切り文字や XML タグで囲み、中間部分に埋め込まないようにする。
  • キャッシュ⁠: 安定した指示や、変化しない少量の参照情報をプレフィックスに配置する(prompt caching の恩恵を受ける)。⁠毎回変化する検索断片は後方に配置し⁠、プレフィックスに挿入してキャッシュを破壊しないようにする。
  • 引用を付与する⁠: モデルに回答内で、各結論がどの断片に由来するかを明記させる(チャンクメタデータによる)。Claude の citations 機能は、引用された断片に位置情報を付与できるが、構造化出力 output_config.format と排他関係にあるため、どちらか一方を選ぶ必要がある点に注意。

検索の評価: 「正確さ」を定量化する

RAG の失敗は往々にして生成ではなく検索にある——⁠モデルが間違えるのは、正しい断片がそもそも検索されていないからである⁠。したがって、二段階を別々に評価する必要がある(評価と観測 に接続):

  • 検索品質(プログラム可能、最も信頼性が高い): 「正解の場所」が分かっている質問セットに対し、recall@k(正しい断片が top-k に入ったか)、precision@k(top-k にどれくらい真に関連するものがあるか)、MRR を計測する。このステップはコードによるアサートのみで、ゼロコスト、バイアスなし。
  • 生成品質(オープンエンド): 回答が検索内容に忠実か、幻覚(hallucination)がないか、引用が正しいか——LLM-as-judge を用い、審査者と被審査者を分離する。

問題の特定にはまず検索を見る:recall が低い場合はチャンク / embedding の変更 / rerank の追加を見直す。recall が高くても間違える場合、初めて生成やプロンプトの問題である。

RAG vs 記憶 vs 長文コンテキスト: 混同するな

「モデルにもっと知識を持たせる」ために混同されがちな三つの手段の境界を明確にする(詳細は 記憶と状態 を参照):

解決する問題保存場所寿命
長文コンテキスト今回のターンで見るべき全資料ウィンドウ内1ターン限り
RAG膨大な知識から必要に応じて関連断片を取得ウィンドウ外の検索データベースデータベースは永続、毎ターン少量をウィンドウに読み込み
記憶セッションを跨いで好み/結論/失敗経験を記憶セッション外のファイル/データベースセッションを跨いで永続

RAG が取得するのは「客観的な知識の断片」であり、記憶が保存するのは「主観的な経験と結論」である。RAG は毎ターン再検索し、記憶はモデルが能動的に書き込み・読み込みを行う。長期的なエージェントではこれら三つを併用することが多い。

ベストプラクティス

  • まず検索を計測し、次に生成を調整する。 「正解の場所」が分かる小さな評価セットを構築し、recall@k を最大化する——検索が不正確であれば、モデルを変えようがプロンプトを調整しようが、的を射たものにはならない。
  • 意味的な構造でチャンク分割し、十分なメタデータを付与する。 固定文字数よりも見出し/段落/関数の境界を優先する。各チャンクにソースとセクションを付与し、フィルタリングと引用の両方に備える。
  • 広くリコールし、厳しく精査ランク付けし、コンテキストは少なく。 粗検索で top-50 → rerank → top-3〜5 のみ配置。k を大きくして無理やり詰め込まない。
  • クエリとデータベースは同一の embedding モデルを使用する。 モデルを変更するとデータベース全体を再 embed する必要がある。embedding のアップグレードはグラデーション方式で行い、その場で置き換えない。
  • 固有名詞/エラーコード/ID はハイブリッド検索を用いる。 純粋なベクトル検索は文字通りの正確な項目に盲点があるため、BM25 を重ねる。
  • 検索断片はウィンドウの後方に配置する。 プレフィックスキャッシュを保護する。安定した指示を前に、変化しやすいヒットを後に配置する。
  • 回答に引用を付与し、追跡可能にする。 チャンクメタデータで出典を明記し、幻覚を減らし、人的検証を容易にする。

トレードオフと失敗パターン

  • 丸ごとデータベースを system に突っ込む⁠: ウィンドウ溢れ、シグナルの希釈、高コスト → 検索後はヒットした断片のみを配置。
  • チャンクが大きすぎる/小さすぎる⁠: 大きなチャンクはベクトルが希釈され、小さなチャンクは意味が分断される → 意味的な境界で分割し、適度なオーバーラップを持たせ、実測で recall を調整。
  • クエリとデータベースの embedding が不一致⁠: ベクトルが異なる空間にあり、検索が破綻する → 同一の embedding モデルを固定し、モデル変更時はインデックスを再構築。
  • 純粋なベクトル検索で正確な項目を見逃す⁠: エラーコード/固有名詞の文字面一致に失敗 → ハイブリッド検索で BM25 を重ねる。
  • k を大きくしてカバーしようとする⁠: ノイズがウィンドウに入り、context rot を引き起こし、トークンを消費 → 広くリコール + rerank で精査 + 少なく配置。
  • 生成のみを見ている⁠: 間違いをモデルのせいにするが、実際は断片がリコールされていない → recall@k を計測してから帰属分析を行う。
  • 検索断片をキャッシュプレフィックスに挿入する⁠: 毎ターンヒットが異なるため、プレフィックスキャッシュが無効化される → ヒットは後方に配置し、プレフィックスは安定させる。

参考

Keywords: RAG, retrieval-augmented generation, embedding, ベクトル検索, cosine similarity, 最近傍, ANN, HNSW, チャンク, チャンク分割, オーバーラップ, チャンク分割, BM25, ハイブリッド検索, RRF, rerank, cross-encoder, recall@k, precision@k, MRR, citations, hallucination, 検索は無理やり詰め込むより優れている, vector database, sqlite-vec, Qdrant