背景や目的
1.生成AIへの期待は益々高まっていますが、一般のITシステムでは企業独自の情報を回答させる事が重要な観点だと感じます。
2.別途実施した独自モデル作成実験で、追加学習では誤情報の排除は無理!と考えたその後に、RAG(検索拡張生成)の話題が増え、それを試した次第です。実際にシステムにする時の知見にしたく、AWSで作成してみました。
3.ここでは、その構築方法をまとめました。AWSで組んでみましたが、デプロイ方法の1つの例として、生成AI関連のエンジニアの方々に、ご参考になれば幸いです。また、より良い方法等のご意見も頂けると嬉しいです。
RAGアプリの構成
以下のような処理にしました。
1.LLMは、総合的に最も注目度が高いChatGPTを利用。
2.検索部はAWS KendraとOpenAI Embeddingsの2つを試す。
3.デプロイ環境はAWS Lambdaを利用(サーバレス環境を試す)。

RAGアプリ環境設定サマリー
上の処理を実現する環境設定は以下の通り。
この中で、外部データの設置も実施している。
1.Amazon S3 バケット作成とファイルアップ
2.Lambdaでレイヤーを追加 (open ai、numpyのimport用)
3.VPCの設定 (EFS、外部インターネットアクセス、S3アクセスを可能にする基礎設定)
4.EFSの設定 (sklearnのimport用)
5.Lambdaの設定 (VPCとEFS利用に関わる事)
6.LambdaでS3参照するための追加設定
7.Amazon Kendra インデックス作成、データソース作成、sync、削除
8.RAGアプリの最終構成
Amazon S3 バケット作成とファイルアップ(検索対象ファイル設置用)
検索対象ファイルをS3に設置します。
1.検索窓に”S3″を入力し「S3」を押下。Amazon S3の画面に遷移。
2.メニュー「バケット」で「バケットを作成」を押す。

3.「バケットを作成」画面では以下設定値を登録する。
| Kendra用データの設定値 | 値 |
|---|---|
| バケットタイプ | 汎用バケット |
| バケット名 | tsh-kendra1 |
| オブジェクト所有者 | ACL無効(デフォルト) |
| embeddings用データの設定値 | 値 |
|---|---|
| バケットタイプ | 汎用バケット |
| バケット名 | tsh-embeddings1 |
| オブジェクト所有者 | ACL無効(デフォルト) |

| Kendra用データの設定値 | 値 |
|---|---|
| パブリックアクセス | パブリックアクセスをすべてブロック(デフォルト) |
| バージョニング | 無効にする(デフォルト) |
| embeddings用データの設定値 | 値 |
|---|---|
| パブリックアクセス | パブリックアクセスをすべてブロック(デフォルト) |
| バージョニング | 無効にする(デフォルト) |

| Kendra用データの設定値 | 値 |
|---|---|
| タグ | (Key)Name,(Value)tsh-kendra |
| 暗号化タイプ | Amazon S3 マネージドキー使用・・ (SSE-S3)(デフォルト) |
| バケットキー | 有効にする。(デフォルト) |
| ストレージクラス | スタンダード |
| アップするファイル | 学習済QA.pdf 操作説明書.pdf |
| embeddings用データの設定値 | 値 |
|---|---|
| タグ | (Key)Name,(Value)tsh-embeddings |
| 暗号化タイプ | Amazon S3 マネージドキー使用・・ (SSE-S3)(デフォルト) |
| バケットキー | 有効にする。(デフォルト) |
| ストレージクラス | スタンダード |
| アップするファイル | embeddings_data.json |

Lambdaでレイヤーを追加 (OpenAI、numpyのimport用)
OpenAIは、テキスト生成等で、numpyはコサイン類似度の計算で、それぞれ使いました。Lambdaでimportするために、レイヤーを追加する必要があります。
1.検索窓に”Lambda”を入力し「Lambda」を押下。Lambdaの画面に遷移。
2.上部の「Cloud Shell」アイコンを押し、シェルを開き、以下コマンドでzipファイルを2つ作成。
//コマンド:
[cloudshell-user@** ~]$pip install -t ./python/ numpy
[cloudshell-user@** ~]$zip -r numpy.zip ./python
[cloudshell-user@** ~]$rm -r ./python/
[cloudshell-user@** ~]$pip install -t ./python/ --upgrade openai
[cloudshell-user@** ~]$zip -r openai.zip python
[cloudshell-user@** ~]$rm -r python
3.作成されたファイルは手元にダウンロードしておく

