From 012511f5a3c1b8b0a302720cce3cd4723f232398 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Sun, 3 May 2026 11:10:51 +0000 Subject: [PATCH] feat(aurora-serverless-v2-lambda-bedrock-cdk): Add Aurora Serverless v2 RAG with Bedrock Deploy Aurora Serverless v2 PostgreSQL (platform version 4, 30% perf boost) with Lambda functions that query stored knowledge via Data API and use Amazon Bedrock for AI-powered answers. Scales to zero. --- .../README.md | 81 +++++++++++++++++ .../bin/app.ts | 16 ++++ .../cdk.context.json | 60 +++++++++++++ .../cdk.json | 3 + .../example-pattern.json | 50 +++++++++++ ...rora-serverless-v2-lambda-bedrock-stack.ts | 90 +++++++++++++++++++ .../package.json | 15 ++++ .../src/query-fn/index.py | 53 +++++++++++ .../src/setup/index.py | 53 +++++++++++ .../tsconfig.json | 20 +++++ 10 files changed, 441 insertions(+) create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/README.md create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/bin/app.ts create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/cdk.context.json create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/cdk.json create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/example-pattern.json create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/lib/aurora-serverless-v2-lambda-bedrock-stack.ts create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/package.json create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/src/query-fn/index.py create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/src/setup/index.py create mode 100644 aurora-serverless-v2-lambda-bedrock-cdk/tsconfig.json diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/README.md b/aurora-serverless-v2-lambda-bedrock-cdk/README.md new file mode 100644 index 000000000..5a373400a --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/README.md @@ -0,0 +1,81 @@ +# Aurora Serverless v2 with Lambda and Amazon Bedrock + +This pattern deploys an Aurora Serverless v2 PostgreSQL cluster (platform version 4) with Lambda functions that query stored knowledge and use Amazon Bedrock for AI-powered answers. Aurora Serverless v2 scales to zero when idle, making it cost-effective for agentic AI workloads. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/aurora-serverless-v2-lambda-bedrock-cdk + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node and NPM](https://nodejs.org/en/download/) installed +* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed +* [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your target region + +## How it works + +![Architecture](architecture.png) + +1. A setup Lambda creates the `knowledge` table in Aurora PostgreSQL and seeds it with sample data. +2. A query Lambda receives a question, searches Aurora for relevant context using SQL, and sends the context + question to Amazon Bedrock. +3. Bedrock (Claude Sonnet) generates an answer grounded in the database context. +4. Aurora Serverless v2 (platform version 4) automatically scales capacity based on demand and scales to zero when idle. + +## Deployment + +1. Clone the repository and navigate to the pattern directory: + ```bash + git clone https://github.com/aws-samples/serverless-patterns + cd serverless-patterns/aurora-serverless-v2-lambda-bedrock-cdk + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Deploy the stack: + ```bash + cdk deploy + ``` + +4. Seed the database: + ```bash + aws lambda invoke \ + --function-name $(aws cloudformation describe-stacks \ + --stack-name AuroraServerlessV2LambdaBedrockStack \ + --query 'Stacks[0].Outputs[?OutputKey==`SetupFunctionName`].OutputValue' \ + --output text) \ + --payload '{}' setup-output.json + ``` + +## Testing + +Query the knowledge base: + +```bash +aws lambda invoke \ + --function-name $(aws cloudformation describe-stacks \ + --stack-name AuroraServerlessV2LambdaBedrockStack \ + --query 'Stacks[0].Outputs[?OutputKey==`QueryFunctionName`].OutputValue' \ + --output text) \ + --cli-binary-format raw-in-base64-out \ + --payload '{"question": "What is Aurora Serverless v2?"}' \ + output.json + +cat output.json | python3 -m json.tool +``` + +## Cleanup + +```bash +cdk destroy +``` + +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/bin/app.ts b/aurora-serverless-v2-lambda-bedrock-cdk/bin/app.ts new file mode 100644 index 000000000..555497d00 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/bin/app.ts @@ -0,0 +1,16 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { AuroraServerlessV2LambdaBedrockStack } from "../lib/aurora-serverless-v2-lambda-bedrock-stack"; + +const app = new cdk.App(); +new AuroraServerlessV2LambdaBedrockStack( + app, + "AuroraServerlessV2LambdaBedrockStack", + { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + } +); diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/cdk.context.json b/aurora-serverless-v2-lambda-bedrock-cdk/cdk.context.json new file mode 100644 index 000000000..1a91c7438 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/cdk.context.json @@ -0,0 +1,60 @@ +{ + "availability-zones:account=742460038667:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c", + "us-east-1d", + "us-east-1e", + "us-east-1f" + ], + "vpc-provider:account=742460038667:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": { + "vpcId": "vpc-0d2ccb9ba9da8174c", + "vpcCidrBlock": "172.31.0.0/16", + "ownerAccountId": "742460038667", + "availabilityZones": [], + "subnetGroups": [ + { + "name": "Public", + "type": "Public", + "subnets": [ + { + "subnetId": "subnet-09f81666a668b49d7", + "cidr": "172.31.16.0/20", + "availabilityZone": "us-east-1a", + "routeTableId": "rtb-0d6e0c8254189f150" + }, + { + "subnetId": "subnet-01e469e26a62f79cc", + "cidr": "172.31.32.0/20", + "availabilityZone": "us-east-1b", + "routeTableId": "rtb-0d6e0c8254189f150" + }, + { + "subnetId": "subnet-0edf680549fc43b35", + "cidr": "172.31.0.0/20", + "availabilityZone": "us-east-1c", + "routeTableId": "rtb-0d6e0c8254189f150" + }, + { + "subnetId": "subnet-0a4b73e1b77dfe7e3", + "cidr": "172.31.80.0/20", + "availabilityZone": "us-east-1d", + "routeTableId": "rtb-0d6e0c8254189f150" + }, + { + "subnetId": "subnet-007839dd58f1d60a0", + "cidr": "172.31.48.0/20", + "availabilityZone": "us-east-1e", + "routeTableId": "rtb-0d6e0c8254189f150" + }, + { + "subnetId": "subnet-03604e5a5796bce0a", + "cidr": "172.31.64.0/20", + "availabilityZone": "us-east-1f", + "routeTableId": "rtb-0d6e0c8254189f150" + } + ] + } + ] + } +} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/cdk.json b/aurora-serverless-v2-lambda-bedrock-cdk/cdk.json new file mode 100644 index 000000000..a6700a2ff --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/example-pattern.json b/aurora-serverless-v2-lambda-bedrock-cdk/example-pattern.json new file mode 100644 index 000000000..4454c57d1 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/example-pattern.json @@ -0,0 +1,50 @@ +{ + "title": "Aurora Serverless v2 with Lambda and Amazon Bedrock", + "description": "Deploy an Aurora Serverless v2 PostgreSQL database that scales to zero, with Lambda functions that query stored knowledge and use Amazon Bedrock for AI-powered answers.", + "language": "Python", + "level": "300", + "framework": "AWS CDK", + "services": { + "from": "auroraserverlessv2", + "to": "bedrock" + }, + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys an Aurora Serverless v2 PostgreSQL cluster (platform version 4 with up to 30% better performance) that scales to zero when idle. A setup Lambda seeds a knowledge table, and a query Lambda retrieves relevant context from Aurora and sends it to Amazon Bedrock for AI-powered answers.", + "Aurora Serverless v2 is ideal for agentic AI workloads with burst patterns and long idle windows. The enhanced scaling algorithm in platform version 4 efficiently handles workloads where multiple tasks compete for resources." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/aurora-serverless-v2-lambda-bedrock-cdk", + "templateURL": "serverless-patterns/aurora-serverless-v2-lambda-bedrock-cdk", + "projectFolder": "aurora-serverless-v2-lambda-bedrock-cdk", + "templateFile": "lib/aurora-serverless-v2-lambda-bedrock-stack.ts" + } + }, + "resources": { + "bullets": [ + { "text": "Aurora Serverless v2 platform version 4", "link": "https://aws.amazon.com/about-aws/whats-new/2026/04/aurora-serverless-smarter-scaling/" }, + { "text": "Amazon Aurora Serverless", "link": "https://aws.amazon.com/rds/aurora/serverless/" }, + { "text": "Amazon Bedrock", "link": "https://aws.amazon.com/bedrock/" } + ] + }, + "deploy": { + "text": ["cdk deploy"], + "file": "lib/aurora-serverless-v2-lambda-bedrock-stack.ts" + }, + "testing": { + "text": ["See the README for testing instructions."] + }, + "cleanup": { + "text": ["cdk destroy"] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ] +} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/lib/aurora-serverless-v2-lambda-bedrock-stack.ts b/aurora-serverless-v2-lambda-bedrock-cdk/lib/aurora-serverless-v2-lambda-bedrock-stack.ts new file mode 100644 index 000000000..3d502e948 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/lib/aurora-serverless-v2-lambda-bedrock-stack.ts @@ -0,0 +1,90 @@ +import * as cdk from "aws-cdk-lib"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as rds from "aws-cdk-lib/aws-rds"; +import { Construct } from "constructs"; + +export class AuroraServerlessV2LambdaBedrockStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Use default VPC + const vpc = ec2.Vpc.fromLookup(this, "DefaultVpc", { isDefault: true }); + + // Aurora Serverless v2 cluster (PostgreSQL, scales to zero) + const cluster = new rds.DatabaseCluster(this, "AuroraCluster", { + engine: rds.DatabaseClusterEngine.auroraPostgres({ + version: rds.AuroraPostgresEngineVersion.VER_16_4, + }), + serverlessV2MinCapacity: 0, + serverlessV2MaxCapacity: 4, + writer: rds.ClusterInstance.serverlessV2("writer"), + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + defaultDatabaseName: "appdb", + enableDataApi: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Data API policy for Lambdas + const dataApiPolicy = new iam.PolicyStatement({ + actions: [ + "rds-data:ExecuteStatement", + "rds-data:BatchExecuteStatement", + ], + resources: [cluster.clusterArn], + }); + + const envVars = { + CLUSTER_ARN: cluster.clusterArn, + SECRET_ARN: cluster.secret!.secretArn, + DB_NAME: "appdb", + }; + + // Setup Lambda — initializes the knowledge table + const setupFn = new lambda.Function(this, "SetupFn", { + runtime: lambda.Runtime.PYTHON_3_12, + handler: "index.handler", + code: lambda.Code.fromAsset("src/setup"), + timeout: cdk.Duration.seconds(60), + memorySize: 256, + environment: envVars, + description: "Seeds Aurora knowledge table via Data API", + }); + cluster.secret!.grantRead(setupFn); + setupFn.addToRolePolicy(dataApiPolicy); + + // Query Lambda — queries Aurora, sends context to Bedrock + const queryFn = new lambda.Function(this, "QueryFn", { + runtime: lambda.Runtime.PYTHON_3_12, + handler: "index.handler", + code: lambda.Code.fromAsset("src/query-fn"), + timeout: cdk.Duration.minutes(2), + memorySize: 512, + environment: { + ...envVars, + MODEL_ID: "us.anthropic.claude-sonnet-4-20250514-v1:0", + }, + description: "Queries Aurora knowledge base and sends to Bedrock", + }); + cluster.secret!.grantRead(queryFn); + queryFn.addToRolePolicy(dataApiPolicy); + queryFn.addToRolePolicy( + new iam.PolicyStatement({ + actions: ["bedrock:InvokeModel"], + resources: ["*"], + }) + ); + + new cdk.CfnOutput(this, "SetupFunctionName", { + value: setupFn.functionName, + }); + new cdk.CfnOutput(this, "QueryFunctionName", { + value: queryFn.functionName, + }); + new cdk.CfnOutput(this, "ClusterEndpoint", { + value: cluster.clusterEndpoint.hostname, + }); + } +} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/package.json b/aurora-serverless-v2-lambda-bedrock-cdk/package.json new file mode 100644 index 000000000..64e4f0c25 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "aurora-serverless-v2-lambda-bedrock-cdk", + "version": "1.0.0", + "bin": { "app": "bin/app.ts" }, + "scripts": { "build": "tsc", "cdk": "cdk" }, + "dependencies": { + "aws-cdk-lib": "^2.180.0", + "constructs": "^10.4.2" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "ts-node": "^10.9.0", + "typescript": "~5.7.0" + } +} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/src/query-fn/index.py b/aurora-serverless-v2-lambda-bedrock-cdk/src/query-fn/index.py new file mode 100644 index 000000000..ed781cf35 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/src/query-fn/index.py @@ -0,0 +1,53 @@ +"""Query handler — searches Aurora via Data API, sends context to Bedrock for answer.""" + +import json +import os +import boto3 + +rds_data = boto3.client("rds-data") +bedrock = boto3.client("bedrock-runtime") + +CLUSTER_ARN = os.environ["CLUSTER_ARN"] +SECRET_ARN = os.environ["SECRET_ARN"] +DB_NAME = os.environ["DB_NAME"] + + +def handler(event, _context): + question = event.get("question", "What is Aurora Serverless v2?") + stop_words = {"what", "is", "the", "a", "an", "how", "does", "do", "can", "tell", "me", "about"} + keywords = [w for w in question.split() if w.lower().strip("?.,!") not in stop_words] + search_term = keywords[0] if keywords else "Aurora" + + # Query Aurora for relevant knowledge via Data API + result = rds_data.execute_statement( + resourceArn=CLUSTER_ARN, + secretArn=SECRET_ARN, + database=DB_NAME, + sql="SELECT topic, content FROM knowledge WHERE content ILIKE :term OR topic ILIKE :term LIMIT 3", + parameters=[{"name": "term", "value": {"stringValue": f"%{search_term}%"}}], + ) + + rows = result.get("records", []) + context = "\n".join( + f"[{r[0]['stringValue']}]: {r[1]['stringValue']}" for r in rows + ) if rows else "No context found." + + # Send context + question to Bedrock + response = bedrock.invoke_model( + modelId=os.environ["MODEL_ID"], + contentType="application/json", + accept="application/json", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 512, + "messages": [ + { + "role": "user", + "content": f"Context from database:\n{context}\n\nQuestion: {question}\n\nAnswer based on the context above.", + } + ], + }), + ) + answer = json.loads(response["body"].read())["content"][0]["text"] + + return {"statusCode": 200, "body": json.dumps({"question": question, "answer": answer, "sources": len(rows)})} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/src/setup/index.py b/aurora-serverless-v2-lambda-bedrock-cdk/src/setup/index.py new file mode 100644 index 000000000..ec6aac6c8 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/src/setup/index.py @@ -0,0 +1,53 @@ +"""Setup handler — creates the knowledge table and seeds sample data via RDS Data API.""" + +import json +import os +import boto3 + +rds_data = boto3.client("rds-data") + +CLUSTER_ARN = os.environ["CLUSTER_ARN"] +SECRET_ARN = os.environ["SECRET_ARN"] +DB_NAME = os.environ["DB_NAME"] + + +def execute(sql, params=None): + kwargs = { + "resourceArn": CLUSTER_ARN, + "secretArn": SECRET_ARN, + "database": DB_NAME, + "sql": sql, + } + if params: + kwargs["parameters"] = params + return rds_data.execute_statement(**kwargs) + + +def handler(event, _context): + execute(""" + CREATE TABLE IF NOT EXISTS knowledge ( + id SERIAL PRIMARY KEY, + topic TEXT NOT NULL, + content TEXT NOT NULL + ) + """) + + result = execute("SELECT count(*) FROM knowledge") + count = result["records"][0][0]["longValue"] + + if count == 0: + rows = [ + ("Aurora Serverless v2", "Aurora Serverless v2 platform version 4 delivers up to 30% better performance with enhanced scaling. It scales to zero when idle and is ideal for agentic AI workloads with burst patterns."), + ("Lambda", "AWS Lambda supports up to 10 GB memory, 15-minute timeout, and features like durable functions, SnapStart, managed instances, and S3 Files mounting."), + ("Bedrock", "Amazon Bedrock provides access to foundation models from Anthropic, Meta, Amazon, and OpenAI through a unified API with built-in security and governance."), + ] + for topic, content in rows: + execute( + "INSERT INTO knowledge (topic, content) VALUES (:topic, :content)", + [ + {"name": "topic", "value": {"stringValue": topic}}, + {"name": "content", "value": {"stringValue": content}}, + ], + ) + + return {"statusCode": 200, "body": f"Setup complete. Rows: {count + len(rows) if count == 0 else count}"} diff --git a/aurora-serverless-v2-lambda-bedrock-cdk/tsconfig.json b/aurora-serverless-v2-lambda-bedrock-cdk/tsconfig.json new file mode 100644 index 000000000..9b34373a0 --- /dev/null +++ b/aurora-serverless-v2-lambda-bedrock-cdk/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "outDir": "build", + "rootDir": ".", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "esModuleInterop": true + }, + "exclude": ["node_modules", "build"] +}