このページの目次

RAGと検索強化

データベース全体をウィンドウに詰め込んで、モデルが自力で見つけてくれることを期待しないでください。RAGは「無限の知識」を「まず検索し、ヒットした数ページをコンテキストに読み込む」という形に分解します——回答の品質を決めるのはコンテキストの長さではなく、検索の精度です。

概要

コンテキストエンジニアリング は繰り返し強調しています。⁠検索は強引な詰め込みよりも優れています。 この記事では、その導入部を具体的なエンジニアリングへと展開し、RAG(Retrieval-Augmented Generation、検索強化生成) について解説します。

モデルのパラメータには学習時に目にした知識が圧縮されていますが、プライベートなドキュメント、今日のデータ、本プロジェクトの規約については知りません⁠。さらに、ウィンドウサイズには限りがあり、注意機構は中央部分の情報を「はっきりと見ることができません」(lost in the middle)。2つの行き詰まりがあります。1つ目は、知識ベース全体をシステムプロンプトに詰め込むこと(ウィンドウが溢れ、信号が希釈され、高コストになる)。2つ目は、ファインチューニングで知識を重みに焼き付けること(遅く、高コストで、更新が困難)。RAGは3つ目の道です。⁠知識をウィンドウ外の検索データベースに置いたまま、現在の問題に関連する断片だけを毎回取り出してコンテキストに結合します。

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

コアループ: retrieve → augment → generate

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

2つのフェーズに分かれます。⁠オフラインインデックス構築⁠(ドキュメントをチャンクに分割し、ベクトル化してベクトルデータベースに読み込む、一度きりの処理)と、⁠オンライン検索生成⁠(各リクエストで質問をベクトル化し、検索し、結合し、生成する)。後者は エージェントループ に直接接続されます——検索は1つのツールとなり、モデルがいつ、何を検索するかを判断します。

埋め込みとベクトル検索: なぜ「意味」で検索できるのか

キーワード検索は文字列の一致に基づきます。「GPUのVRAMを節約するにはどうすればよいか」と問いかけても、「KV cache量子化」と書かれたセクションはヒットしません。⁠埋め込み(embedding) はテキストの断片を高次元ベクトルにマッピングし、意味が近いテキストのベクトルも近くなるため、検索は「ベクトル空間での最近傍探索」になります——⁠意味で検索し、文字列の一致ではありません。

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

埋め込みモデル ≠ 生成モデル。これらはそれぞれ独自のトークン化を行い、互いに通用しません。 検索データベースへの埋め込みに使用するモデルと、クエリ時の埋め込みに使用するモデルは同一でなければなりません⁠——モデルを変更すると、データベース全体の再埋め込みが必要になり、異なるモデルではベクトル空間が一致せず、検索が機能しなくなります。

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

ドキュメントはまずチャンク(塊) に分割してから埋め込む必要があります。チャンク分割は RAG において最も過小評価されがちですが、結果に最も大きな影響を与えるステップです。

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

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

ヒット率の向上: 混合検索とリランク

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

  • 混合検索(hybrid): ベクトル検索(意味を扱う)+ キーワード検索 BM25(文字列を扱う)。2つの結果を融合します(例: RRF、reciprocal rank fusion)。「model_context_window_exceeded は何か」と問う際、BM25 はその文字列を正確にヒットさせますが、ベクトル検索は必ずしもそうとは限りません。
  • リランク(rerank): 検索でまず top-50 を粗く取得し、その後 cross-encoder リランカー を用いてクエリと断片のペアごとに精密なスコアリングを行い、本当に最も関連性の高い top-5 を選択してウィンドウに読み込みます。リコールは「見逃さないこと」を、精密順位付けは「過剰なものを除くこと」を担当します——二段階方式は、単に k を大きくするよりもウィンドウを節約し、正確です。

k を大きくしてカバーしようとしてはいけません。 k が大きくなると、ウィンドウに読み込まれるノイズが増え、コンテキストローテーション を引き起こし、トークンを無駄にします。むしろ広くリコールし、厳しく精密順位付けを行い、最後に少数精鋭のページだけを配置しましょう。

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

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

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

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

RAG の失敗は往々にして生成ではなく、検索にあります——⁠モデルが誤答するのは、正しい断片がそもそも検索されていないことが多いからです⁠。そのため、2つのフェーズを分離して評価する必要があります(評価と観測可能性 に接続):

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

問題の特定はまず検索から行います。recall が低い場合は、チャンク調整 / 埋め込みモデルの変更 / リランクの追加に戻ります。recall が高くても誤答する場合、初めて生成やプロンプトの問題となります。

RAG vs 記憶 vs 長文コンテキスト: 混同しないこと

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

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

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

ベストプラクティス

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

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

  • データベース全体をシステムプロンプトに詰め込む⁠: ウィンドウが溢れ、信号が希釈され、高コスト → 検索後はヒットした断片のみを配置。
  • チャンクが大きすぎる/小さすぎる⁠: 大きなチャンクではベクトルが希釈され、小さなチャンクでは意味が断片化 → 意味の境界で分割し、適度なオーバーラップを持たせ、実測で recall を調整。
  • クエリとデータベースの埋め込みが不一致⁠: ベクトル空間が異なり、検索が機能しなくなる → 同一の埋め込みモデルを固定し、モデル変更時はインデックスを再構築。
  • 純粋なベクトル検索で正確な項目を見逃す⁠: エラーコード/固有名詞の文字列一致に失敗 → 混合検索で BM25 を重ねる。
  • k を大きくしてカバーしようとする⁠: ノイズがウィンドウに入り、コンテキストローテーションを引き起こし、トークンを消費 → 広くリコール + 精密順位付け + 少量配置。
  • 生成のみを見て検索を見ない⁠: 誤答をモデルのせいにするが、実際は断片がリコールされていない → recall@k をまず計測し、その後に帰属分析。
  • 検索断片をキャッシュプレフィックスに挿入⁠: 毎回ヒットが異なるため、プレフィックスキャッシュが無効化 → ヒットは後方に配置し、プレフィックスは安定させる。

参考

Keywords: RAG, retrieval-augmented generation, embedding, 埋め込み, cosine similarity, コサイン類似度, 近傍探索, ANN, HNSW, chunk, チャンク, チャンク分割, overlap, オーバーラップ, 分割, BM25, hybrid search, 混合検索, RRF, リランク, cross-encoder, recall@k, precision@k, MRR, citations, 参照, hallucination, 幻覚, 検索は強引な詰め込みよりも優れている, vector database, ベクトルデータベース, sqlite-vec, Qdrant