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
48 changes: 48 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# API Gateway REST Streaming with Lambda and Amazon Bedrock

<!--BEGIN STABILITY BANNER-->
---

![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)

> **This is an experimental example. It may not build out of the box**
>
> This example uses API Gateway REST response streaming (re:Invent 2025 launch) with `ResponseTransferMode: STREAM`.

---
<!--END STABILITY BANNER-->

This example creates an API Gateway REST API that streams responses from Amazon Bedrock (Claude) via Lambda response streaming, delivering tokens to the client as they are generated.

## Architecture

```
Client → API Gateway (REST, streaming) → Lambda (streamifyResponse) → Bedrock (InvokeModelWithResponseStream)
```

## Build

```bash
npm install
npm run build
```

## Deploy

```bash
npx cdk deploy
```

## Test

```bash
curl -N -X POST <ApiEndpoint> \
-H "Content-Type: application/json" \
-d '{"prompt":"Explain serverless in 3 sentences"}'
```

## Cleanup

```bash
npx cdk destroy
```
3 changes: 3 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node index.ts"
}
65 changes: 65 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import * as logs from "aws-cdk-lib/aws-logs";
import { Construct } from "constructs";

export class ApigwStreamingLambdaBedrockStack 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 fn = new lambda.Function(this, "StreamingBedrockFn", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "index.handler",
code: lambda.Code.fromAsset("src"),
timeout: cdk.Duration.minutes(5),
memorySize: 256,
environment: { MODEL_ID: modelId.valueAsString },
logRetention: logs.RetentionDays.ONE_WEEK,
});

