RAGという技術について、Claude先生に聞きながら調べていって、仕組みがだいたい掴めてきたので、自分の理解でまとめておく。だいぶ端折っている上、ベクトル化など細かい理屈のほうは分かっていないので、悪しからず。

RAGとは

調べてみて、「大きな文章のまとまりから、欲しいものを雑に検索できる、高性能な検索エンジン」と捉えるのが良いと思った。厳密には、その高性能検索エンジンから得られた情報をAIがまとめて文章を作る一連をRAG(Retrieval-Augmented Generation)というが、狭い意味では上記の理解で良いと思う。

RAGの使い方

  1. ユーザーがAIに「リモートワークで集中する工夫、前に何かメモした気がする」と話しかける
  2. AIがこの文をRAGに渡す。渡された文はまずベクトルに変換されてから、DBの近傍検索にかかる。
  3. RAGが意味の近い順にchunkを返す。各chunkにIDやソースの文章がどこにあるかとか、メタ情報がついてくる
  4. AIがそのchunkの中身を読んで、質問に合わせて文章を組み立ててユーザーに返す

ポイントは、返答の文章を作っているのはAI(LLM)で、RAG側は近い切れ端を探して返すところまでしかやっていないということ。生成と検索が別。

RAGの作り方

文書データを用意する

わざわざ検索したくなるということは、自分で掘り返すのが面倒になるくらいの量の文書がある、ということ。まずはそれを集める。

文章をchunk(意味のひと塊)に分ける

RAGが最終的に返すのは、意味が近いと判断された文章の切れ端とその関連情報だった。ということは、データを入れる段階で、ある程度の大きさに切り分けておく必要がある。この塊をchunk(チャンク)と呼ぶ。

切り方にはコツがある。文章の途中でぶつ切りにしていると、意味のまとまりが切れて検索の精度が落ちる。たとえば「私は過去にテキサスに住んでいたことがある。」を「私は過去にテキサスに」と「住んでいたことがある。」の2つに割ってしまうと困る。検索はchunk単位で意味の近いものを取ってくるので、「テキサスに住んでいたという記述を探して」と頼んでも、どちらのchunkも意味が欠けていて、拾ってくる確率が下がる。なので、どう切るかにいくつか方法論があるので紹介する。お急ぎならスキップ可。

切り分け方

  1. 固定長分割(fixed-size)
    「500文字ごと」みたいに機械的に切る。一番単純で一番雑。文の途中で切れる。名前だけ知っておけばいい。

  2. オーバーラップ付き固定長(sliding window)
    固定長だけど、隣のchunkと一部を重ねる。

チャンクA: [0〜500文字]
チャンクB: [400〜900文字]   400〜500が重複

境界で意味が切れても、重なっている部分がどちらかのchunkに残るので救われる。実務でよく使われる。

  1. 文・段落境界での分割(recursive / structural)
    「まず段落で切る、長すぎたら文で切る、それでも長ければ…」と、意味の切れ目を優先して切る。固定長よりだいぶマシ。

  2. 意味的分割(semantic chunking)
    隣り合う文をベクトルにして比べて、意味が大きく変わるところで切る。理想には一番近いけど、切る前にベクトルを計算する必要があって実装が重い。

どれがいいかは中身次第だと思うけど、雑にやりたくないなら2か3あたりから始めるのが無難そう。大きくしすぎると1つのchunkに話題が混ざってベクトルがぼやけるし、小さくしすぎると今度は意味が足りなくなる。ちょうどいい大きさは、まだ自分でも掴めていない。

文章をベクトル化してDBに保存

chunkごとにベクトルへ変換して入れていく。意味を数値の形に置き換える作業をする。この変換には専用のモデルとかAPIを呼ぶことになる。

ベクトル化に使えるツール

何かしらツールを使ってベクトル化を行うことになる。

  • GeminiのAPI(埋め込み用のモデルがある)
  • OpenAIのembeddings API
  • Ollamaでローカルに埋め込みモデルを動かす(bge-m3など)
  • クラウド各社が用意している埋め込みモデル

ローカルで完結させたいならOllama、手軽さで選ぶなら各社のAPI、くらいまで把握しておけば大丈夫そう。