[Knowledge bases for Amazon Bedrock] 居酒屋の案内をするナレッジベースをCDKでデプロイしてみました

2024.06.10

1 はじめに

CX事業本部製造ビジネステクノロジー部の平内(SIN)です。

Knowledge bases for Amazon Bedrockを使用すると、非常に簡単にRAGを構築することができまが、今回は、これを更に手軽にデプロイ出来るようにとCDK化してみました。

サンプルとして作成したナレッジベースは、架空の居酒屋の案内をするものです。 最初に、動作している様子をご確認ください。

2 データ

架空の居酒屋のデータとして用意したのは、以下の2つです。

どちらも、GPT-4oに作成してもらいました。

架空の居酒屋の説明文を考えてください。

架空の居酒屋は、焼き鳥とおでんがおすすめで、その他に、ご飯もの、魚料理、サイドメニューもあります
各メニューは、リーズナブルで、250円から600円の範囲となっています

説明文には、以下を含めてください。
1. 営業時間
2. お休み
3. お店の特徴(200文字程度)
4. アクセス
5. その他注意事項

架空の居酒屋のメニューを100種類考えてください
メニューは、一覧形式で出力してください
各メニューには、メニュー番号、名前、価格、説明、分類を含めてください
分類は、「ご飯もの」「焼き鳥」「おでん」「魚料理」「サイドメニュー」の5種類です
各メニューの説明は、それぞれ独創的で、面白みのあるものにしてください
価格は、250円から600円の範囲としてください

3 Pinecone

Pineconeのインデックスデータベースは、手動で作成しています。 設定値は、以下の通りで、作成後にHOST(エンドポイント)をCDKデプロイ時に使用できるようコピーしておきます。

  • 名前: kb-izakaya
  • Dimensions: 1,536
  • Metric: Cosine
  • Capacity mode: SERVERRESS
  • Region: us-east-1

なお、AWSから接続するためには、PineconeのAPIキーをSecretManagerに保存する必要があります。

シークレットのARNもCDKデプロイ時に必要となります。

4 CDK

(1) コード

CDKの主なコードは、以下のとおりです。

データソース用のS3バケット、必要なRole そして、Pineconeをストレージとするナレッジベースとデータソースが定義されています。

lib/amazon-bedrock-kb-izakaya-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import { CfnOutput, RemovalPolicy } from "aws-cdk-lib";
import { aws_iam, aws_s3, aws_bedrock } from "aws-cdk-lib";

export class AmazonBedrockKbIzakayaStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const tag = "kb-izakaya";
    const bucketName = `${tag}-${this.account}`;
    const embeddingModelArn = this.node.tryGetContext("embeddingModelArn");
    const pineconeEndpoint = this.node.tryGetContext("pineconeEndpoint");
    const pineconeSecretArn = this.node.tryGetContext("pineconeSecretArn");

    // S3 bucket for the data source
    const dataSourceBucket = new aws_s3.Bucket(this, "DataSourceBucket", {
      bucketName: bucketName,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Role for the knowledge base
    const knowledgeBaseRole = new aws_iam.Role(this, `KnowledgeBaseRole`, {
      roleName: `${tag}_role`,
      assumedBy: new aws_iam.ServicePrincipal("bedrock.amazonaws.com"),
      inlinePolicies: {
        inlinePolicy1: new aws_iam.PolicyDocument({
          statements: [
            new aws_iam.PolicyStatement({
              resources: [pineconeSecretArn],
              actions: ["secretsmanager:GetSecretValue"],
            }),
            new aws_iam.PolicyStatement({
              resources: [embeddingModelArn],
              actions: ["bedrock:InvokeModel"],
            }),
            new aws_iam.PolicyStatement({
              resources: [
                `arn:aws:s3:::${bucketName}`,
                `arn:aws:s3:::${bucketName}/*`,
              ],
              actions: ["s3:ListBucket", "s3:GetObject"],
            }),
          ],
        }),
      },
    });

    // knowledge Base
    const knowledgeBase = new aws_bedrock.CfnKnowledgeBase(
      this,
      "KnowledgeBase",
      {
        knowledgeBaseConfiguration: {
          type: "VECTOR",
          vectorKnowledgeBaseConfiguration: {
            embeddingModelArn: embeddingModelArn,
          },
        },
        name: tag,
        roleArn: knowledgeBaseRole.roleArn,
        storageConfiguration: {
          type: "PINECONE",
          pineconeConfiguration: {
            connectionString: pineconeEndpoint,
            credentialsSecretArn: pineconeSecretArn,
            fieldMapping: {
              metadataField: "metadata",
              textField: "text",
            },
          },
        },
        description: "IZAKAYA knowledge base",
      }
    );

    // data source
    new aws_bedrock.CfnDataSource(this, "BedrockKnowledgeBaseDataStore", {
      name: `${tag}-data-source`,
      knowledgeBaseId: knowledgeBase.ref,
      dataSourceConfiguration: {
        s3Configuration: {
          bucketArn: dataSourceBucket.bucketArn,
        },
        type: "S3",
      },
    });

    // Output the AWS CLI command to upload a file to the S3 bucket
    const dataSourceFiles: string[] = [
      "izakaya_menu.txt",
      "izakaya_guidance.pdf",
    ];
    dataSourceFiles.forEach((dataSourceFile) => {
      const uploadCommand = `aws s3 cp assets/${dataSourceFile} s3://${bucketName}/${dataSourceFile}`;
      new CfnOutput(this, `UploadCommand_${dataSourceFile}`, {
        value: uploadCommand,
        description: `AWS CLI command to upload a file to the S3 bucket`,
      });
    });
  }
}

