Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
140 changes: 140 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Amazon Bedrock Async Invoke with AWS Lambda durable functions

This pattern shows how to use AWS Lambda durable functions to orchestrate [Amazon Bedrock Async Invoke](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_StartAsyncInvoke.html) for AI video generation. The durable function starts an Amazon Nova Reel video generation job, then polls for completion using `waitForCondition` with exponential backoff. During each polling interval the function suspends execution entirely, incurring zero compute charges while Bedrock processes the video.

Without durable functions this pattern would require a separate polling mechanism such as Step Functions, EventBridge rules, or a cron-based poller. Here the entire workflow is a single, linear function that reads top-to-bottom.

Learn more about this pattern at Serverless Land Patterns: [https://serverlessland.com/patterns/lambda-durable-bedrock-async-invoke](https://serverlessland.com/patterns/lambda-durable-bedrock-async-invoke)

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 v2](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) (latest available version) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html) (version 2.232.1 or later) installed and configured
* [Node.js 22.x](https://nodejs.org/) installed
* Amazon Bedrock model access enabled for **Amazon Nova Reel** (`amazon.nova-reel-v1:1`) in your target region

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:

```bash
git clone https://github.com/aws-samples/serverless-patterns
```

1. Change directory to the pattern directory:

```bash
cd lambda-durable-bedrock-async-invoke
```

1. Install the project dependencies:

```bash
npm install
```

1. Deploy the CDK stack:

```bash
npx cdk deploy
```

The stack creates:
- An S3 bucket for video output (auto-deleted on stack destroy, 7-day lifecycle)
- A durable Lambda function with 30-minute execution timeout
- IAM permissions for Bedrock and S3 access
- A CloudWatch log group with 1-week retention

1. Note the outputs from the CDK deployment process. These contain the resource names and ARNs used for testing.

## How it works

The durable function performs three logical phases:

1. **`start-video-generation` step** — calls `StartAsyncInvoke` with a `clientRequestToken` for Bedrock-level idempotency. Because this runs inside a durable step, the Bedrock invocation ARN is checkpointed and will not be re-executed on replay.

2. **`wait-for-video-ready` waitForCondition** — polls `GetAsyncInvoke` with exponential backoff (30 s → 60 s cap). The function suspends during each wait interval, consuming no compute time while the video is being generated.

3. **`build-result` step** — assembles the final response with the S3 output location and metadata, or throws an error if the generation failed.

```
Client ──► Lambda (durable) ──► Bedrock StartAsyncInvoke ──► S3 (video output)
│ │
│ ◄── waitForCondition ──► │
│ (poll with │
│ exponential │
│ backoff) │
│ │
└── GetAsyncInvoke ─────────┘
```

Key concepts:

| Concept | How it is used |
|---|---|
| `step` | Wraps the `StartAsyncInvoke` call so it is checkpointed and never re-executed on replay |
| `waitForCondition` | Polls `GetAsyncInvoke` with exponential backoff; the function suspends between polls |
| `clientRequestToken` | Bedrock idempotency token generated inside a step, ensuring replays cannot create duplicate invocations |
| `context.logger` | Replay-aware structured logging throughout the workflow |
| S3 output | Bedrock writes the generated video directly to an S3 bucket provisioned by CDK |

## Testing

After deployment, invoke the durable function using the AWS CLI.

Because the durable execution timeout is 30 minutes (exceeding Lambda's 15-minute synchronous limit), you must invoke the function **asynchronously**. Use `--durable-execution-name` for idempotency at the Lambda level.

### Invoke the durable function

```bash
aws lambda invoke \
--function-name 'video-generator-durable:$LATEST' \
--invocation-type Event \
--durable-execution-name "my-beach-video-001" \
--payload '{"prompt":"A golden retriever playing fetch on a sunny beach","durationSeconds":6}' \
--cli-binary-format raw-in-base64-out \
response.json
```

Repeat the same command with the same `--durable-execution-name` to safely retry without creating a duplicate execution.

### Check execution status

```bash
aws lambda get-durable-execution \
--function-name 'video-generator-durable:$LATEST' \
--durable-execution-name "my-beach-video-001"
```

Once the status shows `SUCCEEDED`, the result will contain the S3 URI where the video was written.

### Run unit tests

```bash
npm test
```

This runs both CDK infrastructure tests and durable handler tests (with mocked Bedrock calls).

## Cleanup

1. Delete the stack:

```bash
npx cdk destroy
```

1. Confirm the stack has been deleted by checking the AWS CloudFormation console or running:

```bash
aws cloudformation list-stacks --stack-status-filter DELETE_COMPLETE
```

----
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/opt/homebrew/opt/node/bin/node
import * as cdk from 'aws-cdk-lib/core';
import { CdkBedrockAsyncInvokeStack } from '../lib/cdk-bedrock-async-invoke-stack';

const app = new cdk.App();
new CdkBedrockAsyncInvokeStack(app, 'CdkBedrockAsyncInvokeStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */

/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },

/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
103 changes: 103 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"app": "npx ts-node --prefer-ts-exts bin/cdk-bedrock-async-invoke.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-signer:signingProfileNamePassedToCfn": true,
"@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/core:explicitStackTags": true,
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
"@aws-cdk/core:aspectPrioritiesMutating": true,
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
"@aws-cdk/aws-lambda:useCdkManagedLogGroup": true,
"@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": true,
"@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": true
}
}
71 changes: 71 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"title": "Amazon Bedrock Async Invoke with AWS Lambda durable functions",
"description": "Orchestrate long-running Amazon Bedrock video generation jobs using AWS Lambda durable functions with waitForCondition polling and zero-cost waits",
"language": "TypeScript",
"level": "300",
"framework": "AWS CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys an AWS Lambda durable function that orchestrates Amazon Bedrock Async Invoke to generate AI videos using Amazon Nova Reel.",
"The durable function starts a video generation job with StartAsyncInvoke, then polls for completion using waitForCondition with exponential backoff. During each polling interval the function suspends entirely, incurring zero compute charges while Bedrock processes the video.",
"An S3 bucket is provisioned for video output. The durable execution SDK checkpoints progress at each step, so the workflow is fault-tolerant and idempotent without requiring external orchestration."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-bedrock-async-invoke",
"templateURL": "serverless-patterns/lambda-durable-bedrock-async-invoke",
"projectFolder": "lambda-durable-bedrock-async-invoke",
"templateFile": "lib/cdk-bedrock-async-invoke-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "AWS Lambda durable functions documentation",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-basic-concepts.html"
},
{
"text": "Durable Execution SDK",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-execution-sdk.html"
},
{
"text": "Amazon Bedrock Async Invoke API reference",
"link": "https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_StartAsyncInvoke.html"
},
{
"text": "AWS CDK Developer Guide",
"link": "https://docs.aws.amazon.com/cdk/latest/guide/"
}
]
},
"deploy": {
"text": [
"npm install",
"npx cdk deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>npx cdk destroy</code>."
]
},
"authors": [
{
"name": "Ben Freiberg",
"image": "https://serverlessland.com/assets/images/resources/contributors/ben-freiberg.jpg",
"bio": "Ben is a Senior Solutions Architect at Amazon Web Services (AWS) based in Frankfurt, Germany.",
"linkedin": "benfreiberg"
},{
"name": "Michael Gasch",
"bio": "Michael is a Senior Product Manager at Amazon Web Services (AWS) based in Germany.",
"linkedin": "michael-gasch"
}
]
}
8 changes: 8 additions & 0 deletions lambda-durable-bedrock-async-invoke-cdk/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test', '<rootDir>/lib'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
};
Loading