Amazon Bedrock、Amazon Auroraを組み合わせたRAGで回答精度の向上に取り組んでみた！①概要編

# はじめに
今回はAWS上で実装できるRAGにおいて、回答精度を向上させる手法を調査、検証を行いました。
その際にたまったナレッジを共有いたします。

本記事では、**①概要編**として調査・検証した内容の概要を紹介いたします。
具体的な実装方法については**②実装編**として後日投稿予定です。

なお、本記事の内容は2月6日開催されたJr.Champions勉強会 -Top Engineers参観回で話した内容とほぼ同じなので、その際のスライドも共有します。

<script defer class="speakerdeck-embed" data-slide="1" data-id="ab2e9e27a3854079ae5e53ac7e8c5137" data-ratio="1.7777777777777777" src="//speakerdeck.com/assets/embed.js"></script>

## RAGとは何か？
RAGとは、LLMが外部のDBから情報を取得し回答を生成する仕組みのことで、
社内ナレッジに関する質問に回答できるAIチャットボットなどの用途として昨今注目されています。

## RAGの回答生成までの流れ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/5ec89cc6-4753-21f4-6726-d54d37ca45e4.png)
RAGによる回答生成の一般的な流れは以下の通りです:

**①ドキュメントを前処理して追加
②ユーザーが質問
③質問内容を基に検索
④ドキュメントの一部を取得
⑤たドキュメントと質問をLLMに渡す
⑥回答生成**

## AWSでRAGを実装する方法3選

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/63dd67f1-ba86-4f18-4a01-95125ed63846.png)

AWSはRAGを実装するための複数の方法を提供しています。
主要な3つのアプローチを紹介します。

### 1. [Bedrock](https://aws.amazon.com/jp/bedrock/) ＆ [Kendra](https://aws.amazon.com/jp/kendra/)
このアプローチは、**RAGのフロー処理**(質問から回答生成までの流れ)は開発が必要なもの、ドキュメントの前処理や検索処理は**Kendraの機能**として提供しているため、開発の負担は軽減されています。

### 2. [Knowledge Base for Amazon Bedrock](https://aws.amazon.com/jp/bedrock/knowledge-bases/)
このアプローチでは、表の3処理(**RAGのフロー処理**、**ドキュメントの前処理**、**検索処理**)全てを**AWSサービスの機能**として提供してくれるため、開発は不要です。

また、DBの選択肢も豊富で**OpenSearch Serveless**、**Aurora Serveless**、**Pinecone**、**Redis Enterprise Cloud**と4種類のベクターDBから選択が可能です。

### 3. Bedrock ＆ [ベクターDB](https://aws.amazon.com/jp/what-is/vector-databases/)
このアプローチは表の3処理**全て開発が必要** すが、自由にカスタムで実装できるというメリットがあります。
また、ベクターDBの選択肢も最も豊富です。

今回は3番目の**BedrockとベクターDBを組み合わせる手法**を選択し、それぞれの処理をカスタム実装することで、**精度の向上**に挑戦しました。

また、ベクターDBは、最小コストが低いためスモールスタートしやすく、管理がしやすい**Aurora Serveless**を選択しました。


### ベクターDBを用いたRAGの処理と実装方法
精度向上手法の説明の前に、ベクターDBを用いたRAGの処理の流れとその実装方法の少し具体的な説明を補足します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/4c79b1d8-f150-2ef2-1b77-3ad372cc4511.png)

ベクターDBを用いた場合、ドキュメントは**チャンク**という単位で分割し、それぞれのチャンクを**埋め込みモデル**を用いてベクトル化します。質問内容も同様にベクトル化し、ベクトル検索を通じて関連するチャンクを取得します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/da1f752c-4f82-f903-bf73-54687f6f483d.png)

実装方法として、**Knowledge Base for Amazon Bedrock**は固定のチャンクサイズ、埋め込みモデルなどいくつか項目を設定するだけでRAGを構築できます。
**Bedrock ＆ ベクターDB**ではこれらの処理を**自前で実装**する必要があります。

