RAGをLangChainを使わずに作ってみる（理解編）

# はじめに
今回は最近流行りのRAG(Retrieval-Augmented Generation)をLangChainのようにRAG環境の構築を一括でセットアップする機能を使用せずに処理を構築した内容を紹介したいと思います。記事を書いていて少し長くなったので理解編と構築編に分けているので、コードの詳細に関しては構築編を見て頂けると幸いです。

- シリーズ
    - [RAGをLangChainを使わずに作ってみる（理解編）](https://qiita.com/DeOma004/items/0ab1c8afa0667ee71ac2)
    - [RAGをLangChainを使わずに作ってみる（実装編）](https://qiita.com/DeOma004/items/9e80afaefd3c2191822e)

今回の目的はLangChainを使わずにRAGを作るため、モデルの精度など細かいところは考慮していません。

# RAGのやりたいこと
## RAGのメリット
RAGとはChatGPTやGeminiなどのLLMモデルに対して、引用する情報をあたえることで正確な回答を生成させることを目的とした技術です。RAGを利用し、引用情報をあたえることで以下のような利点があります。

- 最新の情報やモデルが未知な情報を参照できる
- 詳細で正確な回答を生成できる

まず1つ目の「最新の情報やモデルが未知な情報を参照できる」利点は、学習時で止まっているモデルの知識を補えることになります。なので、RAGを活用することで世間一般的な知識を引用するだけでなく、社内情報や特定の内容を引用することでドメインに特化したモデルをつくることができます。

2つ目の「詳細で正確な回答を生成できる」利点は、信頼できる引用情報があることでテキトウな回答を減らすことになります。モデルは複雑な質問や未知の単語を与えられた際にある程度、意味を類推して回答を生成します。ただし、モデル単体では類推する精度に関しては正しいと保証できるものではありません。なので、類推するために複数の情報を明示的に与えることで精度を高めようとする意図があります。

## RAG処理の概要
RAGの処理は以下の2つの処理に分けることができ、それぞれの大まかな処理は以下のようになります。

- Inferencing: RAGをつかったLLMの推論
    1. LLMモデルに質問(prompt)の投げる
    2. 質問(prompt)を埋め込みモデルでベクトルに変換する
    3. 変換したベクトルをもとにDB内を検索する
    4. 検索結果の引用情報を質問(prompt)に埋め込む
    5. 引用情報を埋め込んだ質問(prompt)を用いて推論を行う

- Ingestion: ベクトルDBの準備
    1. 関連文書を任意のサイズ(chunk_size)で分割する
    2. 関連文書を埋め込みベクトルに変換する
    3. DBに関連文書とベクトルを記録する


InferencingはDBから取得した引用情報をpromptに加えてLLMに推論させるための処理になり、Ingestionは引用情報を検索するためにDBへ情報をあらかじめ蓄積する処理になります。処理を図示すると以下のように表現でき、赤く囲った部分がIngestion、青く囲った部分がInferencingとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/3915b965-f75b-3984-30b3-59f563271e87.png)

# RAGをつかったLLMの推論を分解してみる
ここでは先ほどの図の青い部分であるInferencingの部分の処理を詳しく見てみましょう。

## 1.LLMへの質問文(prompt)の投げかけ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/c50d65cc-eed8-aef5-19ec-322f360c2b03.png)

はじめにLLMへpromptを投げて回答が返ってくるところから始めましょう。この処理に対してRAG特有の処理を肉付けしていきます。

## 2.引用情報を利用した質問文(prompt)の投げかけ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/89946dc7-77c5-7933-9072-f1b9c7afb131.png)

最終的にはモデルへ投げるpromptに引用情報を載せて推論を行うことになります。そのため、新しく加えられた内容としてはpromptに引用情報が付加されており、その内容から回答する旨の指示が増えています。
promptの内容に関しては様々な工夫点があると思いますが、大まかには以下のような内容になります。

- RAGを用いる際のprompt例
    ```
    ## 指示: 
    以下の「引用情報」を元に質問に回答してください。
    なお、「引用情報」に無い情報は回答に含めないでください。

    ## 引用情報: 
    <<引用情報が埋め込まれる場所>>
    ```

次のstepで引用情報をどこから持ってくるかを見ていきましょう。

## 3.引用情報の検索
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/8a102f80-9ea0-f737-7e4a-3dc8d994e9c6.png)