4.Lambda画面の「レイヤー」メニューを開き、「レイヤーの作成」を押す。

5.その作成画面内で以下の値を入力し、作成を完了させる(2つ作成)。
| 作成1:設定値(openai) | 値 |
|---|---|
| 名前 | openai |
| ファイル | .zip ファイルをアップロードにチェックし、openai.zipをアップロード |
| レイヤ作成)レイヤー互換アーキテクチャ | x86_64 |
| レイヤ作成)互換性のあるランタイム | Python3.9 |

| 作成2:設定値(numpy) | 値 |
|---|---|
| 名前 | numpy |
| ファイル | .zip ファイルをアップロードにチェックし、numpy.zipをアップロード |
| レイヤ作成)レイヤー互換アーキテクチャ | x86_64 |
| レイヤ作成)互換性のあるランタイム | Python3.9 |
6.Lambda>関数>今回作成関数 を選択し、「レイヤーの追加」を押す。

7.その画面で以下の値を入力し、作成を完了させる(2つ追加)。

| レイヤーを選択設定 | 値 |
|---|---|
| レイヤーソース | カスタムレイヤー |
| カスタムレイヤー | 上で作成したレイヤー(openai,numpy) を選択 |
VPCの設定(EFS、外部インターネットアクセス、S3アクセスを可能にする基礎設定)
コサイン類似度の計算用にsklearnを使いたく、一部EC2を利用した事もあり、LambdaはVPC上で使っています。
1.検索窓に”VPC”を入力し「VPC」を押下。VPC画面に遷移。
2.「サブネット」メニューで「サブネットを作成」を押し、以下の設定値にて、サブネットを2件作成する。

| 設定値(public-lambda) | 値 |
|---|---|
| サブネット名 | public-lambda |
| IPv4 subnet CIDR block | 172.31.48.0/20 |
| 設定値(private-lambda) | 値 |
|---|---|
| サブネット名 | private-lambda |
| IPv4 subnet CIDR block | 172.31.64.0/20 |
3.続けて、VPC画面の「インターネットゲートウェイ」メニューを開く。
4.ここでは1件作成済である事を確認する。(無い場合は作成する(説明省略))。

5.続けて、VPC画面の「NATゲートウェイ」メニューを開く。
6.開いた画面で、以下の設定値にて、NATゲートウェイを作成する。

| 設定値(NATゲートウェイ) | 値 |
|---|---|
| 名前タグ | ATD-lambda-NATGW |
| サブネット | public-lambda (先ほどpublic用に作成したもの) |
| 接続タイプ | パブリック |
| ElasticIP割当ID | 52.**.**.**(「ElasticIPを割り当て」を押し、出てきた値) |
7.続けて、VPC画面の「ルートテーブル」メニューを開き、「ルートテーブルを作成」を押す。