## 検証した精度向上手法
今回の検証で構成したRAGの構成図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/4ce66374-d7d7-61f5-0f0d-e9fcc3e9297a.png)
以下3つの精度手法を実施しました。

**①Claudeによるチャンク分割
②LangChainの高度な検索機能
③高性能な埋め込みモデル**

### ①Claudeによるチャンク分割
手法の説明の前にチャンク分割とRAGの回答精度への影響について話します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/bcb781ca-0b4e-c63d-eb5e-ea2a7caaba18.png)

例えば、ドキュメントを**固定サイズでチャンク分割**する場合、1つのチャンクに異なるセクションの内容が含まれてしまい、**回答精度が悪化**してしまう可能性が考えられます。

もし、**セクションごとにチャンク分割**できれば、1チャンクに1セクションの内容がまとまっているため、**回答精度の向上につなげる**ことができます。

なので、ドキュメントはセクション単位でチャンク分割することが好ましいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/2ce318b1-53f9-508d-91bd-5441be9b9c6c.png)

よって、セクションごとのチャンク分割するために、**Claudeを用いたチャンク分割処理**を実装しました。

分割処理では、まずドキュメント全体を含むプロンプトをClaudeに渡します。ここで、プロンプトには、セクション単位のJSONを出力するよう命令も追記します。
これにより、セクションのJSONリストを取得できるため、これを基に元のドキュメントを分割させます。

よって、セクションごとにチャンク分割させることができました。

### ②LangChainの高度な検索機能
手法の説明の前に、チャンクサイズとベクトル検索の精度の関係の説明をさせてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/51ff8ae6-9f6f-738f-5913-60cdc76e2ed0.png)

もし、あるクエリ（質問）と似た意味を持つチャンクが**大きい**（例：セクション単位のチャンク）場合、クエリとそのチャンク間のベクトルが遠くなる、つまり、**ベクトル検索でヒットしにくく**なってしまいます。
しかし、LLMに与えたいチャンクは、前述の通りセクションごとに意味がまとまっているのが好ましいです。

逆に、上記のチャンクをさらに**細かく**した場合、質問とそのチャンクのベクトル距離が近くなり、**検索で見つけやすく**なります。

しかし、チャンクを細かくしてしまうと、提供される情報が断片的になってしまいます。

このことから、検索対象としてのチャンクは**小さく**、LLMに渡すチャンクは**大きく**することが好ましいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/ed943985-c7ea-13ed-6008-8cd8341b32c2.png)