(2) デプロイ

CDKのコードは、Githubにあり、以下の手順でデプロイできます。

https://github.com/furuya02/amazon-bedrock-kb-izakaya

% git clone https://github.com/furuya02/amazon-bedrock-kb-izakaya.git
% cd amazon-bedrock-kb-izakaya
% npm install

cdk.jsonにPineconeに関する設定がありますので、エンドポイント及びシークレットのARNを編集します。

"context": {
    "pineconeEndpoint": "https://xxxxxxx.pinecone.io",
    "pineconeSecretArn": "arn:aws:secretsmanager:us-east-1:xxxxxxxx",

CDKデプロイは、以下のとおりです。

% export AWS_DEFAULT_REGION=us-east-1
% npx cdk deploy

Githubリポジトリの、assetsフォルダに今回使用したデータがありますので、作成されたバケットにアップロード してください。

% aws s3 cp assets/izakaya_guidance.pdf s3://kb-izakaya-xxxxxxxx/izakaya_guidance.pdf
% aws s3 cp assets/izakaya_menu.txt s3://kb-izakaya-xxxxxxxx/izakaya_menu.txt

5 同期

CDKデプロイ及び、データのアップロード後、「同期」することで、データは、ベクトル化され保存されます。

Pinecodeのコンソールでは、ベクトル化されたデータを確認することもできます。

6 動作確認

テストには、Anthropic Claude v2.1を使用しました。

アップロードしたデータを元に、質問に答えていることが確認できます

7 最後に

今回は、PineconeをストレージとしたKnowledge bases for Amazon BedrockをCDKで作成してみました。

作業中少し戸惑ったのは、以下の2点でした。

(1) Embedding ModelのARN

コンソール上には、ARNが出てこないので、CLIで列挙して確認しました。

% export AWS_DEFAULT_REGION=us-east-1
% % aws bedrock list-foundation-models | grep modelArn | grep titan-embed-text
  "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v1:2:8k",
  "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v1",
  "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0:8k",
  "modelArn": "arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0",

(2) 依存関係

最後までよく分からなかったのですが、ロールを先に作成するように、明示的に指定しないと、デプロイに失敗してました。

knowledgeBase.node.addDependency(knowledgeBaseRole);
2024/06/11 追記

「(2) 依存関係」 の問題は、Roleの作成時に addPolidy() を使用していたことが原因でした。 aws_iam.Role.inlinePolicies に修正することでaddDependency()は、必要なくなりました。

https://github.com/furuya02/amazon-bedrock-kb-izakaya/pull/1/commits/f43ed82604a1d6552fedd078ed470e839f7104ae

addPolidy()は、CDK上別のオブジェクトとなるため、タイミングによって、CfnKnowledgeBaseの生成時にポリシー不足が指摘されていたようです。

CfnKnowledgeBaseのプロパティ roleArnは、生成時に検証されているようなので注意が必要のようです。

やまたつさんに教えて頂きました。m(_._)m

元々、Knowledge bases for Amazon Bedrockの構築は、非常に簡単ですが、 ランニングコストが比較的低いPineconeを使用したものをCDK化しておくことで、更に手軽に利用できるのでは?と妄想しています。

8 参考にさせて頂いたリンク


Set up a vector index for your knowledge base in a supported vector store
AWS Marketplace の Pinecone を Amazon Bedrock のナレッジベースとして利用する