8.開いた画面で、以下の設定値にて、ルートテーブルを2件作成する。
| 設定値(public-route-table) | 値 |
|---|---|
| 名前 | public-route-table |
| VPC | vpc-******(既存のVPCを選択。今回は既存のVPCを使用する。) |
| ルートを編集/ルートを追加/送信先 | 0.0.0.0/0 |
| ルートを編集/ルートを追加/ターゲット | igw-******(先ほど確認した既存のインターネットゲートウェイ) |
| サブネットの関連付けを編集 | public-lambda (先ほど作成したサブネットを関連付けする) |
| 設定値(private-route-table) | 値 |
|---|---|
| 名前 | private-route-table |
| VPC | vpc-******(public-route-tableと同じ) |
| ルートを編集/ルートを追加/送信先 | 0.0.0.0/0 |
| ルートを編集/ルートを追加/ターゲット | nat-******(先ほど作成したNATゲートウェイ) |
| サブネットの関連付けを編集 | private-lambda (先ほど作成したサブネットを関連付けする) |
EFSの設定 (sklearnのimport用)
sklearnは、検索用(ベクター値比較用)のコサイン類似度の計算で使いたく、セットアップしました。設置場所で使っているEC2は、既に存在している前提で説明しております(ご容赦下さい)。
1.検索窓に”Lambda”を入力し「Lambda」を押下。Lambdaの画面に遷移。
2.上部の「Cloud Shell」アイコンを押し、シェルを開き、以下コマンドでzipファイルを1つ作成。
//コマンド:
pip3 install -t ./python/ -U scikit-learn
zip -r sklearn_aws_all.zip python
rm -r ./python/3.作成されたファイルは手元にダウンロードしておく。

4.AWSのEC2画面の「セキュリティグループ」メニューを開き、「セキュリティグループの作成」を押し、「セキュリティグループを作成」を開く。

5.その作成画面内で以下の値を入力し、作成を完了させる。
| 設定値 | 値 |
|---|---|
| セキュリティーグループ名 | security-efs |
| インバウンドルール/タイプ | NFS |
| ソース | sg-****** (今回使うEC2インスタンスのセキュリティグループ ID) sg-****** (Lambda側のセキュリティグループ ID) |
| アウトバウンドルール | (そのまま) |
| タグ | (Key)Name, (Value)security-efs |
6.AWSのEFS画面の「ファイルシステム」メニューを開き、「ファイルシステムの作成」を押す。

7.その作成画面内で以下の値を入力し、作成を完了させる。
| 設定値 | 値 |
|---|---|
| 名前 | efs-for-lambda |
| Virtual Private Cloud (VPC) | (そのまま。EC2と同じVPCのまま) |
| ネットワーク/セキュリティーグループ | EC2のサブネット(172.31.32.0/20)と Lambdaのサブネット(172.31.64.0/20)の2つを指定 セキュリティグループは、上で作成したsecurity-efsを設定 |
| アクセスポイント/名前 | accesspoint1 |
| アクセスポイント/(それ以外) | (全てデフォルト) |
8. 作成したファイルシステム画面で「アタッチ」ボタンをおす。EFSアタッチコマンドのコピーボタンを押し、コピーする。


9.AWSのEC2画面の「インスタンス」メニューを開き、今回利用するインスタンスに接続する。(※EC2インスタンスは既に作成済の前提です。)

10.そのコンソールで以下コマンドにて作成と確認をする。

//コマンド:
$sudo su ec2-user
[ec2-user@** ~]$ sudo yum install -y amazon-efs-utils
[ec2-user@** ~]$ cd /home/ec2-user/
[ec2-user@** ~]$sudo mkdir /home/ec2-user/efs
[ec2-user@** ~]$ sudo mount -t efs -o tls fs-******:/ /home/ec2-user/efs
★前の手順「アタッチ」でコピーしたコマンド
[ec2-user@** ~]$ df -T
Filesystem Type 1K-blocks Used Available Use% Mounted on
devtmpfs devtmpfs 4013952 0 4013952 0% /dev
tmpfs tmpfs 4031812 4 4031808 1% /dev/shm
tmpfs tmpfs 4031812 524 4031288 1% /run
tmpfs tmpfs 4031812 0 4031812 0% /sys/fs/cgroup
/dev/nvme0n1p1 xfs 16764908 12746084 4018824 77% /
tmpfs tmpfs 806364 0 806364 0% /run/user/0
127.0.0.1:/ nfs4 9007199254739968 0 9007199254739968 0% /home/ec2-user/efs
ec2-user@** efs]$ ls -la
total 8
drwxr-xr-x 2 root root 6144 Dec 26 11:25 .
drwx------ 14 ec2-user ec2-user 4096 Dec 26 12:13 ..
[ec2-user@** efs]$ sudo chmod go+rw .
[ec2-user@** efs]$ ls -la
total 8
drwxrwxrwx 2 root root 6144 Dec 26 11:25 .
drwx------ 14 ec2-user ec2-user 4096 Dec 26 12:13 ..11.作成済のsklearn_aws_all.zipを前の手順で作成したディレクトリへアップする。
12.引き続きコンソールで以下コマンドを実行し、sklearnをセットする。
//コマンド:
[ec2-user@** efs]$ unzip ./sklearn_aws_all.zip
[ec2-user@** efs]$ ls
python sklearn_aws_all.zip
[ec2-user@** efs]$ rm ./sklearn_aws_all.zip
[ec2-user@** efs]$ ls
python
[ec2-user@** efs]$ mv ./python/ ./sklearn/
[ec2-user@** efs]$ ls
sklearn *******Lambdaの設定 (VPCとEFS利用に関わる事)
これまでに作成したVPC上でLambdaが動作し、またEFSと接続されるように、Lambdaの設定をします。
1.検索窓に”Lambda”を入力し「Lambda」を押下。Lambdaの画面に遷移。
2.「関数」メニューで今回作成した関数を選択し、「設定>VPC」を選択後、「編集」を押下し、以下の値で設定する。

