シンプルなRAGパイプラインを作る:概念的な道のり
検索拡張生成を一段階ずつ組み立てます。魔法も特定のスタックもなし。パイプラインの形と、重要となる判断だけをお伝えします。
検索拡張生成(RAG)は、単一の技術のように聞こえます。実際にはパイプラインです。小さくありふれた段階の連なりで、それらが一緒になって、言語モデルが訓練中に記憶したものだけでなく、あなた自身の文書を使って質問に答えられるようにします。どの段階もそれ単独では巧妙ではありません。技は、それらを接続して、モデルが答えを書く瞬間に、あなたのテキストの適切な一節がその目の前に置かれているようにすることにあります。本稿は、そのパイプラインを一段階ずつ組み立て、各段階がどこで壊れがちかを指摘します。
そもそもなぜRAGが存在するのか
言語モデルは、訓練されたものは知っています。あなたの会社の手引き、先週のインシデント報告書、一分前にアップロードしたPDFの中身は知りません。その資料でモデルをファインチューニングすることもできますが、それは高価で、更新が遅く、間違えやすいものです。RAGはより安い道を取ります。モデルはそのままにして、質問ごとに関連する一節を見つけ、プロンプトに貼り付けるのです。そうすればモデルは、持っていないかもしれない記憶からではなく、見えるテキストから答えます。
メンタルモデルは、本を開いた有能なアシスタントです。アシスタントは賢いがあなたの個別事情には無知で、本は個別事情を持つが推論できません。RAGは、適切なページを適切なときにアシスタントに手渡します。以下のすべては「適切なページを適切なときに」のためにあります。
段階1:文書をチャンクに分ける
モデルに図書館全体を手渡すことはできないので、文書をチャンクと呼ばれる小さな断片に分割します。チャンクとは一節にすぎません。数段落、一セクション、一ページです。チャンク分割は見た目以上に重要です。大きすぎるチャンクは、関連する一文を周囲のノイズで薄め、プロンプトの場所を無駄にします。小さすぎるチャンクは、一文を意味あるものにする文脈を失います。「これはサポートされていません」という一行は、「これ」が何かを述べる段落がなければ役に立ちません。
妥当なデフォルトは、文書の自然な構造に沿って、つまり闇雲な文字数ではなくセクション・見出し・段落ごとにチャンク分けすることです。関連する考えを一緒に保ちましょう。多くのパイプラインはチャンクをわずかに重複させ、境界近くの一文が少なくとも一つのチャンクには丸ごと現れるようにします。普遍的に正しいサイズはありません。それは文書次第であり、結果を測定できるようになったら見直す価値があります。
段階2:埋め込みとベクトルストア
後で関連するチャンクを見つけるには、質問を、単にキーワードを一致させるのではなく意味によってあらゆるチャンクと比較する方法が必要です。これが埋め込みの提供するものです。埋め込みモデルは、テキストの断片を数のリスト、つまりベクトルに変え、似た意味のテキストがその数値空間で互いに近くに着地するように配置します。「パスワードをリセットするには?」と「アカウントアクセスを回復する手順」は共有する単語をほとんど使いませんが、ベクトルとしては近くに座ります。
各チャンクを埋め込みモデルに一度通し、各チャンクのベクトルを元のテキストとともに保存します。その集まりはベクトルストアに置かれます。「どの保存済みベクトルがこれに最も近いか?」に、何百万件にわたってさえ素早く答えるために作られたデータベースです。小さなプロジェクトなら、ベクトルストアはシンプルなインメモリ構造で構いません。スケールすれば専用のデータベースになります。インターフェースはどちらでも同じです。ベクトルを入れ、最近傍を返してもらう。
段階3:クエリ時の検索
さて、パイプラインがライブで動きます。ユーザーが質問します。チャンクに使ったのと同じモデルで質問を埋め込みます。これは重要です。異なるモデルのベクトルは比較できないからです。その質問ベクトルをベクトルストアに手渡し、最近傍のチャンクを求めます。ストアは上位の数件、つまり意味が質問に最も近い一節を返します。
「何件か」は現実の判断です。少なすぎれば、答えを持つ一節を取り逃すリスクがあります。多すぎれば、わずかに関連するだけのテキストでプロンプトを混雑させ、コストがかさみ、モデルの気を散らします。少ない数、つまり答えをカバーするには十分だが、信号がノイズに溺れるほど多くはない数が、通常の出発点です。検索はまた、純粋な意味検索が製品コードや名前のような厳密な用語でつまずくことがある場所でもあり、だからこそ一部のパイプラインは昔ながらのキーワード検索とそれを混ぜます。シンプルに始め、失敗が見えたときにだけそれを加えましょう。
段階4:プロンプトの組み立て
今やユーザーの質問と、検索された数個のチャンクがあります。生成ステップはそれらを単一のプロンプトに組み立てます。概念的にはこう見えます。
You are answering using only the context below.
If the answer is not in the context, say you don't know.
Context:
[chunk 1 text]
[chunk 2 text]
[chunk 3 text]
Question: [the user's question]
ここにある二つの指示が、静かに重い働きをしています。「以下のコンテキストのみを使って」は、モデルに自分の記憶よりも提供された一節を優先するよう告げます。それがRAGの肝心要です。「答えがコンテキストにない場合は、分からないと言う」は、モデルに辞退する許可を与えます。これがないと、モデルはその空白を自信に満ちた推測で埋める傾向があります。その失敗モードに名前を付けることが、正直な「見つかりません」と捏造との分かれ目です。
段階5:生成と出典の引用
組み立てられたプロンプトが言語モデルに渡され、モデルは検索されたテキストに根ざした答えを書きます。各チャンクの元の出典を保持しておいたので、ファインチューニングにはできないことができます。答えがどこから来たかを示すのです。各チャンクに識別子、つまり文書のタイトル・セクション・ページを携えさせ、モデルにそれを参照させるか、単に出典の一節を答えの下に表示します。引用は、不透明な応答を、ユーザーが検証できるものに変えます。そして検証可能性こそが、しばしばRAGシステムをデプロイできるほど信頼に足るものにするのです。
ここはパイプラインの正直さが試される場所でもあります。検索が間違った一節を渡したなら、モデルは間違った一節から流暢に答えます。自信に満ちた答えは、正しい答えの証拠ではありません。これが、最初に作る多くのものが飛ばす部分へと直接つながります。
RAGパイプラインが実際に壊れる場所
失敗はめったにモデルにありません。上流の、検索にあります。関連するチャンクが一度も返されなければ、世界最高のモデルでもそれを使えません。「ゴミを入れれば、流暢なゴミが出る」です。よくある犯人は、答えが境界をまたいでどのチャンクにも丸ごと現れないように分割されたチャンク、あなたのドメインの語彙を捉えない埋め込みモデル、あるいは単にチャンクを少なく求めすぎたこと。RAGシステムが間違った答えを出したら、まずモデルを責めるのを抑えましょう。その質問に対して検索が実際に何を返したかを見るのです。たいていの場合、答えは検索された集合の中にまったくなく、修正はチャンク分割か検索にあって、プロンプトにはありません。
これを捉える方法は、答えをすでに知っている実際の質問でパイプラインを評価し、最終テキストだけでなく検索されたチャンクを点検することです。正しい一節を検索し、それからよく答えるパイプラインは機能しています。間違った一節を検索しながら運良くよく答えるものは、より難しい質問を待つバグです。
まとめ
RAGは一つの手品ではなく、短い組み立てラインです。文書を賢明にチャンク分けし、ベクトルストアへ埋め込み、質問ごとに最近傍のチャンクを検索し、それらを根拠ある一つのプロンプトに組み立て、出典を引用する答えを生成する。モデルは易しい部分です。システム全体の品質は、チャンク分割と検索、つまり適切なページを適切な瞬間にモデルの前に置くことで決まります。各段階を平易に作り、それから実際の質問で検索を測定すれば、失敗が最も印象的に見える場所ではなく、実際に潜む場所に労力を費やすことになります。
