BedrockのナレッジベースでRAGを実装し、資料を元にした回答をClaude 3にLambda経由でしてもらった(ベクトルストアはAurora)

[Supership](https://supership.jp/)の名畑です。たくさんの思い出を生み出してくれた中野のシンボルとも言える中野サンプラザとのお別れイベントである[中野サンプラザプロジェクションマッピング](https://www.city.tokyo-nakano.lg.jp/kanko/city-promotion/0686357420231212172102894.html)、とても素晴らしかったです。別れを惜しみながらも、再会を楽しみに待っております。

## はじめに

前に「[LambdaでBedrockのClaude 3を呼び出してみた](https://qiita.com/nabata/items/5bcc3beb76182f626040)」という記事を書きました。[Anthropic](https://www.anthropic.com/)による**LLM**である[Claude 3](https://www.anthropic.com/news/claude-3-family)を[AWS](https://aws.amazon.com/jp/)の[Labmda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)と[Bedrock](https://aws.amazon.com/jp/bedrock/)で呼び出すという内容でした。

今回はその続きです。

[S3](https://aws.amazon.com/jp/s3/)にアップロードした資料をデータソースとして**RAG**(検索拡張生成)を実装します。

> Retrieval-Augmented Generation (RAG) は、大規模言語モデル（LLM）によるテキスト生成に、外部情報の検索を組み合わせることで、回答精度を向上させる技術のこと。
>
> 参考：[RAG | 用語解説 | 野村総合研究所(NRI)](https://www.nri.com/jp/knowledge/glossary/lst/alphabet/rag)

また、**RAG**の実現のために**Bedrock**の[ナレッジベース](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base.html)を用います。

> Amazon Bedrock のナレッジベースでは、データソースを情報のリポジトリにまとめることができます。ナレッジベースを使用すると、検索拡張生成 (RAG) を活用したアプリケーションを簡単に構築できます。RAG は、データソースから情報を取得することでモデルレスポンスの生成を強化する手法です。
> 
> 参考：[Amazon Bedrock のナレッジベース - Amazon Bedrock](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base.html)

## AWSへのログイン時の注意

ルートユーザーでは今回の記事内容が行えないのでご注意ください。

> ルートユーザーを使用してナレッジベースを作成することはできません。これらの手順を開始する前に、IAM ユーザーでログインします。
> 
> 参考：[ナレッジベースを作成する - Amazon Bedrock](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-create.html)

## Bedrockへのモデル追加

[前回の記事](https://qiita.com/nabata/items/5bcc3beb76182f626040)では**Claude 3 Sonnet**のみにアクセス権限を付与しました。

今回はそれに追加して**Titan Embeddings G1 - Text v1.2**を用いるため、あらかじめモデルアクセスをリクエストしておきます。

まず**リージョン**を **ua-east-1 米国東部 (バージニア北部)** として、**Bedrock**のサイドメニューから**モデルアクセス**のページを開きます。

モデル二つを選んで「**変更を保存**」を押します。リロードをして「**アクセスが付与されました**」の状態になっていればOKです。

![claude_rag_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/da5c5fb0-fb69-89ba-68ba-21d4030a6399.png)


## boto3のダウンロード

[前回の記事](https://qiita.com/nabata/items/5bcc3beb76182f626040)では**AWS**にデフォルトで用意されている**boto3**を用いました。
「[Lambda ランタイム - AWS Lambda](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-runtimes.html)」によると、現在の**boto3**のバージョンは**Python3.12**の場合は以下でした。

- boto3-1.28.72
- botocore-1.31.72

しかし[boto3のリリースノート](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)を見ると、今回利用しようとしている**bedrock-agent-runtime**は**1.33.2**で導入されています。

そのため、今回は最新の**boto3**をローカルにダウンロードし、それを後ほど**Lambda**にアップロードすることにします。
この章ではダウンロードのみを先んじて行っておきます。

まずローカルでの**Python**のバージョンが**Lambda**同様に**3.12**であることを確認します。

```shell-session
$ python --version
Python 3.12.2
```

次に**python**という名称のフォルダに**boto3**をダウンロードします。フォルダ名は必ず**python**としてください。

```shell-session
$ python -m pip install -t ./python boto3
```

これをzip形式で**boto3.zip**として固めておきます。後ほど使います。

```shell-session
$ zip -r boto3.zip ./python
```

## S3へのファイルアップロード

「[OpenAIのAssistants APIでRAG(検索拡張生成)を実装してみた](https://qiita.com/nabata/items/db8890da03df1d81997a)」で作成した下記の内容のテキストファイルである**company.txt**を流用します。

```text
会社名：株式会社あいうえおほげ
社員数：65536人
事業内容：アニメと漫画についての話し相手になること
所在：東京都港区
社長：漫画好木
社風：静か
休日：水曜
```

**S3**を開き、バケットを作成します。バケット名はわかりやすいものにしておきます。たとえば**knowledge-base-data-source-claude**のようなものです。

このバケットに**company.txt**をアップロードします。

![claude_rag_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/573e1235-d9b0-d4ea-9f7a-6558c11bbb9c.png)

## Auroraデータベースの作成

**ナレッジベース**を動作させるためには**ベクトルストア**というものが必要となります。

デフォルトの選択肢は[Amazon OpenSearch Serverless](https://aws.amazon.com/jp/opensearch-service/features/serverless/)ですが、設定等にもよるでしょうが、私が試した限りでは放置状態でも1日で**5ドル**程度がかかるため、テスト用途としてはやや高価に感じられる方も多いかもしれません。
素晴らしいサービスであり、試すにはかなり楽ではあるのですが、今回は2024年の2月にナレッジベースでの対応が発表されたばかりの[Aurora](https://aws.amazon.com/jp/rds/aurora/)を使うことにします。設定次第では価格を半分以下に抑えられるため。

- 参考：[Amazon OpenSearch Service の料金](https://aws.amazon.com/jp/opensearch-service/pricing/)
- 参考：[Amazon Aurora の料金](https://aws.amazon.com/jp/rds/aurora/pricing/)
- 参考：[Amazon Bedrock のナレッジベースで Amazon Aurora PostgreSQL および Cohere 埋め込みモデルのサポートを開始 | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/knowledge-bases-for-amazon-bedrock-now-supports-amazon-aurora-postgresql-and-cohere-embedding-models/)

以下、**Aurora**でのデータベース作成手順です。公式ドキュメントである「[Build generative AI applications with Amazon Aurora and Knowledge Bases for Amazon Bedrock](https://aws.amazon.com/jp/blogs/database/build-generative-ai-applications-with-amazon-aurora-and-knowledge-bases-for-amazon-bedrock/)」に従った内容となります。

**Aurora**ではなく**Amazon OpenSearch Serverless**を使う場合は後述する「[ナレッジベースの作成](#ナレッジベースの作成)」まで飛んでください。

まず[RDS](https://aws.amazon.com/jp/rds/)を開き「**データベースの作成**」を押します。
そして作成するデータベースの設定を選択、入力していきます。

### データベース作成方法を選択

- 標準作成

### エンジンのオプション

- エンジンのタイプ：Aurora (PostgreSQL Compatible)
- 利用可能なバージョン：PostgreSQL 15.5

### テンプレート

- 開発/テスト

今回は動作確認用のため**開発/テスト**としました。

### 設定

**DB クラスター識別子**と**マスターユーザー名**を入れてください。わかりやすいものにしましょう。

**Credentials management**ではマスターユーザーの接続認証方法を指定します。
**Managed in AWS Secrets Manager**を選べば[Secrets Manager](https://aws.amazon.com/jp/secrets-manager/)にあるユーザー情報と紐づきます。
紐づくユーザー情報がない場合は**Self managed**を選んで、**パスワード**を指定します。指定したパスワードは忘れないようにしましょう。

### クラスターストレージ設定

- Aurora I/O最適化

**Aurora I/O最適化**について詳しくは「[AWS が Amazon Aurora I/O 最適化をリリース](https://aws.amazon.com/jp/about-aws/whats-new/2023/05/amazon-aurora-i-o-optimized/)」をご参照ください。

### インスタンスの設定

- Serverless v2
- 最小ACU：0.5
- 最大ACU：1

**Serverless v2**について詳しくは「[Aurora Serverless v2 の働き](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.how-it-works.html)」をご参照ください。

**ACU**は**Aurora Capacity Unit**を意味します。今回は設定可能な最低値としています。

> Aurora Serverless では、データベース容量は 1 秒あたりに請求される Aurora Capacity Unit (ACU) で測定されます。1 ACU には、対応する CPU とネットワークを備えた約 2 GiB のメモリがあり、Aurora でプロビジョンしたインスタンスで使用されているのと類似のものです。
> 
> 参考：[Amazon Aurora の料金](https://aws.amazon.com/jp/rds/aurora/pricing/)

### 可用性と耐久性

デフォルトのままです。

### 接続

「**RDS Data APIの有効化**」にチェックを入れます。
それ以外はデフォルトのままです。

### Tags

デフォルトのままです。

### Babelfishの設定

デフォルトのままです。

### データベース認証

デフォルトのままです。

### モニタリング

今回は動作確認までのため「**Performance Insightsをオンにする**」のチェックをはずしました。

### 追加設定

デフォルトのままです。

ここまでの入力を終え「**データベースの作成**」を押します。

「**データベースの起動には数分かかる場合があります**」とのことなので、しばらく待ちます。

作成されたデータベースの**設定**で**ARN**を確認できます。この**ARN**は後ほど使います。

## クエリの実行

データベース作成完了後、同じく**RDS**にて**クエリエディタ**を開きます。

「**データベースインスタンスまたはクラスター**」は作成したものを選択してください。

**データベースユーザー名**は**Secrets manager ARN**での管理であれば「**Secrets manager ARNと接続する**」を選んで、該当の**Secrets manager ARN**を入力します。
データベース作成時に**Self managed**を選んだ場合は、その際のユーザー名とパスワードを入れてください。ここで入力したものは**Secrets Manager**に自動で保存されます。

「**データベースの名前**」は**postgres**としてください。

入力後「**データベースに接続します**」を押します。

エディタが表示されますので以下のクエリを貼り付けます。「**ここにパスワードを書く**」のところに接続用のパスワードを書いてください。書いたパスワードで初期設定されます。後ほど使いますので忘れないようにしましょう。

```SQL
CREATE EXTENSION IF NOT EXISTS vector;
CREATE SCHEMA bedrock_integration;
CREATE ROLE bedrock_user WITH PASSWORD 'ここにパスワードを書く' LOGIN;
GRANT ALL ON SCHEMA bedrock_integration to bedrock_user;
SET SESSION AUTHORIZATION bedrock_user;
CREATE TABLE bedrock_integration.bedrock_kb (
  id uuid PRIMARY KEY,
  embedding vector(1536),
  chunks text,
  metadata json
);
CREATE INDEX ON bedrock_integration.bedrock_kb
  USING hnsw (embedding vector_cosine_ops);
GRANT ALL ON bedrock_integration.bedrock_kb to bedrock_user;
```

![claude_rag_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/e80ffaa7-9c47-9204-b883-94badd88b773.png)

**実行**すると結果が表示されます。

成功していれば以下のように**Status**に**success**が並ぶはずです。

![claude_rag_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9a730d18-5b6f-69b8-15fb-4d75fbe124d1.png)

## シークレットの作成

**Secrets Manager**を開いて「**新しいシークレットを保存する**」を押します。

4ステップあります。

### ステップ1　シークレットのタイプを選択

![claude_rag_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/6e0c9e5c-2bf7-0b82-301b-58ae23d446ef.png)

シークレットのタイプは「**Amazon RDS データベースの認証情報**」を選びます。
ユーザー名は**bedrock_user**と入れます。パスワードは先ほどのクエリで指定したパスワードを入れます。

**データベース**は作成済みのデータベースを指定します。

### ステップ2　シークレットを設定

シークレットの名前の入力画面です。
わかりやすい名前をつけましょう

### ステップ3　ローテーションを設定

今回は設定せずに**次**を押します。

### ステップ4　レビュー

ここで内容を確認して、問題なければ**保存**を押します。

シークレットが作成され**ARN**が発行されます。この**ARN**も後ほど使います。

## ナレッジベースの作成

ついに**ナレッジベース**の作成です。

**Bedrock**の**ナレッジベース**のページに移動し「**ナレッジベースを作成**」を押します。

4ステップあります。

### ステップ1　ナレッジベースの詳細を入力

**ナレッジベース名**を入力してください。

**IAM許可**については既存のサービスロールがなければ**新しいサービスロールを作成して使用**を選択し、**ロール名**を決めてください。ロール名は **AmazonBedrockExecutionRoleForKnowledgeBase_** から始める必要があります。

- 参考：[KnowledgeBase - Amazon Bedrock](https://docs.aws.amazon.com/ja_jp/bedrock/latest/APIReference/API_agent_KnowledgeBase.html)

### ステップ2　データソースを設定

**データソース名**を入力してください。

**S3 URI**については「**S3を参照**」を押し、先ほど作成した**S3バケット**を選択します。

### ステップ3　埋め込みモデルを選択し、ベクトルストアを設定する

> 埋め込みモデルは、ドキュメントなどのデータをベクトル埋め込みに変換します。ベクトル埋め込みは、ドキュメント内のテキストデータの数値表現です。
> 
> 略
> 
> 各ベクトル埋め込みは、多くの場合、埋め込みが作成された元のコンテンツへの参照などの追加のメタデータとともに、ベクトルストアに格納されます。ベクトルストアは、保存されたベクトル埋め込みにインデックスを付けて、関連データを迅速に取得できるようにします。
> 
> 参考：[Amazon Bedrock のナレッジベースで Amazon Aurora PostgreSQL および Cohere 埋め込みモデルのサポートを開始 | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/knowledge-bases-for-amazon-bedrock-now-supports-amazon-aurora-postgresql-and-cohere-embedding-models/)

今回は**埋め込みモデル**は冒頭にてアクセス付与済みの**Titan Embeddings G1 - Text v1.2**とします。

![claude_rag_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/4586dfcd-45a8-d90c-2c5e-eb0c72cbddc3.png)

次に- **ベクトルデータベース**の設定項目です。

今回は作成済みの**Aurora**を用いるため、「**ベクトルストアの作成方法を選択**」としては「**作成したベクトルスコア**」を選んでください。仮にここで「**新しいベクトルストアをクイック作成**」を選んだ場合は自動で**Amazon OpenSearch Serverless**のベクトルストアが作成されますので以降の設定は不要です。

選択、入力項目は以下です。**ARN**のみ、環境毎に異なります。それ以外は固定値です。

- **既存のデータベースを選択**：Amazon Aurora
- **Amazon Aurora DB クラスター ARN**：作成したデータベースのARNを入れてください
- **データベース名**：postgres
- **テーブル名**：bedrock\_integration.bedrock\_kb
- **シークレット ARN**：作成したシークレットのARNを入れてください
- **ベクトルフィールド**：embedding
- **テキストフィールド**：chunks
- **Bedrock マネージドメタデータフィールド**：metadata
- **プライマリーキー**：id

![claude_rag_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/c6ac0416-0c14-3b84-5f2c-ecb4cc780d1d.png)

### ステップ4　確認して作成

内容を確認して「**ナレッジベースを作成**」を押します。

作成完了まで数分程度待つと

> ナレッジベース「**指定したナレッジベース名**」が正常に作成されました。データを同期してテストを開始してください。

と出ます。

![claude_rag_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/7edb4887-7cc0-91e3-f68b-ff1f63e79c9a.png)

**データソースを同期**しましょう。
同期は**データソース**である**S3**のファイルを更新する度に行う必要があります。詳しくは「[同期してデータソースをナレッジベースに取り込む - Amazon Bedrock](https://docs.aws.amazon.com/ja_jp/bedrock/latest/userguide/knowledge-base-ingest.html)」をご参照ください。

また、画面上で**ナレッジベースID**が確認できます。
この値は**Lambda**から呼び出すときに使います。

## ナレッジベースをブラウザ上でテスト

作成した**ナレッジベース**を**Lambda**から呼ぶ前に、ブラウザ上でテストしてみます。

![claude_rag_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/fc66f408-7ba2-1268-931d-458845963c66.png)

モデルの選択が必要なため、上記画像の通り**Claude 3 Sonnet**を選びました。

![claude_rag_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/9ca88138-8911-97b3-0285-d9612d9abef5.png)

「**あなたの会社名を教えてください**」と質問すると「**私の会社の名前は株式会社あいうえおほげです。**」と答えてくれました。ちゃんと資料を参照してくれています。

![claude_rag_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/e6994506-45dd-c1fb-004c-ec7feacd2955.png)

**結果の詳細を表示**すると、どこからデータを引っ張ってきたかも確認できます。

## boto3のアップロード

ここからは**Lambda**の設定です。

まず、**Lambda**を開き、先ほど用意した**boto3.zip**をアップロードします。

![claude_rag_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/5ac9b65e-a7a4-6357-c77e-d36ca5614153.png)

**Lambda**でサイドメニューから**レイヤー**を選んで「**レイヤーの作成**」をクリックします。
名前は**boto3**としておきます。

**boto3.zip**を**アップロードするファイル**として選びます。

互換性のあるアーキテクチャは**Lambda**関数の設定に合わせて**arm64**を選びます。
互換性のあるランタイムも合わせて**python3.12**とします。

最後に**作成**をクリックします。

## Lambda関数にレイヤーを紐づけ

[前回の記事](https://qiita.com/nabata/items/5bcc3beb76182f626040)で作成した関数**LambdaToBedrockAccess**のコード編集画面に移動し「**レイヤーの追加**」でカスタムレイヤーから**boto3**を選びます。

![claude_rag_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/fe267a27-2c5f-97a8-89d9-f11dd70f66ed.png)

無事に追加されています。

## コード

前回のコードを書き換えます。モデルは**Claude 3 Sonnet**を指定しています。

```python
import json
import boto3

def lambda_handler(event, context):
    bedrock_agent_runtime = boto3.client(service_name='bedrock-agent-runtime', region_name='us-east-1')

    knowledge_base_Id = "作成済みのナレッジベースIDを書く"
    model_arn = "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0"
    
    response = bedrock_agent_runtime.retrieve_and_generate(
        input={
            'text': event.get("message")
        },
        retrieveAndGenerateConfiguration={
            'knowledgeBaseConfiguration': {
                'knowledgeBaseId': knowledge_base_Id,
                'modelArn': model_arn
            },
            'type': 'KNOWLEDGE_BASE'
        }
    )

    return {
        'statusCode': 200,
        'body': response
    }
```

今回は**boto3**の**retrieve\_and\_generate**という関数を用いています。仕様について詳しくは以下あたりをご覧ください。

- 参考：[retrieve\_and\_generate - Boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-agent-runtime/client/retrieve_and_generate.html)
- 参考：[RetrieveAndGenerate - Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_agent-runtime_RetrieveAndGenerate.html)

## テスト

以下の**json**を渡して「**テスト**」を押し、呼び出してみます。

```json
{
  "message": "あなたの会社名を教えてください"
}
```

![claude_rag_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/869934/fbcff21d-96ed-8711-69a6-4621d5f00c05.png)

レスポンスは以下でした。

```json
{
  "statusCode": 200,
  "body": {
  
  略
  
    "citations": [
      {
        "generatedResponsePart": {
          "textResponsePart": {
            "span": {
              "end": 17,
              "start": 0
            },
            "text": "私の会社の名前は株式会社あいうえおほげです。"
          }
        },
        "retrievedReferences": [
          {
            "content": {
              "text": "会社名：株式会社あいうえおほげ 社員数：65536人 事業内容：アニメと漫画についての話し相手になること 所在：東京都港区 社長：漫画好木 社風：静か 休日：水曜"
            },
            
  略
            
          }
        ]
      }
    ],
    "output": {
      "text": "私の会社の名前は株式会社あいうえおほげです。"
    },

  略

  }
}
```

> "text": "私の会社の名前は株式会社あいうえおほげです。"

期待した結果が取得できていました。


## 後片付け

**AWS**は従量課金であり、仮にお金の面を除いたとしてもセキュリティ観点などから基本的にどんなサービスであれ使用後は削除なりをすべきですが、今回は特に**Aurora**や**S3**といった使われなくても課金が発生するサービスが多いです。今回の記事に限らずではありますが、不要なものはきちんと後片付けをしましょう。

## 最後に

資料を元にした回答取得までがスムーズに実現できました。

至れり尽くせりですね。

## 宣伝

[SupershipのQiita Organization](https://qiita.com/organizations/supership-inc)を合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方は[Supership株式会社 採用サイト](https://supership.jp/recruit/)よりご確認ください。