diff --git a/lambda-bedrock-prompt-optimization-cdk/.gitignore b/lambda-bedrock-prompt-optimization-cdk/.gitignore new file mode 100644 index 000000000..8eccb2789 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +build/ +cdk.out/ +*.js +*.d.ts diff --git a/lambda-bedrock-prompt-optimization-cdk/README.md b/lambda-bedrock-prompt-optimization-cdk/README.md new file mode 100644 index 000000000..3ef77c068 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/README.md @@ -0,0 +1,95 @@ +--- + +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 + +--- + +# Lambda to Bedrock Advanced Prompt Optimization (CDK) + +This pattern deploys a Lambda function that creates an Amazon Bedrock Advanced Prompt Optimization job to automatically optimize prompts for your target model(s). The function uploads evaluation data to S3, submits the optimization job, polls for completion, and returns results. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-bedrock-prompt-optimization-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/) 20.x or later +- [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for your target model (Anthropic Claude Sonnet is the default) + +## 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-prompt-optimization-cdk + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Deploy the stack: + ```bash + cdk deploy + ``` + +## How it works + +1. The Lambda function receives a prompt template and evaluation samples (or uses defaults). +2. It uploads the input data as JSONL to an S3 bucket. +3. It calls `CreateAdvancedPromptOptimizationJob` with the input S3 URI, output S3 URI, and target model configuration. +4. The service runs iterative inference-evaluate-rewrite loops to optimize the prompt. +5. The Lambda polls `GetAdvancedPromptOptimizationJob` every 10 seconds until completion. +6. Results (optimized prompts with evaluation metrics) are written to S3 and returned in the response. + +## Architecture + +``` +┌──────────┐ ┌─────────────────┐ ┌────────┐ +│ Lambda │────────▶│ Bedrock AdvPO │────────▶│ S3 │ +│ Function │◀────────│ (Optimization) │ │ Bucket │ +└──────────┘ poll └─────────────────┘ └────────┘ +``` + +## Testing + +Invoke the Lambda function with a test event: + +```bash +aws lambda invoke \ + --function-name \ + --payload '{"promptTemplate": "Answer concisely: {{question}}", "modelId": "us.anthropic.claude-sonnet-4-20250514-v1:0"}' \ + --cli-binary-format raw-in-base64-out \ + output.json + +cat output.json +``` + +Or use the default prompt template and samples: + +```bash +aws lambda invoke \ + --function-name \ + --payload '{}' \ + --cli-binary-format raw-in-base64-out \ + output.json +``` + +## Cleanup + +```bash +cdk destroy +``` + +--- + +## Author + +- Nithin Chandran R - [LinkedIn](https://www.linkedin.com/in/nithin-chandran-r/) diff --git a/lambda-bedrock-prompt-optimization-cdk/bin/app.ts b/lambda-bedrock-prompt-optimization-cdk/bin/app.ts new file mode 100644 index 000000000..551d2f013 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/bin/app.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { LambdaBedrockPromptOptimizationStack } from '../lib/lambda-bedrock-prompt-optimization-stack'; + +const app = new cdk.App(); +new LambdaBedrockPromptOptimizationStack(app, 'LambdaBedrockPromptOptimizationStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); diff --git a/lambda-bedrock-prompt-optimization-cdk/cdk.json b/lambda-bedrock-prompt-optimization-cdk/cdk.json new file mode 100644 index 000000000..6f354a35c --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/cdk.json @@ -0,0 +1,7 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts", + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:stackRelativeExports": true + } +} diff --git a/lambda-bedrock-prompt-optimization-cdk/example-pattern.json b/lambda-bedrock-prompt-optimization-cdk/example-pattern.json new file mode 100644 index 000000000..a89e937c7 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/example-pattern.json @@ -0,0 +1,56 @@ +{ + "title": "Lambda to Bedrock Advanced Prompt Optimization", + "description": "Optimize prompts across multiple models using Amazon Bedrock Advanced Prompt Optimization, triggered by AWS Lambda. Deployed with AWS CDK.", + "language": "Python", + "level": "200", + "framework": "CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys a Lambda function that creates an Amazon Bedrock Advanced Prompt Optimization job.", + "The job takes a prompt template with evaluation samples, optimizes it for your target model(s), and writes results to S3.", + "The Lambda function polls the job status and returns the optimization results, enabling automated prompt engineering pipelines." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-bedrock-prompt-optimization-cdk", + "templateURL": "serverless-patterns/lambda-bedrock-prompt-optimization-cdk", + "projectFolder": "lambda-bedrock-prompt-optimization-cdk", + "templateFile": "lib/lambda-bedrock-prompt-optimization-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Bedrock Advanced Prompt Optimization", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/advanced-prompt-optimization-jobs.html" + }, + { + "text": "Bedrock Advanced Prompt Optimization Prerequisites", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/advanced-prompt-optimization-prereqs.html" + } + ] + }, + "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": "AWS Lambda" }, + "icon2": { "x": 80, "y": 50, "service": "bedrock", "label": "Amazon Bedrock" }, + "line1": { "from": "icon1", "to": "icon2" } + } +} diff --git a/lambda-bedrock-prompt-optimization-cdk/lib/lambda-bedrock-prompt-optimization-stack.ts b/lambda-bedrock-prompt-optimization-cdk/lib/lambda-bedrock-prompt-optimization-stack.ts new file mode 100644 index 000000000..9a7d59396 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/lib/lambda-bedrock-prompt-optimization-stack.ts @@ -0,0 +1,64 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as s3 from 'aws-cdk-lib/aws-s3'; + +export class LambdaBedrockPromptOptimizationStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const bucket = new s3.Bucket(this, 'PromptOptBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + const fn = new lambda.Function(this, 'PromptOptFunction', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset('src/lambda', { + 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.minutes(15), + memorySize: 256, + environment: { + BUCKET_NAME: bucket.bucketName, + }, + }); + + bucket.grantReadWrite(fn); + + fn.addToRolePolicy(new iam.PolicyStatement({ + actions: [ + 'bedrock:CreateAdvancedPromptOptimizationJob', + 'bedrock:GetAdvancedPromptOptimizationJob', + 'bedrock:ListAdvancedPromptOptimizationJobs', + ], + resources: [ + `arn:aws:bedrock:${this.region}:${this.account}:advanced-prompt-optimization-job/*`, + `arn:aws:bedrock:*:${this.account}:inference-profile/*`, + 'arn:aws:bedrock:*::foundation-model/*', + ], + })); + + fn.addToRolePolicy(new iam.PolicyStatement({ + actions: [ + 'bedrock:InvokeModel', + 'bedrock:InvokeModelWithResponseStream', + ], + resources: [ + 'arn:aws:bedrock:*::foundation-model/*', + `arn:aws:bedrock:*:${this.account}:inference-profile/*`, + ], + })); + + new cdk.CfnOutput(this, 'FunctionName', { value: fn.functionName }); + new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName }); + } +} diff --git a/lambda-bedrock-prompt-optimization-cdk/package.json b/lambda-bedrock-prompt-optimization-cdk/package.json new file mode 100644 index 000000000..73061d3c8 --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "lambda-bedrock-prompt-optimization-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": { + "@types/node": "^20.0.0", + "typescript": "~5.4.0", + "aws-cdk": "^2.170.0", + "ts-node": "^10.9.0" + } +} diff --git a/lambda-bedrock-prompt-optimization-cdk/src/lambda/index.py b/lambda-bedrock-prompt-optimization-cdk/src/lambda/index.py new file mode 100644 index 000000000..cea303a7b --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/src/lambda/index.py @@ -0,0 +1,63 @@ +import json +import os +import time +import boto3 + +bedrock = boto3.client('bedrock') +s3 = boto3.client('s3') + +BUCKET = os.environ['BUCKET_NAME'] + + +def handler(event, context): + """Create an Advanced Prompt Optimization job, poll until complete, return results.""" + prompt_template = event.get('promptTemplate', 'You are a helpful assistant. Answer the following question concisely and accurately: {{question}}') + template_id = event.get('templateId', 'qa-template-v1') + model_id = event.get('modelId', 'us.anthropic.claude-sonnet-4-20250514-v1:0') + samples = event.get('evaluationSamples', [ + {'inputVariables': [{'question': 'What is cloud computing?'}], 'referenceResponse': 'Cloud computing is the on-demand delivery of IT resources over the Internet with pay-as-you-go pricing.'}, + {'inputVariables': [{'question': 'What is serverless?'}], 'referenceResponse': 'Serverless is a cloud execution model where the provider manages infrastructure, scaling automatically and charging only for actual usage.'}, + {'inputVariables': [{'question': 'What is Amazon Bedrock?'}], 'referenceResponse': 'Amazon Bedrock is a fully managed service that offers foundation models from leading AI companies through a single API.'}, + ]) + + # Build JSONL input (one line per template) + input_entry = { + 'version': 'bedrock-2026-05-14', + 'templateId': template_id, + 'promptTemplate': prompt_template, + 'steeringCriteria': ['ACCURATE', 'CONCISE'], + 'evaluationSamples': samples, + } + input_key = f'input/{context.aws_request_id}.jsonl' + s3.put_object(Bucket=BUCKET, Key=input_key, Body=json.dumps(input_entry)) + + # Create optimization job + job = bedrock.create_advanced_prompt_optimization_job( + jobName=f'opt-{context.aws_request_id[:8]}', + inputConfig={'s3Uri': f's3://{BUCKET}/{input_key}'}, + outputConfig={'s3Uri': f's3://{BUCKET}/output/{context.aws_request_id}/'}, + modelConfigurations=[{'modelId': model_id}], + ) + job_arn = job['jobArn'] + + # Poll for completion (max ~14 min with 15 min Lambda timeout) + for _ in range(84): + time.sleep(10) + status = bedrock.get_advanced_prompt_optimization_job(jobIdentifier=job_arn) + state = status.get('jobStatus', status.get('status', 'UNKNOWN')) + if state in ('COMPLETED', 'SUCCEEDED'): + return { + 'statusCode': 200, + 'jobArn': job_arn, + 'status': state, + 'outputUri': f's3://{BUCKET}/output/{context.aws_request_id}/', + } + if state in ('FAILED', 'STOPPED'): + return { + 'statusCode': 500, + 'jobArn': job_arn, + 'status': state, + 'failureMessage': status.get('failureMessage', 'Unknown error'), + } + + return {'statusCode': 408, 'jobArn': job_arn, 'status': 'TIMEOUT', 'message': 'Job did not complete within Lambda timeout'} diff --git a/lambda-bedrock-prompt-optimization-cdk/src/lambda/requirements.txt b/lambda-bedrock-prompt-optimization-cdk/src/lambda/requirements.txt new file mode 100644 index 000000000..722386c8c --- /dev/null +++ b/lambda-bedrock-prompt-optimization-cdk/src/lambda/requirements.txt @@ -0,0 +1 @@ +boto3>=1.42.0 diff --git a/lambda-bedrock-prompt-optimization-cdk/tsconfig.json b/lambda-bedrock-prompt-optimization-cdk/tsconfig.json new file mode 100644 index 000000000..343a8577c --- /dev/null +++ b/lambda-bedrock-prompt-optimization-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, + "outDir": "./build", + "rootDir": ".", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["node_modules", "build"] +}