From 50bbbfc26d6034d4bff5b0acacd00890f075edbc Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Wed, 10 Jun 2026 13:52:42 +0000 Subject: [PATCH] feat(lambda-bedrock-mantle-responses-api-cdk): Add OpenAI-compatible Bedrock Mantle Responses API pattern --- .../.gitignore | 4 + .../README.md | 96 +++++++++++++++++++ .../bin/app.ts | 16 ++++ .../cdk.json | 3 + .../example-pattern.json | 46 +++++++++ .../lib/stack.ts | 50 ++++++++++ .../package.json | 21 ++++ .../src/index.py | 33 +++++++ .../src/requirements.txt | 1 + .../tsconfig.json | 18 ++++ 10 files changed, 288 insertions(+) create mode 100644 lambda-bedrock-mantle-responses-api-cdk/.gitignore create mode 100644 lambda-bedrock-mantle-responses-api-cdk/README.md create mode 100644 lambda-bedrock-mantle-responses-api-cdk/bin/app.ts create mode 100644 lambda-bedrock-mantle-responses-api-cdk/cdk.json create mode 100644 lambda-bedrock-mantle-responses-api-cdk/example-pattern.json create mode 100644 lambda-bedrock-mantle-responses-api-cdk/lib/stack.ts create mode 100644 lambda-bedrock-mantle-responses-api-cdk/package.json create mode 100644 lambda-bedrock-mantle-responses-api-cdk/src/index.py create mode 100644 lambda-bedrock-mantle-responses-api-cdk/src/requirements.txt create mode 100644 lambda-bedrock-mantle-responses-api-cdk/tsconfig.json diff --git a/lambda-bedrock-mantle-responses-api-cdk/.gitignore b/lambda-bedrock-mantle-responses-api-cdk/.gitignore new file mode 100644 index 000000000..9d1bc4074 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/.gitignore @@ -0,0 +1,4 @@ +node_modules +cdk.out +*.js +*.d.ts diff --git a/lambda-bedrock-mantle-responses-api-cdk/README.md b/lambda-bedrock-mantle-responses-api-cdk/README.md new file mode 100644 index 000000000..f59eb126f --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/README.md @@ -0,0 +1,96 @@ +# Amazon Bedrock Mantle Responses API with AWS Lambda (OpenAI SDK Compatible) + +This pattern deploys an API Gateway REST API backed by an AWS Lambda function that calls Amazon Bedrock via the OpenAI-compatible Responses API (bedrock-mantle endpoint) using the standard OpenAI Python SDK. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-bedrock-mantle-responses-api-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 +- [Node.js 20+](https://nodejs.org/en/download/) installed +- [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed (`npm install -g aws-cdk`) +- [Python 3.12](https://www.python.org/downloads/) installed (for Lambda bundling) +- [Docker](https://docs.docker.com/get-docker/) installed (for CDK asset bundling) +- A Bedrock API key (see below) + +## Generating a Bedrock API Key + +1. Open the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/) +2. Navigate to **API keys** in the left navigation +3. Click **Create API key** +4. Copy the generated key — you will pass it as a parameter during deployment + +## Deployment Instructions + +1. Create a new directory, navigate to the directory, and clone the repository: + ```bash + git clone https://github.com/aws-samples/serverless-patterns + cd serverless-patterns/lambda-bedrock-mantle-responses-api-cdk + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Bootstrap CDK (if not already done): + ```bash + cdk bootstrap + ``` + +4. Deploy the stack, providing your Bedrock API key: + ```bash + cdk deploy --parameters BedrockApiKey=YOUR_BEDROCK_API_KEY + ``` + +5. Note the `ApiEndpoint` output URL. + +## How it works + +- API Gateway receives a POST request at `/ask` +- Lambda is invoked with the request body +- The Lambda function uses the OpenAI Python SDK configured to point to `https://bedrock-mantle..api.aws/v1` +- Authentication is handled via the Bedrock API key (no `bedrock:InvokeModel` IAM permission needed) +- The Responses API response is returned to the caller + +## Available Regions + +The bedrock-mantle endpoint is available in: `us-east-1`, `us-east-2`, `us-west-2`, and other regions. + +## Testing + +After deployment, send a POST request to the API endpoint: + +```bash +curl -X POST https://.execute-api..amazonaws.com/prod/ask \ + -H "Content-Type: application/json" \ + -d '{"prompt": "What is Amazon Bedrock?", "model": "us.anthropic.claude-sonnet-4-20250514-v1:0"}' +``` + +Expected response: +```json +{ + "response": "Amazon Bedrock is a fully managed service...", + "model": "us.anthropic.claude-sonnet-4-20250514-v1:0", + "id": "resp_..." +} +``` + +## Cleanup + +To delete the stack and all associated resources: + +```bash +cdk destroy +``` + +> **Warning:** This will permanently delete all resources created by this stack. The removal policy is set to DESTROY. + +## Architecture + +``` +Client -> API Gateway (POST /ask) -> Lambda (Python 3.12 + OpenAI SDK) -> Bedrock Mantle Responses API +``` diff --git a/lambda-bedrock-mantle-responses-api-cdk/bin/app.ts b/lambda-bedrock-mantle-responses-api-cdk/bin/app.ts new file mode 100644 index 000000000..3b6d06888 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-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 { LambdaBedrockMantleStack } from '../lib/stack'; + +if (!process.env.CDK_DEFAULT_ACCOUNT) { + throw new Error('CDK_DEFAULT_ACCOUNT environment variable is required. Run: aws sts get-caller-identity'); +} + +const app = new cdk.App(); +new LambdaBedrockMantleStack(app, 'LambdaBedrockMantleResponsesApiStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION || 'us-east-1', + }, +}); diff --git a/lambda-bedrock-mantle-responses-api-cdk/cdk.json b/lambda-bedrock-mantle-responses-api-cdk/cdk.json new file mode 100644 index 000000000..a6700a2ff --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/lambda-bedrock-mantle-responses-api-cdk/example-pattern.json b/lambda-bedrock-mantle-responses-api-cdk/example-pattern.json new file mode 100644 index 000000000..90e7c1866 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/example-pattern.json @@ -0,0 +1,46 @@ +{ + "title": "Amazon Bedrock Mantle Responses API with AWS Lambda (OpenAI SDK Compatible)", + "description": "Call Amazon Bedrock via the OpenAI-compatible Responses API from AWS Lambda using the standard OpenAI Python SDK.", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys an API Gateway REST API that triggers a Lambda function.", + "The Lambda function uses the OpenAI Python SDK to call Amazon Bedrock via the bedrock-mantle Responses API endpoint.", + "Authentication uses a Bedrock API key passed as a CloudFormation parameter at deploy time." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-bedrock-mantle-responses-api-cdk", + "templateURL": "serverless-patterns/lambda-bedrock-mantle-responses-api-cdk", + "projectFolder": "lambda-bedrock-mantle-responses-api-cdk" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Bedrock Documentation", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/" + }, + { + "text": "OpenAI Python SDK", + "link": "https://github.com/openai/openai-python" + } + ] + }, + "deploy": { + "text": ["cdk deploy --parameters BedrockApiKey=YOUR_API_KEY"] + }, + "cleanup": { + "text": ["cdk destroy"] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Cloud Engineer at AWS" + } + ] +} diff --git a/lambda-bedrock-mantle-responses-api-cdk/lib/stack.ts b/lambda-bedrock-mantle-responses-api-cdk/lib/stack.ts new file mode 100644 index 000000000..696a888b6 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/lib/stack.ts @@ -0,0 +1,50 @@ +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 { Construct } from 'constructs'; + +export class LambdaBedrockMantleStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const apiKey = new cdk.CfnParameter(this, 'BedrockApiKey', { + type: 'String', + description: 'Bedrock API key generated from the AWS console', + noEcho: true, + }); + + const fn = new lambda.Function(this, 'ResponsesApiFn', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset('src', { + bundling: { + image: lambda.Runtime.PYTHON_3_12.bundlingImage, + command: [ + 'bash', '-c', + 'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output', + ], + }, + }), + timeout: cdk.Duration.seconds(60), + memorySize: 256, + environment: { + OPENAI_BASE_URL: `https://bedrock-mantle.${this.region}.api.aws/v1`, + OPENAI_API_KEY: apiKey.valueAsString, + }, + }); + + const api = new apigateway.RestApi(this, 'ResponsesApi', { + restApiName: 'Bedrock Mantle Responses API', + }); + + api.root.addResource('ask').addMethod( + 'POST', + new apigateway.LambdaIntegration(fn), + ); + + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url + 'ask', + description: 'API Gateway endpoint URL', + }); + } +} diff --git a/lambda-bedrock-mantle-responses-api-cdk/package.json b/lambda-bedrock-mantle-responses-api-cdk/package.json new file mode 100644 index 000000000..b22906cf9 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/package.json @@ -0,0 +1,21 @@ +{ + "name": "lambda-bedrock-mantle-responses-api-cdk", + "version": "1.0.0", + "bin": { + "app": "bin/app.ts" + }, + "scripts": { + "build": "tsc", + "cdk": "cdk" + }, + "dependencies": { + "aws-cdk-lib": "^2.170.0", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "devDependencies": { + "typescript": "~5.4.0", + "ts-node": "^10.9.0", + "@types/node": "^20.0.0" + } +} diff --git a/lambda-bedrock-mantle-responses-api-cdk/src/index.py b/lambda-bedrock-mantle-responses-api-cdk/src/index.py new file mode 100644 index 000000000..d7a0eaabc --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/src/index.py @@ -0,0 +1,33 @@ +import os +import json +from openai import OpenAI + +client = OpenAI( + base_url=os.environ['OPENAI_BASE_URL'], + api_key=os.environ['OPENAI_API_KEY'], +) + + +def handler(event, context): + body = json.loads(event.get('body', '{}')) + prompt = body.get('prompt', 'What is Amazon Bedrock?') + model = body.get('model', 'openai.gpt-oss-120b') + + try: + response = client.responses.create( + model=model, + input=[{'role': 'user', 'content': prompt}], + ) + return { + 'statusCode': 200, + 'body': json.dumps({ + 'response': response.output_text, + 'model': model, + 'id': response.id, + }), + } + except Exception as e: + return { + 'statusCode': 500, + 'body': json.dumps({'error': str(e)}), + } diff --git a/lambda-bedrock-mantle-responses-api-cdk/src/requirements.txt b/lambda-bedrock-mantle-responses-api-cdk/src/requirements.txt new file mode 100644 index 000000000..ec838c5a8 --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/src/requirements.txt @@ -0,0 +1 @@ +openai diff --git a/lambda-bedrock-mantle-responses-api-cdk/tsconfig.json b/lambda-bedrock-mantle-responses-api-cdk/tsconfig.json new file mode 100644 index 000000000..7b9d45a0c --- /dev/null +++ b/lambda-bedrock-mantle-responses-api-cdk/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "./cdk.out", + "skipLibCheck": true + }, + "exclude": ["node_modules", "cdk.out"] +}