packages/blueprints/gen-ai-chatbot/static-assets/chatbot-genai-cdk/lib/constructs/vectorstore.ts (153 lines of code) (raw):

import { Construct } from "constructs"; import * as rds from "aws-cdk-lib/aws-rds"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import { CustomResource, Duration, RemovalPolicy } from "aws-cdk-lib"; import * as lambda from "aws-cdk-lib/aws-lambda"; import * as path from "path"; import * as events from "aws-cdk-lib/aws-events"; import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"; import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; import { CronSchedule } from "../utils/cron-schedule"; import { CfnSchedule } from "aws-cdk-lib/aws-scheduler"; import { Effect, PolicyStatement, Role, ServicePrincipal, } from "aws-cdk-lib/aws-iam"; import { LambdaPowertoolsLayer } from "cdk-aws-lambda-powertools-layer"; import { NagSuppressions } from "cdk-nag"; const DB_NAME = "postgres"; export interface VectorStoreProps { readonly vpc: ec2.IVpc; readonly rdsSchedule: CronSchedule; } export class VectorStore extends Construct { /** * Vector Store construct. * We use Aurora Postgres to store embedding vectors and search them. */ readonly securityGroup: ec2.ISecurityGroup; readonly cluster: rds.IDatabaseCluster; readonly secret: secretsmanager.ISecret; constructor(scope: Construct, id: string, props: VectorStoreProps) { super(scope, id); const sg = new ec2.SecurityGroup(this, "ClusterSecurityGroup", { vpc: props.vpc, description: "RDSClusterSecurityGroup", }); const cluster = new rds.DatabaseCluster(this, "Cluster", { engine: rds.DatabaseClusterEngine.auroraPostgres({ version: rds.AuroraPostgresEngineVersion.VER_15_3, }), vpc: props.vpc, securityGroups: [sg], defaultDatabaseName: DB_NAME, enableDataApi: true, serverlessV2MinCapacity: 0.5, serverlessV2MaxCapacity: 5.0, writer: rds.ClusterInstance.serverlessV2("writer", { autoMinorVersionUpgrade: true, publiclyAccessible: false, }), storageEncrypted: true, removalPolicy: RemovalPolicy.SNAPSHOT, // readers: [ // rds.ClusterInstance.serverlessV2("reader", { // autoMinorVersionUpgrade: false, // }), // ], }); cluster.addRotationSingleUser(); const dbClusterIdentifier = cluster .secret!.secretValueFromJson("dbClusterIdentifier") .unsafeUnwrap() .toString(); if (props.rdsSchedule.hasCron()) { const rdsSchedulerRole = new Role(this, "role-rds-scheduler", { assumedBy: new ServicePrincipal("scheduler.amazonaws.com"), description: "start and stop RDS", }); rdsSchedulerRole.addToPolicy( new PolicyStatement({ resources: ["*"], effect: Effect.ALLOW, actions: ["rds:startDBCluster", "rds:stopDBCluster"], }) ); new CfnSchedule(this, "StartRdsScheduler", { description: "Start RDS Instance", scheduleExpression: events.Schedule.cron(props.rdsSchedule.startCron) .expressionString, flexibleTimeWindow: { mode: "OFF" }, target: { arn: "arn:aws:scheduler:::aws-sdk:rds:startDBCluster", roleArn: rdsSchedulerRole.roleArn, input: JSON.stringify({ DbClusterIdentifier: dbClusterIdentifier, }), }, }); new CfnSchedule(this, "StopRdsScheduler", { description: "Stop RDS Instance", scheduleExpression: events.Schedule.cron(props.rdsSchedule.stopCron) .expressionString, flexibleTimeWindow: { mode: "OFF" }, target: { arn: "arn:aws:scheduler:::aws-sdk:rds:stopDBCluster", roleArn: rdsSchedulerRole.roleArn, input: JSON.stringify({ DbClusterIdentifier: dbClusterIdentifier, }), }, }); } const setupHandler = new NodejsFunction(this, "CustomResourceHandler", { vpc: props.vpc, runtime: lambda.Runtime.NODEJS_18_X, entry: path.join( __dirname, "../../custom-resources/setup-pgvector/index.js" ), handler: "handler", timeout: Duration.minutes(5), environment: { DB_CLUSTER_IDENTIFIER: dbClusterIdentifier, DB_SECRETS_ARN: cluster.secret!.secretFullArn!, }, }); cluster.secret!.grantRead(setupHandler); sg.connections.allowFrom( setupHandler, ec2.Port.tcp(cluster.clusterEndpoint.port) ); const cr = new CustomResource(this, "CustomResourceSetup", { serviceToken: setupHandler.functionArn, resourceType: "Custom::SetupVectorStore", properties: { // Dummy property to trigger id: cluster.clusterEndpoint.hostname, }, }); cr.node.addDependency(cluster); NagSuppressions.addResourceSuppressions( cr, [ { id: "AwsPrototyping-IAMNoManagedPolicies", reason: "Default Policy", appliesTo: [ { regex: "/^Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole/", }, ], }, { id: "AwsPrototyping-CodeBuildProjectKMSEncryptedArtifacts", reason: "SociIndexBuild is dependencies package", }, ], true ); this.securityGroup = sg; this.cluster = cluster; this.secret = cluster.secret!; } allowFrom(other: ec2.IConnectable) { this.securityGroup.connections.allowFrom( other, ec2.Port.tcp(this.cluster.clusterEndpoint.port) ); } }