From f3beb1f99979f8ce25914c3d8fe4ee28511c0c00 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Wed, 24 Jun 2026 07:56:56 +0000 Subject: [PATCH] feat(code-interpreter): AI data analyst with Bedrock + Code Interpreter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deploy Amazon API Gateway + AWS Lambda that uses Amazon Bedrock to generate Python code from natural language questions, then executes it safely in Amazon Bedrock AgentCore Code Interpreter. Two-step composition: Bedrock reasons about what code to write, Code Interpreter provides sandboxed execution. Neither works alone — Bedrock can't run code, Code Interpreter can't decide what to run. --- .../.gitignore | 6 + .../bin/app.ts | 5 + .../cdk.json | 1 + .../example-pattern.json | 96 ++++++++++++++ ...w-lambda-bedrock-code-interpreter-stack.ts | 55 ++++++++ .../package.json | 1 + .../src/handler/index.py | 120 ++++++++++++++++++ .../tsconfig.json | 1 + 8 files changed, 285 insertions(+) create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/.gitignore create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/bin/app.ts create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/cdk.json create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/example-pattern.json create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/lib/apigw-lambda-bedrock-code-interpreter-stack.ts create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/package.json create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/src/handler/index.py create mode 100644 apigw-lambda-bedrock-code-interpreter-cdk/tsconfig.json diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/.gitignore b/apigw-lambda-bedrock-code-interpreter-cdk/.gitignore new file mode 100644 index 0000000000..888fdf2586 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +cdk.out/ +build/ +*.js +*.d.ts +cdk.context.json diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/bin/app.ts b/apigw-lambda-bedrock-code-interpreter-cdk/bin/app.ts new file mode 100644 index 0000000000..51f2e40b03 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/bin/app.ts @@ -0,0 +1,5 @@ +#!/usr/bin/env node +import * as cdk from 'aws-cdk-lib'; +import { ApigwLambdaBedrockCodeInterpreterStack } from '../lib/apigw-lambda-bedrock-code-interpreter-stack'; +const app = new cdk.App(); +new ApigwLambdaBedrockCodeInterpreterStack(app, 'ApigwLambdaBedrockCodeInterpreterStack', { env: { region: 'us-east-1' } }); diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/cdk.json b/apigw-lambda-bedrock-code-interpreter-cdk/cdk.json new file mode 100644 index 0000000000..d9a6c84524 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/cdk.json @@ -0,0 +1 @@ +{"app":"node build/bin/app.js"} diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/example-pattern.json b/apigw-lambda-bedrock-code-interpreter-cdk/example-pattern.json new file mode 100644 index 0000000000..7c55c371f0 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/example-pattern.json @@ -0,0 +1,96 @@ +{ + "title": "AI data analyst with Amazon Bedrock and Amazon Bedrock AgentCore Code Interpreter", + "description": "Deploy an Amazon API Gateway REST API backed by an AWS Lambda function that uses Amazon Bedrock to generate Python analysis code from natural language questions, then executes it safely in Amazon Bedrock AgentCore Code Interpreter.", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "A user submits a data analysis question in natural language via the Amazon API Gateway REST API.", + "The AWS Lambda function sends the question to Amazon Bedrock, which generates Python code to answer it.", + "The generated code is executed in Amazon Bedrock AgentCore Code Interpreter \u2014 a sandboxed environment with no access to the host system.", + "Results are returned to the user along with the generated code for transparency." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-lambda-bedrock-code-interpreter-cdk", + "templateURL": "serverless-patterns/apigw-lambda-bedrock-code-interpreter-cdk", + "projectFolder": "apigw-lambda-bedrock-code-interpreter-cdk", + "templateFile": "lib/apigw-lambda-bedrock-code-interpreter-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Bedrock AgentCore Code Interpreter documentation", + "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter.html" + }, + { + "text": "Amazon Bedrock InvokeModel API", + "link": "https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModel.html" + } + ] + }, + "deploy": { + "text": [ + "npx cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "npx cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ], + "patternArch": { + "icon1": { + "x": 10, + "y": 50, + "service": "apigateway", + "label": "Amazon API Gateway" + }, + "icon2": { + "x": 35, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "icon3": { + "x": 60, + "y": 30, + "service": "bedrock", + "label": "Amazon Bedrock" + }, + "icon4": { + "x": 60, + "y": 70, + "service": "bedrock", + "label": "Code Interpreter" + }, + "line1": { + "from": "icon1", + "to": "icon2" + }, + "line2": { + "from": "icon2", + "to": "icon3" + }, + "line3": { + "from": "icon2", + "to": "icon4" + } + } +} \ No newline at end of file diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/lib/apigw-lambda-bedrock-code-interpreter-stack.ts b/apigw-lambda-bedrock-code-interpreter-cdk/lib/apigw-lambda-bedrock-code-interpreter-stack.ts new file mode 100644 index 0000000000..2a0a0fe309 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/lib/apigw-lambda-bedrock-code-interpreter-stack.ts @@ -0,0 +1,55 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; + +export class ApigwLambdaBedrockCodeInterpreterStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Amazon Bedrock AgentCore Code Interpreter for safe code execution + const codeInterpreter = new agentcore.CodeInterpreterCustom(this, 'CodeInterpreter', { + codeInterpreterCustomName: 'data_analyst', + description: 'Sandboxed Python execution for AI-generated data analysis code', + networkConfiguration: agentcore.CodeInterpreterNetworkConfiguration.usingPublicNetwork(), + }); + + // AWS Lambda function: Bedrock generates code, Code Interpreter executes it + const fn = new lambda.Function(this, 'AnalystFunction', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset('src/handler'), + timeout: cdk.Duration.seconds(90), + environment: { + CODE_INTERPRETER_ID: (codeInterpreter.node.defaultChild as cdk.CfnResource).ref, + MODEL_ID: 'us.anthropic.claude-sonnet-4-20250514-v1:0', + }, + }); + + // Grant permissions for Amazon Bedrock AgentCore Code Interpreter + codeInterpreter.grantUse(fn); + + // Grant permission to invoke Amazon Bedrock models + fn.addToRolePolicy(new iam.PolicyStatement({ + actions: ['bedrock:InvokeModel'], + resources: [ + `arn:aws:bedrock:*::foundation-model/*`, + `arn:aws:bedrock:*:${this.account}:inference-profile/*`, + ], + })); + + // Amazon API Gateway REST API + const api = new apigateway.RestApi(this, 'AnalystApi', { + restApiName: 'AI Data Analyst', + description: 'Ask data questions in natural language — Amazon Bedrock writes Python, Amazon Bedrock AgentCore Code Interpreter executes it safely', + }); + + api.root.addResource('analyze').addMethod('POST', new apigateway.LambdaIntegration(fn)); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url }); + new cdk.CfnOutput(this, 'FunctionName', { value: fn.functionName }); + } +} diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/package.json b/apigw-lambda-bedrock-code-interpreter-cdk/package.json new file mode 100644 index 0000000000..7a0f949f47 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/package.json @@ -0,0 +1 @@ +{"name":"apigw-lambda-bedrock-code-interpreter-cdk","version":"1.0.0","bin":{"app":"build/bin/app.js"},"scripts":{"build":"tsc","synth":"cdk synth"},"dependencies":{"aws-cdk-lib":"^2.260.0","constructs":"^10.3.0"},"devDependencies":{"aws-cdk":"^2.1128.0","typescript":"~5.4.0"}} diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/src/handler/index.py b/apigw-lambda-bedrock-code-interpreter-cdk/src/handler/index.py new file mode 100644 index 0000000000..4c5c5df8a5 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/src/handler/index.py @@ -0,0 +1,120 @@ +""" +AWS Lambda function: AI Data Analyst. +Amazon Bedrock generates Python analysis code from natural language questions, +Amazon Bedrock AgentCore Code Interpreter executes it in a secure sandbox. +""" + +import json +import os +import boto3 + + +def handler(event, context): + """Generate and execute data analysis code from natural language.""" + try: + body = json.loads(event.get('body', '{}')) if isinstance(event.get('body'), str) else event + question = body.get('question', '') + data = body.get('data', '') + + if not question: + return { + 'statusCode': 400, + 'headers': {'Content-Type': 'application/json'}, + 'body': json.dumps({'error': 'question field is required'}), + } + + code_interpreter_id = os.environ['CODE_INTERPRETER_ID'] + model_id = os.environ.get('MODEL_ID', 'us.anthropic.claude-sonnet-4-20250514-v1:0') + region = os.environ.get('AWS_REGION', 'us-east-1') + + # Step 1: Use Amazon Bedrock to generate Python code for the question + bedrock_client = boto3.client('bedrock-runtime', region_name=region) + + code_gen_prompt = f"""Write a Python script that answers this data analysis question. +The script should print the answer clearly to stdout. +Use only standard library modules (json, math, statistics, datetime, collections, itertools, csv, re). +Do not use external packages like pandas or numpy. + +Question: {question} +{f"Data: {data}" if data else ""} + +Return ONLY the Python code, no explanation. No markdown code fences.""" + + response = bedrock_client.invoke_model( + modelId=model_id, contentType='application/json', accept='application/json', + body=json.dumps({ + 'anthropic_version': 'bedrock-2023-05-31', + 'max_tokens': 1024, + 'messages': [{'role': 'user', 'content': code_gen_prompt}], + }), + ) + + bedrock_body = json.loads(response['body'].read()) + generated_code = bedrock_body['content'][0]['text'].strip() + # Strip markdown fences if present + if generated_code.startswith('```'): + generated_code = '\n'.join(generated_code.split('\n')[1:-1]) + + # Step 2: Execute the generated code in Amazon Bedrock AgentCore Code Interpreter + agentcore_client = boto3.client('bedrock-agentcore', region_name=region) + + session = agentcore_client.start_code_interpreter_session( + codeInterpreterIdentifier=code_interpreter_id, + ) + session_id = session['sessionId'] + + exec_response = agentcore_client.invoke_code_interpreter( + codeInterpreterIdentifier=code_interpreter_id, + sessionId=session_id, + name='executeCode', + arguments={ + 'code': generated_code, + 'language': 'python', + }, + ) + + # Parse EventStream response + result_text = '' + error_text = '' + event_stream = exec_response.get('body', {}) + for event in event_stream: + if 'chunk' in event: + chunk = event['chunk'] + sc = chunk.get('structuredContent', {}) + if sc.get('stdout'): + result_text += sc['stdout'] + if sc.get('stderr'): + error_text += sc['stderr'] + elif 'result' in event: + r = event['result'] + sc = r.get('structuredContent', {}) + if sc.get('stdout'): + result_text += sc['stdout'] + if sc.get('stderr'): + error_text += sc['stderr'] + + stdout = result_text or 'Code executed successfully (no stdout output)' + stderr = error_text + + agentcore_client.stop_code_interpreter_session( + codeInterpreterIdentifier=code_interpreter_id, + sessionId=session_id, + ) + + return { + 'statusCode': 200, + 'headers': {'Content-Type': 'application/json'}, + 'body': json.dumps({ + 'question': question, + 'generatedCode': generated_code, + 'result': stdout, + 'errors': stderr if stderr else None, + }), + } + + except Exception as e: + return { + 'statusCode': 500, + 'headers': {'Content-Type': 'application/json'}, + 'body': json.dumps({'error': str(e)}), + } diff --git a/apigw-lambda-bedrock-code-interpreter-cdk/tsconfig.json b/apigw-lambda-bedrock-code-interpreter-cdk/tsconfig.json new file mode 100644 index 0000000000..addc9e6231 --- /dev/null +++ b/apigw-lambda-bedrock-code-interpreter-cdk/tsconfig.json @@ -0,0 +1 @@ +{"compilerOptions":{"target":"ES2022","module":"commonjs","lib":["es2022"],"declaration":true,"strict":true,"noImplicitAny":true,"noImplicitReturns":true,"inlineSourceMap":true,"inlineSources":true,"experimentalDecorators":true,"strictPropertyInitialization":false,"outDir":"./build","rootDir":".","skipLibCheck":true},"exclude":["node_modules","cdk.out","build"]}