diff --git a/lambda-snapstart-bedrock-cdk/.gitignore b/lambda-snapstart-bedrock-cdk/.gitignore new file mode 100644 index 000000000..7bddfb4c1 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/.gitignore @@ -0,0 +1,6 @@ +*.js +!src/*.js +*.d.ts +node_modules +cdk.out +package-lock.json diff --git a/lambda-snapstart-bedrock-cdk/.npmignore b/lambda-snapstart-bedrock-cdk/.npmignore new file mode 100644 index 000000000..72b28b5da --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/.npmignore @@ -0,0 +1,3 @@ +*.ts +!*.d.ts +cdk.out diff --git a/lambda-snapstart-bedrock-cdk/README.md b/lambda-snapstart-bedrock-cdk/README.md new file mode 100644 index 000000000..5ce1e974f --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/README.md @@ -0,0 +1,87 @@ +# Lambda SnapStart with Amazon Bedrock + +This pattern deploys a Python Lambda function with **SnapStart** enabled that invokes Amazon Bedrock (Claude Sonnet) for text generation, using AWS CDK. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-snapstart-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 +- [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed +- [Node.js](https://nodejs.org/) 18.x or later +- [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your AWS account + +## How it works + +Lambda SnapStart reduces cold start latency by up to 10x by pre-initializing the execution environment and caching a snapshot of the initialized state. When a new execution environment is needed, Lambda restores from the cached snapshot instead of running full initialization. + +**Key points:** +- SnapStart is enabled via `snapStart: lambda.SnapStartConf.ON_PUBLISHED_VERSIONS` in CDK +- SnapStart only applies to **published versions**, not `$LATEST` — the pattern creates a version and alias automatically +- Supported runtimes: Java 11/17/21, Python 3.12/3.13, .NET 8 +- The Bedrock client is initialized at module level (outside the handler) so it gets captured in the SnapStart snapshot + +``` +┌──────────────────────┐ ┌──────────────────────┐ +│ │ │ │ +│ Lambda (SnapStart) │────────▶│ Amazon Bedrock │ +│ Python 3.13 │ │ Claude Sonnet │ +│ │◀────────│ │ +└──────────────────────┘ └──────────────────────┘ +``` + +## Deployment + +1. Install dependencies: + ```bash + cd lambda-snapstart-bedrock-cdk + npm install + ``` + +2. Deploy the stack: + ```bash + cdk deploy + ``` + +## Testing + +Invoke the function via the **alias** (SnapStart only applies to published versions): + +```bash +aws lambda invoke \ + --function-name snapstart-bedrock-cdk:live \ + --payload '{"prompt": "Explain Lambda SnapStart in one paragraph"}' \ + --cli-binary-format raw-in-base64-out \ + output.json + +cat output.json | python3 -m json.tool +``` + +To compare cold start times, invoke `$LATEST` (no SnapStart) vs the alias (with SnapStart): + +```bash +# Without SnapStart ($LATEST) +aws lambda invoke --function-name snapstart-bedrock-cdk \ + --payload '{"prompt": "Hello"}' --cli-binary-format raw-in-base64-out /dev/null + +# With SnapStart (alias) +aws lambda invoke --function-name snapstart-bedrock-cdk:live \ + --payload '{"prompt": "Hello"}' --cli-binary-format raw-in-base64-out /dev/null +``` + +Check the `Init Duration` in CloudWatch Logs — SnapStart-optimized invocations show `Restore Duration` instead, which is significantly lower. + +## Cleanup + +```bash +cdk destroy +``` + +--- + +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/lambda-snapstart-bedrock-cdk/bin/app.ts b/lambda-snapstart-bedrock-cdk/bin/app.ts new file mode 100644 index 000000000..e562c1928 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/bin/app.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { LambdaSnapstartBedrockStack } from "../lib/lambda-snapstart-bedrock-stack"; + +const app = new cdk.App(); +new LambdaSnapstartBedrockStack(app, "LambdaSnapstartBedrockStack"); diff --git a/lambda-snapstart-bedrock-cdk/cdk.context.json b/lambda-snapstart-bedrock-cdk/cdk.context.json new file mode 100644 index 000000000..a4f31f660 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/cdk.context.json @@ -0,0 +1,7 @@ +{ + "acknowledged-issue-numbers": [ + 33623, + 34635, + 34892 + ] +} diff --git a/lambda-snapstart-bedrock-cdk/cdk.json b/lambda-snapstart-bedrock-cdk/cdk.json new file mode 100644 index 000000000..27fe6d2ec --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/app.ts" +} diff --git a/lambda-snapstart-bedrock-cdk/example-pattern.json b/lambda-snapstart-bedrock-cdk/example-pattern.json new file mode 100644 index 000000000..7e842d550 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/example-pattern.json @@ -0,0 +1,52 @@ +{ + "title": "Lambda SnapStart with Amazon Bedrock", + "description": "Invoke Amazon Bedrock from a Lambda function with SnapStart enabled for dramatically reduced cold start latency, deployed with AWS CDK.", + "language": "Python", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys a Python Lambda function with SnapStart enabled that invokes Amazon Bedrock (Claude Sonnet) for text generation.", + "Lambda SnapStart reduces cold start latency by up to 10x by pre-initializing the execution environment and caching a snapshot of the initialized state.", + "When a new execution environment is needed, Lambda restores from the cached snapshot instead of running full initialization, providing near-instant cold starts.", + "A published version and alias are created to activate SnapStart optimization — SnapStart only applies to published versions, not $LATEST." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-snapstart-bedrock-cdk", + "templateURL": "serverless-patterns/lambda-snapstart-bedrock-cdk", + "projectFolder": "lambda-snapstart-bedrock-cdk", + "templateFile": "lib/lambda-snapstart-bedrock-stack.ts" + } + }, + "resources": { + "bullets": [ + { "text": "Lambda SnapStart documentation", "link": "https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html" }, + { "text": "Lambda SnapStart for Python and .NET", "link": "https://aws.amazon.com/blogs/aws/reduce-cold-starts-for-your-lambda-functions-with-snapstart-for-python-and-net/" }, + { "text": "Amazon Bedrock documentation", "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/" } + ] + }, + "deploy": { + "text": ["cdk deploy"] + }, + "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" + } + ], + "patternArch": { + "icon1": { "x": 20, "y": 50, "service": "lambda", "label": "Lambda (SnapStart)" }, + "icon2": { "x": 80, "y": 50, "service": "bedrock", "label": "Amazon Bedrock" }, + "line1": { "from": "icon1", "to": "icon2" } + } +} diff --git a/lambda-snapstart-bedrock-cdk/lib/lambda-snapstart-bedrock-stack.ts b/lambda-snapstart-bedrock-cdk/lib/lambda-snapstart-bedrock-stack.ts new file mode 100644 index 000000000..8b1c53230 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/lib/lambda-snapstart-bedrock-stack.ts @@ -0,0 +1,63 @@ +import * as cdk from "aws-cdk-lib"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as logs from "aws-cdk-lib/aws-logs"; +import { Construct } from "constructs"; + +export class LambdaSnapstartBedrockStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const modelId = new cdk.CfnParameter(this, "BedrockModelId", { + type: "String", + default: "us.anthropic.claude-sonnet-4-20250514-v1:0", + description: "Bedrock inference profile model ID", + }); + + const functionName = "snapstart-bedrock-cdk"; + + const logGroup = new logs.LogGroup(this, "LogGroup", { + logGroupName: `/aws/lambda/${functionName}`, + retention: logs.RetentionDays.TWO_WEEKS, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + const fn = new lambda.Function(this, "BedrockFn", { + runtime: lambda.Runtime.PYTHON_3_13, + handler: "index.handler", + code: lambda.Code.fromAsset("src"), + timeout: cdk.Duration.seconds(30), + memorySize: 512, + functionName, + description: "Bedrock invocation with SnapStart for reduced cold starts", + loggingFormat: lambda.LoggingFormat.JSON, + logGroup, + snapStart: lambda.SnapStartConf.ON_PUBLISHED_VERSIONS, + environment: { MODEL_ID: modelId.valueAsString }, + }); + + fn.addToRolePolicy( + new iam.PolicyStatement({ + actions: ["bedrock:InvokeModel"], + resources: [ + `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`, + "arn:aws:bedrock:*::foundation-model/*", + ], + }) + ); + + // Publish a version to activate SnapStart + const version = fn.currentVersion; + + // Alias pointing to the SnapStart-optimized version + const alias = new lambda.Alias(this, "LiveAlias", { + aliasName: "live", + version, + }); + + new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName }); + new cdk.CfnOutput(this, "FunctionArn", { value: fn.functionArn }); + new cdk.CfnOutput(this, "AliasArn", { value: alias.functionArn }); + new cdk.CfnOutput(this, "LogGroupName", { value: logGroup.logGroupName }); + } +} diff --git a/lambda-snapstart-bedrock-cdk/package.json b/lambda-snapstart-bedrock-cdk/package.json new file mode 100644 index 000000000..6b9edec19 --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "lambda-snapstart-bedrock-cdk", + "version": "1.0.0", + "bin": { "app": "bin/app.ts" }, + "scripts": { "build": "tsc", "cdk": "cdk" }, + "dependencies": { + "aws-cdk-lib": "2.236.0", + "constructs": "10.4.2" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "ts-node": "^10.9.0", + "typescript": "~5.7.0" + } +} diff --git a/lambda-snapstart-bedrock-cdk/src/index.py b/lambda-snapstart-bedrock-cdk/src/index.py new file mode 100644 index 000000000..7805141fc --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/src/index.py @@ -0,0 +1,33 @@ +import json +import os +import boto3 + +bedrock = boto3.client("bedrock-runtime") +MODEL_ID = os.environ["MODEL_ID"] + + +def handler(event, context): + prompt = event.get("prompt", "What are the benefits of Lambda SnapStart?") + + response = bedrock.invoke_model( + modelId=MODEL_ID, + contentType="application/json", + accept="application/json", + body=json.dumps({ + "anthropic_version": "bedrock-2023-05-31", + "max_tokens": 1024, + "messages": [{"role": "user", "content": prompt}], + }), + ) + + body = json.loads(response["body"].read()) + + return { + "statusCode": 200, + "body": json.dumps({ + "prompt": prompt, + "response": body["content"][0]["text"], + "model": MODEL_ID, + "usage": body["usage"], + }), + } diff --git a/lambda-snapstart-bedrock-cdk/tsconfig.json b/lambda-snapstart-bedrock-cdk/tsconfig.json new file mode 100644 index 000000000..5a9a7d6dd --- /dev/null +++ b/lambda-snapstart-bedrock-cdk/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false + }, + "exclude": ["cdk.out"] +}