これを実現してくれるのがLangChainの[Parent Document Retriever](https://python.langchain.com/docs/modules/data_connection/retrievers/parent_document_retriever)です。
Parent Document Retrieverはドキュメントを2段階で分割します。まず1段階目は親チャンク、2段階目はそれをさらに分割した子チャンクとします。

そして、親チャンクとそのIDをローカルディレクトリに、子チャンクとそれの元の親チャンクのIDをベクターストアに保存します。

そして、そのベクターストアに対してクエリを行うと、**検索対象を子チャンク**にしつつ、さそこからさらに紐づく**親チャンクを取得**することができます。

### ③高性能な埋め込みモデル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/13acf11a-3085-62b6-ecc6-1df5bdd80dc8.png)

埋め込みモデルは、Amazon Bedrockで利用可能な**Cohere Embed Multilingual**を採用しました。
理由は単純で、埋め込みモデルのベンチマークであるMTEBの評価が高いためです。　
他のモデルとの比較すると、Amazon Titan Embeddingsや、text-embedding-ada-002よりベンチマークの数値が高いこと分かります。

この埋め込みモデルの特徴として、一度に埋め込みできる最大サイズが512トークンと小さめです。
しかしながらParent Document Retrieverと組み合わせることで、埋め込むチャンクが小さくても、大きなチャンクをLLMに渡すことができ、両者の弱点を補う構成にすることができました。

## 検証したRAGの評価について

今回検証したRAGの評価方法ですが、[Databricks社の記事](
https://www.databricks.com/blog/LLM-auto-eval-best-practices-RAG)を参考にアレンジを加えました。
回答の**正しさ**、**包括性**（必要な情報を網羅しているか）、**読みやすさ**を評価項目とし、それぞれに重みづけ（正しさ**60%** 、包括性**20%** 、読みやすさ**20%** ）をして、総合評価点を算出しました。

評価のための比較として、冒頭に紹介した3つのRAGの回答精度を比較しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/5c0ef099-c273-374b-99c9-a7f72d5d8331.png)

### 評価に用いたドキュメント
[Amazon Bedrockユーザーガイド](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/bedrock-ug.pdf#what-is-bedrock)におけるページの一部を使用しました。

:::note warn
評価に用いたドキュメントは2024年1月23日時点のものです。現在は更新され内容が変更されています。
:::

### RAGへの質問
公式HPのFAQやドキュメントから質問を以下８つ用意しました。
```
Q1. Amazon Bedrock ではどの モデルが利用できますか?
Q2. Amazon Bedrockにおいてプロヴィジョニングされたスループットで利用できるモデルを教えてください。
Q3. Amazon Bedrockではどのような機能を利用することが出来ますか？
Q4. Amazon Bedrock の使用を開始するにはどうすればよいですか?
Q5. Amazon Bedrock Chat Playground とは何ですか?
Q6. Amazon Bedrock はどの AWS リージョンで利用できますか?
Q7. Amazon Bedrock の料金体系について教えてください
Q8. Amazon Bedrockへのアクセス権を付与するにはどのような方法がありますか？
```

## 評価結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/4461e046-ab11-e90c-dc03-deef041571bb.png)

結果として、今回検証したRAGが点満点のうち**平均2.98点**と、**最も点数が高く**なりました。
Knowledge Base for Amazon Bedrockに関してはドキュメントと関連しない回答が2つありました。
Bedrock ＆ Kendraは包括性が低い回答が見受けられました。

## 苦戦したこと
今回の検証で苦戦したことを紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2988434/7199a6be-e8cd-b5e1-6bf2-351b1ffb9f94.png)

ClaudeからセクションJSONを出力させる処理に大苦戦しました。
苦戦した理由を3つ紹介します。

1つめ、LangChainでBedrockのClaudeを呼び出そうとしたところ、JSONが途中で途切れてしまうケースが頻出したことに苦戦しました。
よって、こちらはboto3を使用してClaudeは呼び出しました。

2つめ、Claude v2.1からセクションJSONを出力させると、JSONの後に余計な文章（例えば「依頼通りセクションJSONを出力しました。」）が付いてしまうことに苦戦しました。
こちらは、Claude instant v1を使用した場合、確実にJSONのみを出力ささせることができました。
しかし、セクションの抽出精度ではClaude v2.1が優れていたため、生成された回答からJSONを抜き出す処理を追加しました。

3つめ、「Amazon Bedrockユーザーガイド」の特定のページからはセクションを正確に抽出できないという問題に苦戦しました。
調べてみた結果、このドキュメントには　“\n\nHuman: \n\nAssistant:”という文字列が多く含まれているため、Claudeが混乱してしまい適切に動作しないことが原因であることが判明しました。
よって、今回の検証ではドキュメントのうち一部のページのみを使用しました。

## まとめ

今回検証した手法でRAGの精度を向上できました。改善手法としては以下の通りです。

**①Claudeによるチャンク分割
②Langchainのparentdocumentretriever
③埋め込みモデルにcohere embed multinguals**

みなさんもぜひ検証してください。

## 参考文献

https://hironsan.hatenablog.com/entry/2023/11/06/133504 

https://tech.explaza.jp/entry/2023/12/04/162659

https://speakerdeck.com/mkazutaka/gptsyorijing-du-nogao-iragsisutemunogou-zhu?slide=30
 
https://qiita.com/cyberBOSE/items/aaf64c73f4da7e4cdb43

https://qiita.com/hayao_k/items/45e59c1c2a183c27b20d

https://dev.classmethod.jp/articles/amazon-bedrock-kendra-lambda-rag/

https://qiita.com/shimajiroxyz/items/facf409b81f59bb68775