RAGでよく使うKendraとS3をCloudFormationで実装してみた
こんにちは、つくぼし(tsukuboshi0755)です!
最近流行りのRetrieval-Augmented Generation(以下RAG)を組む際に、文書を保存するデータソースとしてKendraとS3を組み合わせるパターンは多いと思います。
今回は、RAGでよく使うKendraとS3をCloudFormationで実装し、利用する方法を紹介します!
構成
今回の構成図は以下になります。
また以下の設定はRAGには必須ではありませんが、実際にRAGを運用する際に役に立つので合わせて設定します。
- CloudWatch Logsの使用料金を減らすため、Kendraログに保持期間を指定
- S3バケットに保存された文書データが不正にダウンロードされる事を防ぐため、バケットポリシーでKendraデータソースロール以外からのオブジェクト取得を禁止
テンプレート
以下のリポジトリのテンプレートをベースにしています。
amazon-kendra-langchain-extensions/kendra_retriever_samples/kendra-docs-index.yaml
リポジトリのテンプレートはデータソースとしてWebクローラーを使用していますが、今回はS3を使用する形に変更した上で作成しています。
全体のコードは以下の通りです。
CloudFormationコード
AWSTemplateFormatVersion: '2010-09-09' Description: Kendra and S3 Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Parameters: - SysName - Env - KendraEdition - KendraLogRetentionDays - KendraDSBucketPrefix Parameters: SysName: Type: String Default: 'cm' Description: 'System name for this stack.' Env: Type: String Default: 'prd' Description: 'Environment for this stack.' AllowedValues: - 'prd' - 'stg' - 'dev' KendraEdition: Type: String Default: 'ENTERPRISE_EDITION' Description: 'ENTERPRISE_EDITION is suitable for production environments. DEVELOPER_EDITION is suitable for development environments.' AllowedValues: - 'ENTERPRISE_EDITION' - 'DEVELOPER_EDITION' KendraLogRetentionDays: Type: Number Default: 365 Description: 'Retention days for Kendra logs.' AllowedValues: - 1 - 3 - 5 - 7 - 14 - 30 - 60 - 90 - 120 - 150 - 180 - 365 - 400 - 545 - 731 - 1096 - 1827 - 2192 - 2557 - 2922 - 3288 - 3653 KendraDSBucketPrefix: Type: String Default: 'awsdoc' Description: 'Bucket prefix for search range.' Resources: ##Role for Kendra Index KendraIndexRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${SysName}-${Env}-kendra-index-role' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: kendra.amazonaws.com Action: 'sts:AssumeRole' ##Policy for Kendra Index KendraIndexPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-index-policy' Roles: - !Ref KendraIndexRole PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: '*' Condition: StringEquals: 'cloudwatch:namespace': 'AWS/Kendra' Action: - 'cloudwatch:PutMetricData' - Effect: Allow Resource: '*' Action: 'logs:DescribeLogGroups' - Effect: Allow Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*' Action: 'logs:CreateLogGroup' - Effect: Allow Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*:log-stream:*' Action: - 'logs:DescribeLogStreams' - 'logs:CreateLogStream' - 'logs:PutLogEvents' ##Kendra Index KendraIndex: Type: 'AWS::Kendra::Index' Properties: Name: !Sub '${SysName}-${Env}-kendra-index' Edition: !Ref KendraEdition RoleArn: !GetAtt KendraIndexRole.Arn ##CloudWatch LogGroup for Kendra Index KendraIndexLogs: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Sub - '/aws/kendra/${IndexId}' - IndexId: !GetAtt KendraIndex.Id RetentionInDays: !Ref KendraLogRetentionDays ##Role for Kendra Data Source KendraDSRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${SysName}-${Env}-kendra-ds-role' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: kendra.amazonaws.com Action: 'sts:AssumeRole' ##Policy for Kendra Data Source KendraDSPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-ds-policy' Roles: - !Ref KendraDSRole PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: - !Join - '' - - 'arn:aws:s3:::' - !Ref KendraDSBucket - '/*' Action: - 's3:GetObject' - Effect: Allow Resource: !GetAtt KendraDSBucket.Arn Action: - 's3:ListBucket' - Effect: Allow Resource: !Sub - 'arn:aws:kendra:${AWS::Region}:${AWS::AccountId}:index/${IndexId}' - IndexId: !GetAtt KendraIndex.Id Action: - 'kendra:BatchPutDocument' - 'kendra:BatchDeleteDocument' ##Kendra Data Source KendraDS: Type: 'AWS::Kendra::DataSource' Properties: DataSourceConfiguration: S3Configuration: BucketName: !Ref KendraDSBucket InclusionPrefixes: - !Ref KendraDSBucketPrefix IndexId: !GetAtt KendraIndex.Id LanguageCode: 'ja' Name: !Sub '${SysName}-${Env}-kendra-ds' RoleArn: !GetAtt KendraDSRole.Arn Type: 'S3' ##Data Source Bucket KendraDSBucket: Type: 'AWS::S3::Bucket' Properties: BucketName: !Sub '${SysName}-${Env}-kendra-ds-bucket-${AWS::AccountId}' PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 BucketKeyEnabled: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced ##Data Source Bucket Policy KendraDSBucketPolicy: Type: 'AWS::S3::BucketPolicy' Properties: Bucket: !Ref KendraDSBucket PolicyDocument: Version: 2012-10-17 Statement: - Effect: Deny Action: - 's3:GetObject' Resource: - !Join - '' - - 'arn:aws:s3:::' - !Ref KendraDSBucket - '/*' Principal: '*' Condition: StringNotEquals: aws:PrincipalArn: - !GetAtt KendraDSRole.Arn Outputs: KendraIndexID: Value: !GetAtt KendraIndex.Id KendraDSID: Value: !GetAtt KendraDS.Id KendraDSBucketName: Value: !Ref KendraDSBucket
以下より、本テンプレートで作成される各リソースについて説明します。
Kendraインデックスロール
KendraIndexRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${SysName}-${Env}-kendra-index-role' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: kendra.amazonaws.com Action: 'sts:AssumeRole' KendraIndexPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-index-policy' Roles: - !Ref KendraIndexRole PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: '*' Condition: StringEquals: 'cloudwatch:namespace': 'AWS/Kendra' Action: - 'cloudwatch:PutMetricData' - Effect: Allow Resource: '*' Action: 'logs:DescribeLogGroups' - Effect: Allow Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*' Action: 'logs:CreateLogGroup' - Effect: Allow Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/kendra/*:log-stream:*' Action: - 'logs:DescribeLogStreams' - 'logs:CreateLogStream' - 'logs:PutLogEvents'
Kendraインデックス用のIAMロールを作成します。
ここでは以下の公式ドキュメントに基づき、Kendraに関するCloudWatchログ及びメトリクスの出力権限をアタッチします。
Kendraインデックス
KendraIndex: Type: 'AWS::Kendra::Index' Properties: Name: !Sub '${SysName}-${Env}-kendra-index' Edition: !Ref KendraEdition RoleArn: !GetAtt KendraIndexRole.Arn
文書データを検索するKendraインデックスを作成します。
Edition
では、KendraのエディションをENTERPRISE_EDITION(本番用)またはDEVELOPER_EDITION(検証用)から選択します。
RoleArn
では、先ほど作成したKendraインデックスロールARNを指定します。
Kendraログ(任意)
KendraIndexLogs: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Sub - '/aws/kendra/${IndexId}' - IndexId: !GetAtt KendraIndex.Id RetentionInDays: !Ref KendraLogRetentionDays
Kendra用のCloudWatch Logsロググループを作成します。
必要に応じて、RetentionInDays
でKendraログの保持期間を指定します。
Kendraデータソースロール
KendraDSRole: Type: 'AWS::IAM::Role' Properties: RoleName: !Sub '${SysName}-${Env}-kendra-ds-role' AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Sid: '' Effect: Allow Principal: Service: kendra.amazonaws.com Action: 'sts:AssumeRole' KendraDSPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: !Sub '${SysName}-${Env}-kendra-ds-policy' Roles: - !Ref KendraDSRole PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: - !Join - '' - - 'arn:aws:s3:::' - !Ref KendraDSBucket - '/*' Action: - 's3:GetObject' - Effect: Allow Resource: !GetAtt KendraDSBucket.Arn Action: - 's3:ListBucket' - Effect: Allow Resource: !Sub - 'arn:aws:kendra:${AWS::Region}:${AWS::AccountId}:index/${IndexId}' - IndexId: !GetAtt KendraIndex.Id Action: - 'kendra:BatchPutDocument' - 'kendra:BatchDeleteDocument'
Kendraデータソース用のIAMロールを作成します。
ここではS3上の文書データへのアクセス権限、及びKendraインデックスへの文書データの登録・削除権限をアタッチします。
Kendraデータソース
KendraDS: Type: 'AWS::Kendra::DataSource' Properties: DataSourceConfiguration: S3Configuration: BucketName: !Ref KendraDSBucket InclusionPrefixes: - !Ref KendraDSBucketPrefix IndexId: !GetAtt KendraIndex.Id LanguageCode: 'ja' Name: !Sub '${SysName}-${Env}-kendra-ds' RoleArn: !GetAtt KendraDSRole.Arn Type: 'S3'
S3バケットの特定プレフィックス配下の文書データを対象とするKendraデータソースを作成します。
S3Configuration
では、文書データが保存されているバケット名とプレフィックスを指定します。
IndexId
では、先ほど作成したKendraインデックスのIDを指定します。
LanguageCode
では、文書データの言語コードを日本語(ja)で指定します。
RoleArn
では、先ほど作成したKendraデータソースロールARNを指定します。
Type
では、文書データの保存先がS3バケットである事を指定します。
(2024/6/28追記)
S3バケットに接続するKendraデータソース設定には、旧型のS3DataSourceConfiguration
と、新型のTemplateConfiguration
の2種類が存在します。
2024/6現在、CloudFormationではS3DataSourceConfiguration
しか対応していません。
TemplateConfiguration
を使用したい場合、現状KendraデータソースをコンソールまたはAPIから設定する必要があるためご注意下さい。
Amazon Kendra now supports an upgraded Amazon S3 connector. You must now use the TemplateConfiguration object instead of the S3DataSourceConfiguration object to configure your connector. Connectors configured using the older console and API architecture will continue to function as configured. However, you won't be able to edit or update them. If you want to edit or update your connector configuration, you must create a new connector. We recommended migrating your connector workflow to the upgraded version. Support for connectors configured using the older architecture is scheduled to end by June 2024.
S3バケット
KendraDSBucket: Type: 'AWS::S3::Bucket' Properties: BucketName: !Sub '${SysName}-${Env}-kendra-ds-bucket-${AWS::AccountId}' PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 BucketKeyEnabled: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced
文書データを保存するS3バケットを作成します。
今回は基本的なパブリックアクセス/暗号化/ACL無効化を実施しています。
S3バケットポリシー(任意)
KendraDSBucketPolicy: Type: 'AWS::S3::BucketPolicy' Properties: Bucket: !Ref KendraDSBucket PolicyDocument: Version: 2012-10-17 Statement: - Effect: Deny Action: - 's3:GetObject' Resource: - !Join - '' - - 'arn:aws:s3:::' - !Ref KendraDSBucket - '/*' Principal: '*' Condition: StringNotEquals: aws:PrincipalArn: - !GetAtt KendraDSRole.Arn
Kendraデータソースロール以外からのオブジェクト取得を禁止するバケットポリシーを作成します。
このバケットポリシーを設定するで、S3バケットにアップロードした文書データに機密情報が含まれる場合でも、Kendraデータソース以外からの不正アクセスを防ぐ事ができます。
なおもしオブジェクトにアクセス可能なIAMロールを追加したい場合は、以下のような形で該当リソースのARNを追加してください。
Condition: StringNotEquals: aws:PrincipalArn: - !GetAtt KendraDSRole.Arn - !Sub "arn:aws:iam::${AWS::AccountId}:role/<IAMロール名>"
- 参考
検索の確認
上記のテンプレートをデプロイする事で、Kendraを用いて正常に文書データを検索できるか確認します。
なお今回の確認で使用する文書データのアップロード及び確認手順としては、以下の記事を参照します。
CloudFormationスタックのデプロイ
今回は以下のパラメータで、CloudFormationスタックをデプロイします。
なおKendra関連リソースのデプロイには、およそ20-30分程度かかります。
S3バケットへのデータ投入
次にCloudShell上で以下のコマンドを実施し、PDFファイルを作成したS3バケットにアップロードします。
# S3 バケット名を設定 BUCKET_NAME=<バケット名> # S3 プレフィックスを設定 BUCKET_PREFIX=awsdoc # AWS の公式ドキュメントの PDF ファイルをダウンロード mkdir ${BUCKET_PREFIX} cd ${BUCKET_PREFIX} wget https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/dynamodb-dg.pdf -O DynamoDB.pdf wget https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-dg.pdf -O Lambda.pdf wget https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-ug.pdf -O VPC.pdf wget https://docs.aws.amazon.com/ja_jp/kendra/latest/dg/kendra-dg.pdf -O Kendra.pdf wget https://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/route53-dg.pdf -O Route53.pdf cd .. # S3 バケットに PDF ファイルをアップロード aws s3 cp ${BUCKET_PREFIX} s3://${BUCKET_NAME}/${BUCKET_PREFIX}/ --recursive
ファイルのアップロードが成功すると、以下の通り作成したS3バケットのawsdoc
プレフィックス配下に5つのPDFファイルが作成されます。
Kendraデータソースの同期
続いてKendraのコンソールに移動し、左ペインのIndexes
に切り替え、作成したインデックスをクリックします。
Kendraのインデックス設定は以下のようになっています。
左ペインのData Sources
に切り替え、作成したデータソースをクリックします。
Sync now
ボタンを押すと、S3にアップロードしたPDFファイルがKendraに同期されます。
データの同期が成功すると、Sync History
タブに以下のようなCompleted履歴が表示されます。
Kendraでの検索実行
Kendraで検索が可能か確認するために、左ペインのSearch indexed content
に切り替えます。
右端にある設定ボタンをクリックします。
Default language of source documents
でJapanese(ja)を選択し、save
ボタンを押します。
これで検索設定は完了です。
試しに「IP アドレス」と検索すると、「IP」と「アドレス」の両方のキーワードが含まれているドキュメントがヒットしました!
なおもしバケットポリシーでKendraデータソースロール以外のオブジェクト取得を禁止している場合、ドキュメントに表示されているURLをクリックしてもアクセスできない事にご注意ください。
最後に
今回は、RAGでよく使うKendraとS3をCloudFormationで実装し、利用する方法を紹介しました!
今後RAGをAWSで組む機会は増えていくと考えられるので、その際にぜひご活用頂けるとありがたいです。
以上、つくぼし(tsukuboshi0755)でした!