fn.addToRolePolicy(
new iam.PolicyStatement({
actions: ["bedrock:InvokeModelWithResponseStream"],
resources: [
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`,
"arn:aws:bedrock:*::foundation-model/*",
],
})
);

const api = new apigateway.RestApi(this, "StreamingApi", {
restApiName: "Bedrock Streaming API",
description: "REST API with response streaming to Bedrock",
deployOptions: { stageName: "prod" },
});

const chatResource = api.root.addResource("chat");
const method = chatResource.addMethod(
"POST",
new apigateway.LambdaIntegration(fn, { timeout: cdk.Duration.minutes(5) })
);

// Override to use streaming invocation path
const cfnMethod = method.node.defaultChild as apigateway.CfnMethod;
cfnMethod.addPropertyOverride(
"Integration.Uri",
`arn:aws:apigateway:${this.region}:lambda:path/2021-11-15/functions/${fn.functionArn}/response-streaming-invocations`
);
cfnMethod.addPropertyOverride("Integration.ResponseTransferMode", "STREAM");
cfnMethod.addPropertyOverride("Integration.TimeoutInMillis", 300000);

new cdk.CfnOutput(this, "ApiEndpoint", { value: api.urlForPath("/chat") });
new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName });
}
}

const app = new cdk.App();
new ApigwStreamingLambdaBedrockStack(app, "ApigwStreamingLambdaBedrockStack");
11 changes: 11 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "apigw-streaming-lambda-bedrock",
"version": "1.0.0",
"description": "API Gateway REST streaming with Lambda and Amazon Bedrock",
"private": true,
"scripts": { "build": "tsc", "watch": "tsc -w", "cdk": "cdk" },
"author": { "name": "Amazon Web Services", "url": "https://aws.amazon.com", "organization": true },
"license": "Apache-2.0",
"devDependencies": { "@types/node": "^22.0.0", "aws-cdk": "2.1119.0", "typescript": "~5.7.0" },
"dependencies": { "aws-cdk-lib": "2.250.0", "constructs": "^10.0.0" }
}
42 changes: 42 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { BedrockRuntimeClient, InvokeModelWithResponseStreamCommand } = require("@aws-sdk/client-bedrock-runtime");

const client = new BedrockRuntimeClient();
const MODEL_ID = process.env.MODEL_ID;

exports.handler = awslambda.streamifyResponse(async (event, responseStream, _context) => {
const body = JSON.parse(event.body || "{}");
const prompt = body.prompt || "Hello";

responseStream = awslambda.HttpResponseStream.from(responseStream, {
statusCode: 200,
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
});

try {
const response = await client.send(
new InvokeModelWithResponseStreamCommand({
modelId: MODEL_ID,
contentType: "application/json",
accept: "application/json",
body: JSON.stringify({
anthropic_version: "bedrock-2023-05-31",
max_tokens: 2048,
messages: [{ role: "user", content: prompt }],
}),
})
);

for await (const event of response.body) {
if (event.chunk) {
const parsed = JSON.parse(new TextDecoder().decode(event.chunk.bytes));
if (parsed.type === "content_block_delta" && parsed.delta?.text) {
responseStream.write(`data: ${JSON.stringify({ text: parsed.delta.text })}\n\n`);
}
}
}
responseStream.write("data: [DONE]\n\n");
} catch (err) {
responseStream.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
}
responseStream.end();
});
22 changes: 22 additions & 0 deletions typescript/apigw-streaming-lambda-bedrock/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}
62 changes: 62 additions & 0 deletions typescript/lambda-durable-bedrock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Lambda Durable Functions with Amazon Bedrock

<!--BEGIN STABILITY BANNER-->
---

![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)

> **This is an experimental example. It may not build out of the box**
>
> This example uses AWS Lambda Durable Functions (re:Invent 2025 launch) which requires `nodejs24.x` runtime and the `@aws/durable-execution-sdk-js` SDK.

---
<!--END STABILITY BANNER-->

This example demonstrates AWS Lambda Durable Functions integrated with Amazon Bedrock to build a multi-step AI content generation workflow with automatic checkpointing.

The Lambda function uses durable execution to:
1. Generate a blog outline using Bedrock (Claude)
2. Wait 5 seconds (simulating editorial review)
3. Expand the outline into a draft
4. Generate a summary

Each step is automatically checkpointed — if the function fails mid-execution, it resumes from the last completed step rather than restarting.

## Architecture

```
User → Lambda (Durable) → Step 1: Bedrock (outline)
→ Wait (5s)
→ Step 2: Bedrock (draft)
→ Step 3: Bedrock (summary)
→ Return result
```

## Build

```bash
npm install
npm run build
```

## Deploy

```bash
npx cdk deploy
```

## Test

```bash
aws lambda invoke \
--function-name <FunctionName> \
--qualifier <VersionNumber> \
--payload '{"topic":"AWS Lambda Durable Functions"}' \
output.json && cat output.json
```

## Cleanup

```bash
npx cdk destroy
```
3 changes: 3 additions & 0 deletions typescript/lambda-durable-bedrock/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node index.ts"
}
60 changes: 60 additions & 0 deletions typescript/lambda-durable-bedrock/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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 { Construct } from "constructs";

export class LambdaDurableBedrockStack 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 fn = new lambda.Function(this, "DurableBedrockFn", {
runtime: lambda.Runtime.NODEJS_20_X,
handler: "index.handler",
code: lambda.Code.fromAsset("src"),
timeout: cdk.Duration.minutes(15),
memorySize: 256,
environment: { MODEL_ID: modelId.valueAsString },
});

// Enable durable execution via escape hatch (no L2 yet)
const cfnFn = fn.node.defaultChild as lambda.CfnFunction;
cfnFn.addOverride("Properties.Runtime", "nodejs24.x");
cfnFn.addOverride("Properties.DurableConfig", {
ExecutionTimeout: 900,
RetentionPeriodInDays: 14,
});

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/*",
],
})
);

fn.role!.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaBasicDurableExecutionRolePolicy"
)
);

const cfnVersion = new lambda.CfnVersion(this, "DurableVersion", {
functionName: fn.functionName,
description: "Durable execution version",
});

new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName });
new cdk.CfnOutput(this, "VersionNumber", { value: cfnVersion.attrVersion });
}
}

const app = new cdk.App();
new LambdaDurableBedrockStack(app, "LambdaDurableBedrockStack");
26 changes: 26 additions & 0 deletions typescript/lambda-durable-bedrock/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "lambda-durable-bedrock",
"version": "1.0.0",
"description": "Lambda Durable Functions with Amazon Bedrock for multi-step AI workflows",
"private": true,
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"cdk": "cdk"
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"organization": true
},
"license": "Apache-2.0",
"devDependencies": {
"@types/node": "^22.0.0",
"aws-cdk": "2.1119.0",
"typescript": "~5.7.0"
},
"dependencies": {
"aws-cdk-lib": "2.250.0",
"constructs": "^10.0.0"
}
}
44 changes: 44 additions & 0 deletions typescript/lambda-durable-bedrock/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const { withDurableExecution } = require("@aws/durable-execution-sdk-js");
const { BedrockRuntimeClient, InvokeModelCommand } = require("@aws-sdk/client-bedrock-runtime");

const client = new BedrockRuntimeClient();
const MODEL_ID = process.env.MODEL_ID;

async function callBedrock(prompt) {
const res = await client.send(
new InvokeModelCommand({
modelId: MODEL_ID,
contentType: "application/json",
accept: "application/json",
body: JSON.stringify({
anthropic_version: "bedrock-2023-05-31",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
})
);
return JSON.parse(new TextDecoder().decode(res.body)).content[0].text;
}

exports.handler = withDurableExecution(async (event, context) => {
const topic = event.topic || "Serverless computing";

const outline = await context.step(async (stepCtx) => {
stepCtx.logger.info(`Generating outline for: ${topic}`);
return callBedrock(`Create a concise blog post outline (5 sections max) about: ${topic}. Return only the outline.`);
});

await context.wait({ seconds: 5 });

const draft = await context.step(async (stepCtx) => {
stepCtx.logger.info("Expanding outline into draft");
return callBedrock(`Expand this outline into a short blog draft (under 500 words):\n\n${outline}`);
});

const summary = await context.step(async (stepCtx) => {
stepCtx.logger.info("Generating summary");
return callBedrock(`Summarize this blog post in 2-3 sentences:\n\n${draft}`);
});

return { topic, outline, draft, summary };
});
Loading