はじめに
最近は、ChatGPT や NotebookLM のように、文書を参照しながら回答する仕組みを手軽に試せるようになってきました。
その中で、よく見かけるようになったのが RAG です。
RAG は Retrieval-Augmented Generation の略で、簡単に言えば、外部文書を検索してから回答を生成する仕組み です。
LLM が自分の内部知識だけで答えるのではなく、必要に応じて関連文書を取り出し、その内容をもとに回答する点が特徴です。
この記事では、RAG の基本的な考え方を整理したうえで、
最小構成の RAG がどのような要素で成り立つのかをまとめます。
RAGとは何か
RAG は、大きく言えば 検索 と 生成 を組み合わせた仕組みです。
通常の LLM は、学習済みの知識をもとに応答を返します。
一方で RAG では、質問を受けたあとに外部文書を検索し、その検索結果を踏まえて回答を生成します。
この違いによって、RAG では次のようなことがしやすくなります。
- 手元の資料を参照した回答
- 特定の文書群に基づく質問応答
- 回答の根拠の明示
- モデル内部にない情報の補完
つまり RAG は、LLM 単体では扱いにくい外部知識を、検索によって補う仕組み だと言えます。
なぜRAGが必要なのか
LLM は非常に便利ですが、そのままではいくつか弱い部分があります。
たとえば、次のような点です。
- 最新情報に弱い
- 手元の文書をそのまま参照できない
- もっともらしい誤答を返すことがある
- 回答の根拠を確認しにくい
こうした問題に対して、RAG では外部文書を検索してから回答を生成することで、
文書に基づく回答をしやすくする ことができます。
もちろん、RAG を入れればすべて解決するわけではありません。
それでも、少なくとも「何を根拠に答えているのか」を取り扱いやすくなる点は大きいです。
RAGの基本的な流れ
RAG の流れは、単純化すると次のようになります。
- 文書を用意する
- 文書を小さく分割する
- 分割した文書をベクトル化して保存する
- 質問をベクトル化する
- 質問に近い chunk を検索する
- 検索結果をもとに LLM が回答を生成する
一般的には、この流れの中で次の用語が使われます。
Chunking
文書を小さな単位に分割する処理です。
文書全体をそのまま検索対象にすると扱いにくいため、一定の長さで区切っておきます。
Embedding
文書や質問をベクトルに変換する処理です。
これにより、意味の近さに基づいた検索がしやすくなります。
Retrieval
質問に関連する chunk を探す処理です。
ベクトルDBを使って近い chunk を検索する形がよく使われます。
Generation
検索で見つかった chunk をもとに、LLM が回答を生成する処理です。
要するに、RAG は
文書を分ける → ベクトル化する → 検索する → 回答する
という流れで動きます。
最小構成のRAGはどう作るか
RAG というと複雑に見えますが、原理確認だけなら、最小構成はそこまで大きくありません。
たとえば、次のような構成でも RAG として成立します。
- ローカルの文書を読み込む
- 文書を chunk に分割する
- chunk を embedding する
- ベクトルDBに保存する
- 質問時に関連 chunk を検索する
- 取得した chunk を LLM に渡して回答させる
この構成では、文書の種類は必ずしも一つである必要はありません。
PDF でもテキストファイルでも Markdown でも、最終的にテキストとして取り出せれば、同じ検索対象として扱えます。
重要なのは、文書形式そのものよりも、
読み込んだ内容をどのように分割し、検索可能な形で保持するか です。
実装上の最低限の要素
最小構成の RAG を考えるとき、最低限必要になる要素はそれほど多くありません。
1. 文書読み込み
まずは検索対象となる文書を読み込みます。
PDF のようなファイルを対象にする場合は、テキスト抽出の処理が必要です。
Markdown やテキストファイルであれば、そのまま比較的扱いやすいです。
2. Chunking
文書はそのままだと長すぎるため、一定の大きさで分割します。
このとき、前後を少し重ねる overlap を入れることがあります。
そうすることで、区切り目で文脈が切れにくくなります。
3. メタデータ
検索後に扱いやすくするため、chunk に最低限のメタデータを付けておくと便利です。
たとえば次のような情報です。
- ファイル名
- ページ番号
- 文書種別
- 保存元のパス
これがあると、回答のあとで参照元を示しやすくなります。
4. Embedding
分割した chunk を embedding モデルでベクトル化します。
質問側も同じようにベクトル化し、近いものを検索できるようにします。
5. ベクトルDB
ベクトル化した chunk を保存し、検索するための保存先が必要です。
Qdrant、FAISS、Chroma など、いくつか選択肢があります。
6. 回答生成
検索で見つかった chunk をそのまま見せるだけでも確認用には使えますが、
通常はそれを LLM に渡して最終回答を生成します。
小さな構成でも分かること
最小構成でも、RAG の特徴はかなり見えてきます。
たとえば、質問に対していきなり回答するのではなく、
まず関連 chunk を取り出してから答えるため、
どの情報を材料にして答えたのか を追いやすくなります。
また、複数の文書をまとめて検索対象にできるため、
一つの資料だけではなく、複数の資料を横断した質問応答も可能になります。
この時点でも、単なるチャットとはかなり違う振る舞いになります。
最小構成の限界
ただし、最小構成の RAG はあくまで最小構成です。
実際に動かしてみると、いくつかの限界が見えてきます。
検索精度の問題
質問に完全には合っていない chunk が混ざることがあります。
意味的には近くても、答えとしては遠い情報を拾ってしまうことがあります。
表記ゆれの問題
人名や専門用語の表記が揺れると、うまく拾えないことがあります。
略称、英語表記、日本語表記の違いなどは典型例です。
chunk 設計の難しさ
chunk を細かくしすぎると文脈が切れやすくなり、
大きくしすぎると検索精度が落ちやすくなります。
最適な粒度は、文書の性質や用途によってかなり変わります。
実用化には追加設計が必要
業務利用や大規模利用を考えると、次のような工夫が必要になります。
- 前処理の改善
- クエリの調整
- rerank
- 権限制御
- 引用の出し方
- 更新時の再取り込み
- 複数ソースの扱い分け
そのため、最小構成は原理理解には向いていますが、
そのまま実用レベルになるわけではありません。
おわりに
RAG は、言葉だけを見ると少し大きな仕組みに見えますが、
最小構成まで分解してみると、やっていること自体は比較的整理しやすいです。
- 文書を読む
- 分割する
- ベクトル化する
- 検索する
- 回答する
まずはこの流れを小さく作ってみるだけでも、
RAG が何をしていて、どこに難しさがあるのかはかなり具体的に見えてきます。
RAG を業務利用の文脈で考えている場合でも、
あるいは個人の文書整理として試したい場合でも、
最初はこうした最小構成から理解していくのがよいと思います。
参考文献
-
Patrick Lewis, Ethan Perez, Aleksandara Piktus, et al.
Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
https://arxiv.org/abs/2005.11401 -
Yunfan Gao, Yun Xiong, Xinyu Gao, et al.
Retrieval-Augmented Generation for Large Language Models: A Survey
https://arxiv.org/abs/2312.10997 -
Qdrant Documentation
https://qdrant.tech/documentation/