ここでは質問文(prompt)に関連する情報をDBから検索する処理が増えています。
RAGでは関連する情報をDBにあらかじめ登録しておき、質問文を用いて関連のある情報を検索します。

では、次のstepで引用情報をどのようにして探してくるのか具体的に見ていきましょう。


## 4.引用情報のベクトル検索
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/c129f65f-595a-cc5f-571c-fcf28a1d819d.png)

ここでの検索はDBを用いた検索になります。検索方法は様々ありますが、RAGで用いられる検索方法はベクトル検索と呼ばれる方法が多く用いられます。ベクトル検索とは検索元のデータとDBのデータをベクトルに変換し、cos類似度などの類似度からデータを検索する方法になります。
なので、この部分では質問文をなんらかの方法でベクトル化し、ベクトルをクエリとしたベクトル検索を行う処理が増えています。
後ほど、DBのにあるベクトル化されたデータの詳細については後ほど説明するので、一旦準備されているものとします。

実際のサービスではベクトル検索と他の手法と組み合わせる工夫がされていますが、ここは単純化のためにベクトル検索だけに着目します。

それではベクトル検索のベクトルはどのようにして得るかを次のstepで見ていきましょう。

## 5.埋め込みベクトルへの変換
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/eafc4b5f-1b4c-63e5-e888-fec7da446c15.png)

ベクトル検索で用いるベクトルは埋め込みモデル(embedding model)を用いて質問文(prompt)をベクトル化し検索を行います。
埋め込みモデル(embedding model)は文章をベクトルに変換するためのものになります。この埋め込みモデルはOpenAIからChatGPT用のモデルが提供されていたり、hugging faceであらかじめ作成されたモデルが公開されています。
なので、べクトル変換の最も難し部分はカプセル化されておりライブラリやAPIの使用方法がわかれば実装上では問題ないようになっています。

また、埋め込みモデルで変換されたベクトルのことを埋め込みベクトルと言い、変換処理をエンベッティング(embedding)と言います。


## Inferencingのまとめ
ここで、Inferencingの流れを順番になぞると以下のような処理をたどりLLMの推論が可能になります。

1. LLMモデルに質問(prompt)の投げる
2. 質問(prompt)を埋め込みモデルでベクトルに変換する
3. 変換したベクトルをもとにDB内を検索する
4. 検索結果の引用情報を質問(prompt)に埋め込む
5. 引用情報を埋め込んだ質問(prompt)を用いて推論を行う

# ベクトルDBの準備を分解してみる
ここでは先ほどの図の青い部分であるIngestionの部分の処理を詳しく見てみましょう。

## 1.関連文書を埋め込みベクトルに変換する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/25084214-e130-5763-93ac-95fb0264be66.png)

ベクトル検索を行うDBはあらかじめ関連文書をベクトルに変換しておく必要があります。なので、推論時に使用する埋め込みモデルを使用し、関連文書をベクトルに変換します。
ただ、関連文書すべてを1つの埋め込みベクトルに変換すればよいというわけではなく、すこし工夫する必要があります。では、次のstepでその工夫点であるchunkingを見てみましょう。

## 2.関連文書をchunkingしてDBへ登録する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682846/7bf0e6e3-a7ba-569b-03d2-26fa37f0eb6e.png)

ここではベクトルに変換する関連文書を任意のサイズ(chunk_size)で分割する処理になります。
chunk_sizeで分割する意図としては検索結果の情報量を調整するためになります。
検索結果として文書全体が返ってきたとしても、具体的にどの部分を引用すれば良いかがわからないと思います。なので、検索結果として適切な粒度となるように分割し、埋め込みベクトルに変化します。
また、chunk_sizeを調整方法はさまざまあり、RAGの出力精度に影響を与える箇所になっています。

## Ingestionのまとめ
ここで、Ingestionの流れを順番になぞると以下のような処理で関連文書のDBが完成します。

1. 関連文書を任意のサイズ(chunk_size)で分割する
2. 関連文書を埋め込みベクトルに変換する
3. DBに関連文書とベクトルを記録する


# おわりに
RAGにおける主な処理のInferencingとIngestionに関しては以上になります。次回は紹介した処理に沿って具体的なコードを見ていきたいと思います。