ゼロから始めるAIシステム開発 #07 「RAGアプリを作ってみる」

入門書の4章で学んだことのまとめ。RAGについての情報と、実際にアプリを作ってみるハンズオン。
**Amazon Bedrock 生成AIアプリ開発入門 ［AWS深掘りガイド］**
https://amzn.asia/d/co7MB5S
## RAGとは
Retorieval-Augmented Generation（検索により拡張された生成）の略。LLMの学習内容にない社内情報などを検索、取得してLLMに入力することによって回答の品質を上げる手法。AIによるハルシネーション（それっぽい嘘を言うこと）を回避する対策の一つ。
## 「埋め込み」について
RAGを使用する際に質問と関連する文章を見つけるための方策であり、テキストや画像などを「ベクトル」に変換する手法のこと。単語同士のベクトルの角度の近似によって関連性を検知することができ、これを**セマンティック検索**という。
## Knowledge basesでRAGを実装してみる
「Knowledge bases for Amazon Bedrock」はAWSのコンソールからGUI操作だけでRAGアーキテクチャを実装できる機能。質問を埋め込みモデルで受けてベクトルDBへベクトル変換し、検索結果を取得してLLMに渡す機能を有する。（参照させたい社内情報などは別のS3バケットなどに格納する。）
## ナレッジベース上で動作確認
まずドキュメント（今回はBedrockのマニュアル）を読ませない場合の回答を確認する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/614c143b-9658-c9a3-6052-29ffd450bc60.png)
そんなの無理だよ、と見事にハルシネーションを返してくる。（Claudiaって誰だよ。）入門書の内容とも違うので執筆時点から学習内容に変化があったのだろうか。
そこでBedrockのマニュアルを読ませて同期し、同じ質問をしてみる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/8d9dc03b-b438-83d2-25dc-d471b0bc9922.png)
するとマニュアルにある内容をきちんと返してくるようになる。

```python:1_rag.py
# 外部ライブラリをインポート
import streamlit as st
from langchain_aws import ChatBedrock
from langchain_aws.retrievers import AmazonKnowledgeBasesRetriever
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough

# 検索手段を指定
retriever = AmazonKnowledgeBasesRetriever(
    knowledge_base_id="XXXXXXXXXX",  # ここにナレッジベースIDを記載する
    retrieval_config={"vectorSearchConfiguration": {"numberOfResults": 10}},
)

# プロンプトのテンプレートを定義
prompt = ChatPromptTemplate.from_template(
    "以下のcontextに基づいて回答してください: {context} / 質問: {question}"
)

# LLMを指定
model = ChatBedrock(
    model_id="anthropic.claude-3-sonnet-20240229-v1:0",
    model_kwargs={"max_tokens": 1000},
)

# チェーンを定義（検索 → プロンプト作成 → LLM呼び出し → 結果を取得）
chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

# フロントエンドを記述
st.title("おしえて！Bedrock")
question = st.text_input("質問を入力")
button = st.button("質問する")

# ボタンが押されたらチェーン実行結果を表示
if button:
    st.write(chain.invoke(question))
```
- LangChainとStreamlitの外部ライブラリをインポート
- LangChainのRetrieverにナレッジベースのIDを指定
- LLMへの回答形式を指示（プロンプトのテンプレートを定義）
- LLMを指定
- 検索→プロンプト作成→LLM呼び出し→結果のチェーンを定義
- Streamlitでアプリ画面を作成、結果表示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3885923/b51094eb-0095-546d-6e7e-db5db872379a.png)
## Knowledge basasのクエリ設定について
回答の精度向上のためにクエリ（命令）を詳細に設定することも可能。
### 検索タイプ
セマンティック検索と全文検索のいいとこどりをした**ハイブリッド検索**なども選べる
### 推論パラメーター
回答のランダム性や回答可能なトークン上限などを調整できる
### ソースチャンク最大数
チャンクの取得件数を最大100まで調整できる。一般的な値は存在しないのでシステムに合わせたチューニングが必要
### メタデータによるフィルター
ドキュメントにメタデータ（地域など）を付与し、絞り込み検索が可能
### プロンプトテンプレート
回答を生成するときLLMモデルに対応するテンプレが使用されるが、これをカスタマイズ可能
### ガードレール
モデルへの望ましくない入出力をフィルターできる
## RAGアーキテクチャの例
### とりあえず試したい場合
**Knowledge basas**がおすすめ。ただし自動的に生成されるOpenSearch Serverlessのコレクションが高額なので注意。
### 回答品質を重視
**OpenSearch Serverless**のハイブリッド検索を用いるのがおすすめ。日本語に適したプラグインなどもある。
### データソースとの接続性重視
Knowledge basasの場合は検索対象によってはS3バケットへのデータ配置がしづらいケースがあるので、**Kendra**が候補になる。Kendraには**Webクローラー**など各所のデータソースに対応したコネクタがある。比較的高額な点に注意。
## RAGの回答品質を上げるための工夫
### チャンクサイズの調整
ドキュメントをベクトル変換する際にチャンク（塊）に分割する必要があるが、その分割し方を工夫する。段落ごとに分けたり、前チャンクの最後尾と後ろチャンクの頭の文字が重なる（**オーバーラップ**）ように分けたりすると品質が向上する。
### メタデータの追加
↑のクエリ設定と同様にメタデータを付与しておくと絞り込みが可能。
### リランク
取得された検索結果をユーザーの質問内容と関連性の高い順に並べること。
### RAGフュージョン
ユーザーの質問を**視点を変えた複数の質問に変換**し、それらの結果を集約して上述のリランクをして最終的な回答を生成する。
### Rewrite-Retrieve-Read
ユーザーの質問を検索に適したクエリに置き換えてからRAGを行う手法。人間が検索するときにするような変換をすること（例：「有給休暇の申請ってどうすればいい？」→「有給休暇」「申請方法」）
### HyDE
「仮説的なドキュメント埋め込み」の略。仮の回答を用意してそれを検索すること。（例：「信長の好物は？」を「信長はマカロンを好んだとされる」にすることで「XXXを好んだとされる」という回答がヒットしやすくなる
### CRAG
「修正的RAG」の略。検索結果の妥当性を正しい・曖昧・誤りの3評価に分類して、誤り・曖昧の場合は別の検索を追加で行い結果を補完するなど、評価に応じたアクションを追加する手法のこと。
## まとめ
Pythonの勉強と並行なのでちょっと投稿頻度が落ちてしまった。4章はコードを実装するハンズオンよりRAGのテクニックの記述にボリュームが割かれていた印象。HyDEとかRAGフュージョンの概念は思わずあったまいい～！と膝を打った。普通にLLMを利用する上でも重宝しそうなヒラメキである。具体的なRAGサービスや結果の評価ツールの紹介ついても記述があったが割愛。
次回は5章、**AIエージェント**についての予定。