From 961a6de90ad8251af64d5aa474cb82c5042b5c98 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Sun, 26 Apr 2026 18:55:33 +0000 Subject: [PATCH] feat(sfn): Add Step Functions Bedrock InvokeModel CDK pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step Functions Express workflow that invokes Amazon Bedrock Claude Sonnet directly using the native InvokeModel optimized integration. No Lambda function required — reduces latency, cost, and complexity. - BedrockInvokeModel L2 CDK construct with resultSelector - Express workflow for synchronous sub-5-minute executions - Built-in retry with exponential backoff (3 attempts, 20s base) - Scoped IAM for inference profile + foundation model ARNs --- sfn-bedrock-invokemodel-cdk/.gitignore | 5 ++ sfn-bedrock-invokemodel-cdk/.npmignore | 3 + sfn-bedrock-invokemodel-cdk/README.md | 83 +++++++++++++++++ sfn-bedrock-invokemodel-cdk/bin/app.ts | 7 ++ sfn-bedrock-invokemodel-cdk/cdk.context.json | 7 ++ sfn-bedrock-invokemodel-cdk/cdk.json | 3 + .../example-pattern.json | 52 +++++++++++ .../lib/sfn-bedrock-invokemodel-stack.ts | 88 +++++++++++++++++++ sfn-bedrock-invokemodel-cdk/package.json | 15 ++++ sfn-bedrock-invokemodel-cdk/tsconfig.json | 22 +++++ 10 files changed, 285 insertions(+) create mode 100644 sfn-bedrock-invokemodel-cdk/.gitignore create mode 100644 sfn-bedrock-invokemodel-cdk/.npmignore create mode 100644 sfn-bedrock-invokemodel-cdk/README.md create mode 100644 sfn-bedrock-invokemodel-cdk/bin/app.ts create mode 100644 sfn-bedrock-invokemodel-cdk/cdk.context.json create mode 100644 sfn-bedrock-invokemodel-cdk/cdk.json create mode 100644 sfn-bedrock-invokemodel-cdk/example-pattern.json create mode 100644 sfn-bedrock-invokemodel-cdk/lib/sfn-bedrock-invokemodel-stack.ts create mode 100644 sfn-bedrock-invokemodel-cdk/package.json create mode 100644 sfn-bedrock-invokemodel-cdk/tsconfig.json diff --git a/sfn-bedrock-invokemodel-cdk/.gitignore b/sfn-bedrock-invokemodel-cdk/.gitignore new file mode 100644 index 000000000..23c7a1062 --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/.gitignore @@ -0,0 +1,5 @@ +*.js +*.d.ts +node_modules +cdk.out +package-lock.json diff --git a/sfn-bedrock-invokemodel-cdk/.npmignore b/sfn-bedrock-invokemodel-cdk/.npmignore new file mode 100644 index 000000000..72b28b5da --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/.npmignore @@ -0,0 +1,3 @@ +*.ts +!*.d.ts +cdk.out diff --git a/sfn-bedrock-invokemodel-cdk/README.md b/sfn-bedrock-invokemodel-cdk/README.md new file mode 100644 index 000000000..bbdd1a5e4 --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/README.md @@ -0,0 +1,83 @@ +# Step Functions to Amazon Bedrock InvokeModel (CDK) + +This pattern deploys a Step Functions Express workflow that invokes Amazon Bedrock (Claude Sonnet) directly using the **native InvokeModel optimized integration** — no Lambda function required. Deployed with AWS CDK. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/sfn-bedrock-invokemodel-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 + +Step Functions has a native optimized integration with Amazon Bedrock that lets you call `bedrock:InvokeModel` directly from a workflow state — no Lambda function needed. This reduces latency, cost, and operational complexity. + +**Key points:** +- Uses `arn:aws:states:::bedrock:invokeModel` resource (optimized integration) +- Express workflow for synchronous execution (lower cost, sub-5-minute executions) +- Built-in retry with exponential backoff for transient failures +- `resultSelector` extracts just the response text, model, and usage from the Bedrock response + +``` +┌──────────────────────┐ ┌──────────────────────┐ +│ │ │ │ +│ Step Functions │────────▶│ Amazon Bedrock │ +│ (Express) │ │ Claude Sonnet │ +│ │◀────────│ │ +└──────────────────────┘ └──────────────────────┘ +``` + +## Deployment + +1. Install dependencies: + ```bash + cd sfn-bedrock-invokemodel-cdk + npm install + ``` + +2. Deploy the stack: + ```bash + cdk deploy + ``` + +## Testing + +Start a synchronous execution of the Express workflow: + +```bash +aws stepfunctions start-sync-execution \ + --state-machine-arn $(aws cloudformation describe-stacks \ + --stack-name SfnBedrockInvokemodelStack \ + --query 'Stacks[0].Outputs[?OutputKey==`StateMachineArn`].OutputValue' \ + --output text) \ + --input '{"prompt": "What are the benefits of Step Functions native Bedrock integration?"}' \ + --query '{status: status, output: output}' \ + --output json +``` + +The response includes the generated text, model ID, and token usage: + +```json +{ + "status": "SUCCEEDED", + "output": "{\"response\": \"...\", \"model\": \"claude-...\", \"usage\": {\"input_tokens\": 15, \"output_tokens\": 200}}" +} +``` + +## Cleanup + +```bash +cdk destroy +``` + +--- + +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/sfn-bedrock-invokemodel-cdk/bin/app.ts b/sfn-bedrock-invokemodel-cdk/bin/app.ts new file mode 100644 index 000000000..dd601cf18 --- /dev/null +++ b/sfn-bedrock-invokemodel-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 { SfnBedrockInvokemodelStack } from "../lib/sfn-bedrock-invokemodel-stack"; + +const app = new cdk.App(); +new SfnBedrockInvokemodelStack(app, "SfnBedrockInvokemodelStack"); diff --git a/sfn-bedrock-invokemodel-cdk/cdk.context.json b/sfn-bedrock-invokemodel-cdk/cdk.context.json new file mode 100644 index 000000000..a4f31f660 --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/cdk.context.json @@ -0,0 +1,7 @@ +{ + "acknowledged-issue-numbers": [ + 33623, + 34635, + 34892 + ] +} diff --git a/sfn-bedrock-invokemodel-cdk/cdk.json b/sfn-bedrock-invokemodel-cdk/cdk.json new file mode 100644 index 000000000..27fe6d2ec --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/app.ts" +} diff --git a/sfn-bedrock-invokemodel-cdk/example-pattern.json b/sfn-bedrock-invokemodel-cdk/example-pattern.json new file mode 100644 index 000000000..aba3a6b8c --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/example-pattern.json @@ -0,0 +1,52 @@ +{ + "title": "Step Functions to Amazon Bedrock InvokeModel", + "description": "Invoke Amazon Bedrock directly from Step Functions using the native InvokeModel optimized integration — no Lambda function required. Deployed with AWS CDK.", + "language": "TypeScript", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys a Step Functions Express workflow that invokes Amazon Bedrock (Claude Sonnet) directly using the native bedrock:InvokeModel optimized integration.", + "No Lambda function is needed — Step Functions calls Bedrock directly, reducing latency, cost, and operational complexity.", + "The workflow accepts a prompt string as input, sends it to Bedrock, and returns the generated response with usage metadata.", + "Built-in retry logic handles transient failures with exponential backoff." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sfn-bedrock-invokemodel-cdk", + "templateURL": "serverless-patterns/sfn-bedrock-invokemodel-cdk", + "projectFolder": "sfn-bedrock-invokemodel-cdk", + "templateFile": "lib/sfn-bedrock-invokemodel-stack.ts" + } + }, + "resources": { + "bullets": [ + { "text": "Step Functions optimized integration for Amazon Bedrock", "link": "https://aws.amazon.com/about-aws/whats-new/2023/11/aws-step-functions-optimized-integration-bedrock/" }, + { "text": "Build generative AI apps using Step Functions and Bedrock", "link": "https://aws.amazon.com/blogs/aws/build-generative-ai-apps-using-aws-step-functions-and-amazon-bedrock/" }, + { "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": "sfn", "label": "Step Functions" }, + "icon2": { "x": 80, "y": 50, "service": "bedrock", "label": "Amazon Bedrock" }, + "line1": { "from": "icon1", "to": "icon2" } + } +} diff --git a/sfn-bedrock-invokemodel-cdk/lib/sfn-bedrock-invokemodel-stack.ts b/sfn-bedrock-invokemodel-cdk/lib/sfn-bedrock-invokemodel-stack.ts new file mode 100644 index 000000000..3822b76d5 --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/lib/sfn-bedrock-invokemodel-stack.ts @@ -0,0 +1,88 @@ +import * as cdk from "aws-cdk-lib"; +import * as sfn from "aws-cdk-lib/aws-stepfunctions"; +import * as tasks from "aws-cdk-lib/aws-stepfunctions-tasks"; +import * as bedrock from "aws-cdk-lib/aws-bedrock"; +import * as logs from "aws-cdk-lib/aws-logs"; +import * as iam from "aws-cdk-lib/aws-iam"; +import { Construct } from "constructs"; + +export class SfnBedrockInvokemodelStack 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", + }); + + // Construct inference profile ARN for the model + const inferenceProfileArn = `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`; + const model = bedrock.ProvisionedModel.fromProvisionedModelArn( + this, + "Model", + inferenceProfileArn + ); + + // Step Functions task: invoke Bedrock directly (no Lambda needed) + const invokeModel = new tasks.BedrockInvokeModel(this, "InvokeModel", { + model, + body: sfn.TaskInput.fromObject({ + anthropic_version: "bedrock-2023-05-31", + max_tokens: 1024, + messages: [ + { + role: "user", + content: sfn.JsonPath.stringAt("$.prompt"), + }, + ], + }), + resultSelector: { + "response.$": "$.Body.content[0].text", + "model.$": "$.Body.model", + "usage.$": "$.Body.usage", + }, + }); + + // Add retry for transient failures + invokeModel.addRetry({ + errors: ["States.TaskFailed"], + interval: cdk.Duration.seconds(20), + maxAttempts: 3, + backoffRate: 2, + }); + + const logGroup = new logs.LogGroup(this, "LogGroup", { + logGroupName: "/aws/stepfunctions/sfn-bedrock-invokemodel", + retention: logs.RetentionDays.TWO_WEEKS, + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + const stateMachine = new sfn.StateMachine(this, "StateMachine", { + stateMachineName: "sfn-bedrock-invokemodel", + definitionBody: sfn.DefinitionBody.fromChainable(invokeModel), + stateMachineType: sfn.StateMachineType.EXPRESS, + timeout: cdk.Duration.minutes(5), + logs: { + destination: logGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // Grant Bedrock access (inference profile + foundation model) + stateMachine.addToRolePolicy( + new iam.PolicyStatement({ + actions: ["bedrock:InvokeModel"], + resources: [ + `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`, + "arn:aws:bedrock:*::foundation-model/*", + ], + }) + ); + + new cdk.CfnOutput(this, "StateMachineArn", { value: stateMachine.stateMachineArn }); + new cdk.CfnOutput(this, "StateMachineName", { value: stateMachine.stateMachineName! }); + new cdk.CfnOutput(this, "LogGroupName", { value: logGroup.logGroupName }); + } +} diff --git a/sfn-bedrock-invokemodel-cdk/package.json b/sfn-bedrock-invokemodel-cdk/package.json new file mode 100644 index 000000000..54e8c47bc --- /dev/null +++ b/sfn-bedrock-invokemodel-cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "sfn-bedrock-invokemodel-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/sfn-bedrock-invokemodel-cdk/tsconfig.json b/sfn-bedrock-invokemodel-cdk/tsconfig.json new file mode 100644 index 000000000..5a9a7d6dd --- /dev/null +++ b/sfn-bedrock-invokemodel-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"] +}