| 設定値 | 値 |
|---|---|
| VPC | vpc-****** (今回EFSで参照するEC2インスタンスが属するVPCをセット) |
| サブネット | private-lambda(172.31.64.0/20)の1つだけ設定(重要) |
| セキュリティーグループ | default VPC security group (EFS側設定と一致させる点に注意) |
3.続けて、lambda画面の「設定>アクセス権限」を押し、設定済のロール名(事前に設定済の想定です)のリンクをクリックする。

4.IAM画面が開くので、そこで「許可を追加>ポリシーをアタッチ」を押し、以下のポリシーを追加する。
| 設定値 | 値 |
|---|---|
| 追加ポリシー | AWSLambdaVPCAccessExecutionRole |

5.Lambda画面に戻り、「設定>ファイルシステム」を選択後、「ファイルシステムの追加」を押下し、以下の値で設定する。
| 設定値 | 値 |
|---|---|
| EFSファイルシステム | efs-for-lambda |
| アクセスポイント | accesspoint1 |
| ローカルマウントパス | /mnt/efs |
6.同様に、「設定>環境変数」を選択し、「編集」を押下し、 以下の値を設定する。
| 設定値 | 値 |
|---|---|
| キー | LD_LIBRARY_PATH |
| 値 | /lib64:/usr/lib64:$LAMBDA_RUNTIME_DIR:$LAMBDA_RUNTIME_DIR/lib:$LAMBDA_TASK_ROOT:$LAMBDA_TASK_ROOT/lib:/opt/lib:/mnt/efs |

LambdaでS3参照するための追加設定
VPCを使う事も考慮し、接続可能な状態にします。
1.検索窓に”Lambda”を入力し「Lambda」を押下。Lambdaの画面に遷移。
2.「関数」メニューで今回作成した関数を選択し、「設定>アクセス権限」を押し、設定済のロール名のリンクをクリックする。

3.IAM画面が開くので、そこで「許可を追加>ポリシーをアタッチ」を押し、以下のポリシーを追加する。
| 設定値 | 値 |
|---|---|
| 追加ポリシー | AmazonS3ReadOnlyAccess |

4.続けて、VPC画面を開き、「エンドポイント」メニューを開き、「エンドポイントを作成」を押す。

5.その画面では、以下の設定値を入力し、エンドポイントを1つ作成する。
| 設定値 | 値 |
|---|---|
| 名前タグ | atd-s3 |
| サービスカテゴリ | AWSサービス(デフォルト) |
| サービス | com.amazonaws.ap-northeast-1.s3 (タイプ=Gatewayの方) |
| VPC | vpc-******(今回全体で使用しているvpc) |
| ルートテーブル | private-route-table(private-lambda(172.31.64.0/20)の制御用テーブル) |

