【AWS Skill Builder】『Building Generative AI Applications Using Amazon Bedrock』コースをやってみた - その 2

## はじめに
[AWS Skill Builder](https://aws.amazon.com/jp/training/digital/)にある『[Building Generative AI Applications Using Amazon Bedrock](https://explore.skillbuilder.aws/learn/courses/17904/building-generative-ai-applications-using-amazon-bedrock/lessons)』というコースを紹介する記事の<strong>「その 2」</strong>になります。

### 記事の構成
その 1：[BedrockとLangChainについて](https://qiita.com/tripleO/items/3446fdb2982441399bfd)
**その 2：この記事**
その 3：[投稿済み](https://qiita.com/tripleO/items/cf01d9b8bbb0a50b6c10)

:::note
Skill Builderのコース説明や記事の概要などは、その1の記事をご覧ください。
:::

今回のその2では、コース内の*Demo 2*の内容に即して、RAGに関する知識を取り扱います。

## RAG
<strong>Retrieval Augmented Generation (RAG)</strong>は、基盤モデル (FM)が外部ソースを必要とするタスクを実行できるようにするシステムです。RAG自体の概念については、以下のKnowledge Baseのセクションで軽く説明しています。

### Knowledge Base
RAGシステムを簡単に構築できる、Amazon Bedrockによるフルマネージドなサービスが、**Knowledge Base**です。RAGの構築とテストが、マネジメントコンソールを使えばノーコードでできます。

#### 作成手順の流れ
1. ナレッジベースの名前や他サービスへのアクセス権限を持つロールを作成する
1. ドキュメント類などの、RAGに取得させたい外部ソースを保存する場所（=**データソース**）を選択する
    - コース内のデモでは*Amazon S3*を利用
1. ベクトル埋め込みをする基盤モデルを選択
1. 埋め込みしたベクトルを保存する、**ベクトルストア**を選択
    - コース内のデモでは*Amazon OpenSearch Serverless*を利用
1. 以上の設定を完了し作成後、回答を生成するLLMを選択
1. データソースにデータを格納

ここまでがRAG構築の最低限の設定であり、データソースを同期させればRAGを動かすことができます。

:::note
ベクトルストアの他にも、**構造化データストア**[^1]や**グラフデータ**[^2]などを使用したRAGもあります。
:::

[^1]: https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-build-structured.html
[^2]: https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-build-graphs.html

#### Knowledge Baseの動作フロー
<dl>
  <dt>事前準備（データソースの同期）</dt>
  <dd>Knowledge Baseは、事前にデータソースのドキュメントデータを埋め込みして、ベクトルストアに保存します。</dd>
  <dd>
      <ol>
          <li>データソースのデータをいくつかのチャンクに分割</li>
          <li>チャンクをベクトルに埋め込み</li>
          <li>ベクトルをベクトルストアに保存</li>
      </ol>
  </dd>
  <dt>ランタイム</dt>
  <dd>ユーザーがクエリをしてから、Knowledge Baseがレスポンスを返すまでの処理は、</dd>
  <dd>
      <ol>
          <li>ユーザーのクエリを埋め込み</li>
          <li>ベクトルストアで、ユーザーのクエリと<strong>関連性の高い</strong>ドキュメントを検索</li>
          <li>取得したドキュメントをユーザーのクエリと合わせて、LLMにリクエストする</li>
          <li>LLMが与えられたドキュメントの情報（= <strong>コンテキスト</strong>）を基に、クエリに回答</li>
          <li>LLMからのレスポンスをユーザーに返す</li>
      </ol>
  </dd>
  <dd>といった感じです。「あるサービスのQA」などの限られた特定の知識が必要なクエリだとしても、LLMにドキュメントを渡す事で、そのようなクエリに対応させることができるというわけです。</dd>
  <dd>
      <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946195/b3b2d698-4369-4aad-86aa-3f90c754740a.jpeg" width="600">
  </dd>
</dl>

### APIメソッド
#### *RetrieveAndGenerate*
Knowledge Baseを用いて生成された回答を得るには、`RetrieveAndGenerate`メソッドを使用します。
```python:example.py
 response = bedrock_agent_runtime.retrieve_and_generate(
    input = {
        'text': 'When was the AnyCompany established?' # ユーザーからの入力
    },
    retrieveAndGenerateConfiguration = {
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': kbId, # 作成時に生成されるID
            'modelArn': f'arn:aws:bedrock:{region_name}::foundation-model/{model_id}', # LLMのリソースネーム
            'retrievalConfiguration': {
                'vectorSearchConfiguration': {
                    'numberOfResults': 6, # ベクトルストアから検索して取得するドキュメントの数
                    'overrideSearchType': 'HYBRID'
                }
            }
        },
        'type': 'KNOWLEDGE_BASE'
    }
)

generated_text = response['output']['text'] # 生成された回答
```
ドキュメント → [retrieve_and_generate - Boto3 1.37.9 documentation](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent-runtime/client/retrieve_and_generate.html)

#### *Retrieve*
Knowledge Baseを用いて、ベクトルストアの検索まで（[上述のランタイムのフロー](#knowledge-baseの動作フロー)でいう2.まで）の結果を取得するには、`Retrieve`メソッドを使用します。
このメソッドは、RAGの検索後のワークフローをカスタマイズしたい時などに役立ちます。
```python:example.py
 response = bedrock_agent_runtime.retrieve(
    retrievalQuery = {
        'text': 'When was the AnyCompany established?' # ユーザーからの入力
    },
    knowledgeBaseId = kbId, # 作成時に生成されるID
    retrievalConfiguration = {
        'vectorSearchConfiguration': {
            'numberOfResults': 6, # ベクトルストアから検索して取得するドキュメントの数
            'overrideSearchType': 'HYBRID'
        }
    }
)

retrievalResults = response['retrievalResults'] # 取得したドキュメントごとの配列
```
ドキュメント → [retrieve - Boto3 1.37.9 documentation](http://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent-runtime/client/retrieve.html)

### RAGの評価方法
RAGアプリケーションを作成したら、パフォーマンスを最適化してより正確さを上げるために、アプリケーションを評価することが大切です。<strong>RAG Assessment (RAGAS)</strong>は、RAGを評価するためのオープンソースのフレームワークです。

RAGASでは、パフォーマンスを測るための様々なメトリクスを計測できます。以下はそのメトリクスの例です。
1. **Faithfulness**
    - LLMに送られたコンテキストと、それを基に生成された回答を比較して計測される値
    - `0~1`の数値
    - 低いほど**ハルシネーション**であることを示唆する
1. **Answer relevance**
    - 生成された回答が、コンテキストやユーザーのクエリとどれだけ関連性があるかを示す値
    - `-1~1`の数値
    - 低いほど、回答が不完全であるか、冗長な情報を含むことを示す
1. **Context recall**
    - ユーザーの質問に対して想定される回答（出力の正解）と、コンテキストの内容から計算される値
    - `0~1`の数値
    - 低いほど、出力の正解とコンテキストの間に乖離が見られる
1. **Context precision**
    - 出力の正解とコンテキストの内容、ユーザーのクエリから計測される値
    - `0~1`の数値
    - 低いほど、コンテキストの中に関連のある情報が少ないことを意味する

参考 → [Overview - Ragas](https://docs.ragas.io/en/latest/concepts/metrics/overview/)

### RAGの応用テクニック
#### 1. チャンク戦略
RAGではデータソースのドキュメントを埋め込む際、チャンクに分割するのが一般的です。その分割方法において効果的な戦略を取れるかどうかが、RAGのパフォーマンスに大きく関わってきます。

チャンク戦略には以下の4つがあります。
1. 固定長 - ***fixed***
    - チャンクサイズを1つの値に固定して分割する
    - チャンクサイズは、大きすぎると検索の精度と速度が落ちる一方、小さすぎると情報の不足や途切れた文章による意味の欠落を招くため、適切な粒度を見極める必要がある
1. セマンティック - ***semantic***
    - テキストを文の区切りや、文章のトピック、つまり文脈ごとに分割する
    - チャンクごとのサイズに違いが出るが、途中で文が途切れることはない
1. 階層的 - ***hierarchical***
    - 複雑に構造化したドキュメント内の、ネストされた構成や関係性を基に階層的にチャンクを分割する
    - 法的文書や技術マニュアル、学術的な記事などの場合に向いている

また、いくつかの戦略を組み合わせて利用する、ハイブリッドなアプローチが有用なケースもあります。

:::note warn
Knowledge Baseでは、ベクトルストアに***Amazon OpenSearch Serverless*を利用している場合のみ**、`HYBRID`検索タイプが指定できます。[^3]
:::

[^3]: https://docs.aws.amazon.com/bedrock/latest/userguide/kb-test-config.html の「***Search type***」より

#### 2. Query reformulation （クエリ分解）
ユーザーのクエリは、複数の質問を含んで複雑になることがあります。そのように複数のニュアンスがクエリに含まれていると、RAGのベクトル検索において、一部のニュアンスのみが結果に反映されたり、関連性を精度良く計れず曖昧な結果になったりと、パフォーマンスの悪化を引き起こします。

それを解決するのが、**Query reformulation or decomposition** （日本語にするなら**クエリ分解**）です。複数の質問を含むようなクエリを、質問ごとのサブクエリに分割しそれぞれを別々に処理することで、上記のようなパフォーマンスの悪化を防ぐことができます。

Knowledge Baseの[***RetrieveAndGenerate***](#retrieveandgenerate)メソッドでは、クエリ分解を有効にする設定が可能です。
```python:example.py
'orchestrationConfiguration': {
    'queryTransformationConfiguration': {
        'type': 'QUERY_DECOMPOSITION' # クエリ分解を有効にする
    }
}
```

#### 3. Guardrails
**Amazon Bedrock Guardrails**は、AIが攻撃的な内容や倫理に反する回答を生成することを防ぎ、RAGアプリケーションの信頼性や安全性を守るための機能です。

例えば、以下のような設定ができます。
- 特定のトピックを拒否
- コンテンツのフィルタリング
- センシティブな情報を設定

## Demo 2
### 構成
コースの*Demo 2*には、RAGに関する以下の5つの話題に対して、それぞれ実装例が用意されています。
1. `RetrieveAndGenerate`によるRAGの使用
1. `Retrieve`を使ったRAGのカスタマイズ
1. クエリ分解
1. RAGASによるRAGの評価
1. Guardrailsの設定

### 補足事項
- プロファイルとリソースのリージョンの一致
    デモのコードでは、読み込んだ**プロファイルに設定されたリージョン**内でKnowledge Baseを検索するため（しかも最初の1つ）、**そのリソースを作成したリージョン**と一致している必要があります。
    （しかし、Knowledge BaseのIDを取得するために検索APIを利用するので、IDをハードコーディングか環境変数などで渡すようにすれば、そもそも検索するコードが不要です）
    <br>

- ベクトルストアに***Pinecone***を利用しよう
    デモでベクトルストアに設定する*Amazon OpenSearch Serverless*は、かなり費用がかかります。
    代替案として、[***Pinecone***](https://www.pinecone.io/)の利用をオススメします。AWS外のサービスですが、「*Starter*」という**無料プラン**があり、デモを実施するには十分です。詳しい設定方法は、[公式ブログの記事](https://aws.amazon.com/jp/blogs/news/leveraging-pinecone-on-aws-marketplace-as-a-knowledge-base-for-amazon-bedrock/)をご参照ください。
    
    :::note warn
    前述しましたが、*Pinecone*の場合は`HYBRID`検索タイプが使用できません。[^3]
        :::

    （追記）
    APIKeyの保存に使用する***SecretsManager***の課金が発生します[^4]。結構高いのでデモの実施が終わり使用しなくなったら、削除する方が良さそうです。

    [^4]: https://aws.amazon.com/jp/secrets-manager/pricing/?did=ap_card&trk=ap_card （執筆時点で1シークレットあたり、**$0.40 / 月**）

    ※ベクトルストアのコストについては、以下の記事がかなり参考になります。

https://zenn.dev/konta_dev/articles/28808875c2419f#%E3%82%B3%E3%82%B9%E3%83%88%E6%AF%94%E8%BC%83

- LangChainのライブラリについて
    デモ内のコードで登場する、LangChainライブラリの`AmazonKnowledgeBasesRetriever`クラスが非推奨になっています。しかし、ただライブラリの場所が変わっただけっぽいので、以下のようにインポートを変更すれば問題なさそうです。
    ```diff_python:Task-2.ipynb
    - from langchain.retrievers import AmazonKnowledgeBasesRetriever
    + from langchain_aws.retrievers.bedrock import AmazonKnowledgeBasesRetriever
    ```
    <br>

- 必要なポリシー
    デモの実行には、Bedrockサービスに対するポリシー以外にも必須のポリシーがあります。マネジメントコンソールにログインするIAMユーザー等に、以下のポリシーを許可して下さい。（ローカル開発環境と上述した*Pinecone*を使う場合）
    - S3
        - `s3:ListBucket`
        - `s3:ListAllMyBuckets`
        - `s3:PutEncryptionConfiguration`
        - `s3:GetObject`
        - `s3:CreateBucket`
        - `s3:PutObject`
    - IAM
        - `iam:CreateRole`
        - `iam:CreatePolicy`
        - `iam:CreatePolicyVersion`
        - `iam:AttachRolePolicy`
        特にKnowledge Baseの作成時に必要
    - STS
        - `sts:GetCallerIdentity`
        `Task-3.ipynb`でAWSアカウントID取得のために必要となりますが、アカウントID使っていないので取得のコード消せば不要…
    <br>

- pandasライブラリの依存関係
    Knowledge Baseは全く関係ありませんが、コードの実行中（2025年2月）にpandasライブラリ関係のエラーに遭遇したので、補足として残します。
    以下の記事を参考に対処しました。

https://pineplanter.moo.jp/non-it-salaryman/2023/01/01/python-jinja2/

## おわり
Knowledge BaseでRAGを簡単に構築できて便利だと思います。
あと、英単語に当てはまるいい感じの訳を考えるのが結構難しかったです。

その3に続きます。

https://qiita.com/tripleO/items/cf01d9b8bbb0a50b6c10

## 参考リンク
https://zenn.dev/konta_dev/articles/28808875c2419f#%E3%82%B3%E3%82%B9%E3%83%88%E6%AF%94%E8%BC%83