Amazon Kendraインデックス作成、データソース作成、sync、削除
検索方法AパターンのKendraを準備します。インデックス(DBみたいなもの)を作成すれば、完了です。余談ですが、課金が気になり、作成と削除を何回か繰り返しました。
1.検索窓に“kendra”を入力し「Amazon Kendra」を押下、Amazon Kendraの画面に遷移。
2.Amazon Kendraの画面に「create an index」を押す。

3.作成画面内で以下の値を入力し、作成を完了させる。
| 設定値 | 値 |
|---|---|
| Index name | aitest1 |
| IAM role | create a new role -> aitest1(2回目以降は、AmazonKendra-ap-northeast-1-aitest1を選択、で大丈夫) |
| Encryption | そのまま |
| タグ | (Key)Name, (Value)kendra-aitest1 |
| Access control settings | No(デフォルト) |
| User-group expansion | None(デフォルト) |
| Provisioning editions | Developer edition(デフォルト) |
| index作成(create押下~完了) | 30分かかったり、1分位で終わる事もある。 |
| (Add data sources押下後) Data sources | Amazon S3 connector を選択 |
| Data source name | aitest1-s3-test1 |
| Default language | Japanese |
| (Data sourceの)IAM role | create a new role -> aitest(n) (★(n)は今まで未使用の数値。2回目以降も、必ず新規作成する。しないとsyncでエラー発生(原因不明)) |
| Data-source/VPC | vpc-****** (既存のVPCを選択。) |
| Data-source/VPC/subnet | subnet-****** (172.31.64.0/20) | ap-northeast-1c, private-lambda (lambda/VPC設定値と同一) |
| Data-source/VPC/VPC security groups | sg-****** (lambda/VPCの設定値と同じ) |
| Enter the data source location | s3://tsh-kendra1 |
| Sync mode | New, modified, or deleted content sync |
| Sync run schedule | Run on demand |
| S3 field mapping | そのまま |
| datasource作成(Add data source押下~完了) | 1分位で終わる。 |
| Syncする(Sync now押下~完了) | 15分~20分位、かかっていた。 |

Lambdaの方で、KendraにアクセスできるようにIAMポリシーを追加しておきます。
1.検索窓に”Lambda”を入力し「Lambda」を押下。Lambdaの画面に遷移。
2.「関数」メニューで今回作成した関数を選択し、「設定>アクセス権限」を押し、設定済のロール名のリンクをクリックする。
3.IAM画面が開くので、そこで「許可を追加>ポリシーをアタッチ」を押し、以下のポリシーを追加する。
| 設定値 | 値 |
|---|---|
| 追加ポリシー | AmazonKendraFullAccess |
RAGアプリの最終構成
最終的な構成は以下の通り。

以下は、実行してわかった事です。
1.EFS利用は、コサイン類似度の計算をするための苦肉の策
Lambdaでは、必要なライブラリのインストール時に「レイヤー」を利用しますが、50MB制限の問題で、EFSを利用しました。初回アクセス時に、ロード(おそらくEFS経由のsklearn)で1分位かかるので、正直あまり良くないです。(動作はします。)
2.VPCを使い対応箇所が増えるも良い経験に
EFSを使う都合でLambdaをVPC内で動作させる必要が生じ、設定箇所がとても増えました。ただ、そのおかげで、色々勉強になりました。
3.Kendraは課金額に注意
インデックスが存在するだけで、1時間あたり約170円(2024/6/18時点)かかるので、終業時に毎回削除していました。うっかり放置し、想像以上の額にならないよう、お使いになる際には、ご注意ください。最新の価格はこちらをご覧ください。
画面
ちなみに画面はこんな感じです。

まとめ
1.RAGシステムをサーバレス環境で構築でき、良い勉強になりました。
2.コサイン類似度の計算をするために苦労した点は、想定外でしたが、この辺りは、色んなツールが出ているようなので、今後試してみたいと思います。
3.テキスト生成と検索部分で外部連携がありますが、そこは概ね想像通りの動作でした(精度は別として)。
4.精度としては、ベクター検索が良さそうでした。精度の詳細(確認実験)は、こちらでやっていますので、どうぞご参照ください。
