1
0
mirror of synced 2026-05-22 22:53:35 +00:00

(02-usecases)Amazon Bedrock AgentCore Deployment with CDK (#924)

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

* Amazon Bedrock AgentCore Deployment with CDK

---------

Co-authored-by: Uriel Ramirez <beralfon@amazon.com>
This commit is contained in:
Uriel Ramirez
2026-02-05 08:07:44 -06:00
committed by GitHub
parent 8d64290d76
commit 43f25348f0
35 changed files with 620 additions and 548 deletions
@@ -35,31 +35,30 @@ The following architecture diagram illustrates a reference solution for a genera
> [!IMPORTANT]
> This sample application is meant for demo purposes and is not production ready. Please make sure to validate the code with your organizations security best practices.
### AgentCore Runtime & Memory Infrastructure
**Amazon Bedrock AgentCore** is a fully managed service that enables you to deploy, run, and scale your custom agent applications with built-in runtime and memory capabilities.
- **[Amazon Bedrock AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html)**: Provides the managed execution environment with invocation endpoints (`/invocations`) and health monitoring (`/ping`) for your agent instances
- **[Amazon Bedrock AgentCore Memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html)**: A fully managed service that gives AI agents the ability to remember, learn, and evolve through interactions by capturing events, transforming them into memories, and retrieving relevant context when needed
The AgentCore infrastructure handles all storage complexity and provides efficient retrieval without requiring developers to manage underlying infrastructure, ensuring continuity and traceability across agent interactions.
### CDK Infrastructure Deployment
The AWS CDK stack deploys and configures the following managed services:
- **IAM AgentCore Execution Role**: Provides necessary permissions for Amazon Bedrock AgentCore execution
- **VPC and Private Subnet**: Network isolation and security for database resources
- **Amazon Aurora Serverless PostgreSQL**: Stores the video game sales data with RDS Data API integration
**Amazon Bedrock AgentCore Resources:**
- **[AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html)**: Provides the managed execution environment with invocation endpoints (`/invocations`) and health monitoring (`/ping`) for your agent instances
- **[AgentCore Memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html)**: A fully managed service that gives AI agents the ability to remember, learn, and evolve through interactions by capturing events, transforming them into memories, and retrieving relevant context when needed
The AgentCore infrastructure handles all storage complexity and provides efficient retrieval without requiring developers to manage underlying infrastructure, ensuring continuity and traceability across agent interactions.
**Data Source and VPC Infrastructure:**
- **VPC with Public and Private Subnets**: Network isolation and security for database resources
- **Amazon Aurora Serverless v2 PostgreSQL**: Stores the video game sales data with RDS Data API integration
- **Amazon DynamoDB**: Stores raw query results for data analysis audit trails
- **Parameter Store Configuration Management**: Securely manages application configuration
- **AWS Secrets Manager**: Secure storage for database credentials
- **Amazon S3**: Import bucket for loading data into Aurora PostgreSQL
- **SSM Parameter Store**: Configuration management for AgentCore runtime parameters
### Amplify Deployment for the Front-End Application
- **React Web Application**: Delivers the user interface for the assistant
- Uses Amazon Cognito for user authentication and permissions management
- The application invokes the Amazon Bedrock AgentCore for interacting with the assistant
- For chart generation, the application directly invokes the Claude 3.7 Sonnet model
- For chart generation, the application directly invokes the Claude Haiku 4.5 model
### Strands Agent Features
@@ -81,18 +80,17 @@ The AWS CDK stack deploys and configures the following managed services:
The **user interaction workflow** operates as follows:
- The web application sends user business questions to the AgentCore Invoke
- The Strands Agent (powered by Claude 3.7 Sonnet) processes natural language and determines when to execute database queries
- The Strands Agent (powered by Claude Haiku 4.5) processes natural language and determines when to execute database queries
- The agent's built-in tools execute SQL queries against the Aurora PostgreSQL database and formulate an answer to the question
- AgentCore Memory captures session interactions and retrieves previous conversations for context
- After the agent's response is received by the web application, the raw data query results are retrieved from the DynamoDB table to display both the answer and the corresponding records
- For chart generation, the application invokes a model (powered by Claude 3.7 Sonnet) to analyze the agent's answer and raw data query results to generate the necessary data to render an appropriate chart visualization
- For chart generation, the application invokes a model (powered by Claude Haiku 4.5) to analyze the agent's answer and raw data query results to generate the necessary data to render an appropriate chart visualization
## Deployment Instructions
The deployment consists of two main steps:
1. **Back-End Deployment - [Data Source and Configuration Management Deployment with CDK](./cdk-agentcore-strands-data-analyst-assistant/)**
1. **Agent Deployment - [Strands Agent Infrastructure Deployment with AgentCore](./agentcore-strands-data-analyst-assistant/)**
1. **Back-End Deployment - [Amazon Bedrock AgentCore and Data Source Deployment with CDK](./cdk-data-analyst-assistant-agentcore-strands/)**
2. **Front-End Implementation - [Integrating AgentCore with a Ready-to-Use Data Analyst Assistant Application](./amplify-video-games-sales-assistant-agentcore-strands/)**
> [!NOTE]
@@ -1,180 +0,0 @@
# Agent Deployment - Strands Agent Infrastructure Deployment with AgentCore
Deploy the Strands Agent Data Analyst Assistant for Video Game Sales using **[Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/)**'s fully managed service for scalable agent applications with **Runtime** and **Memory** capabilities.
> [!NOTE]
> **Working Directory**: Make sure you are in the `agentcore-strands-data-analyst-assistant/` folder before starting this tutorial. All commands in this guide should be executed from this directory.
## Overview
This tutorial guides you through deploying a video game sales data analyst agent using Amazon Bedrock AgentCore's managed infrastructure, includes the following modular services:
- **[Amazon Bedrock AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html)**: Provides the managed execution environment with invocation endpoints (`/invocations`) and health monitoring (`/ping`) for your agent instances
- **[Amazon Bedrock AgentCore Memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html)**: A fully managed service that gives AI agents the ability to remember, learn, and evolve through interactions by capturing events, transforming them into memories, and retrieving relevant context when needed
Don't forget to review the **[Amazon Bedrock AgentCore documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html)**.
> [!IMPORTANT]
> This sample application is meant for demo purposes and is not production ready. Please make sure to validate the code with your organizations security best practices.
>
> Remember to clean up resources after testing to avoid unnecessary costs by following the clean-up steps provided.
## Environment Setup and Requirements
Before you begin, ensure you have:
* **[Back-End Deployment - Data Source and Configuration Management Deployment with CDK](../cdk-agentcore-strands-data-analyst-assistant)**
* **[Docker](https://www.docker.com)**
* **Required Packages**:
* Install the Amazon Bedrock AgentCore CLI:
```bash
pip install bedrock-agentcore
```
* Install all project dependencies:
```bash
pip install -r requirements.txt
```
## Create Short-Term AgentCore Memory
Before deploying your agent, you need to create a **[short-term memory](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/short-term-memory.html)** store that will help your agent maintain conversation context:
1. Create a memory store with a 7-day default expiry period:
```bash
python3 resources/memory_manager.py create DataAnalystAssistantMemory ${MEMORY_ID_SSM_PARAMETER}
```
2. List all available memory stores to validate that your memory store was created successfully:
```bash
python3 resources/memory_manager.py list
```
This memory store enables your agent to remember previous interactions within the same session, providing a more coherent and contextual conversation experience.
## Local Testing
Before deploying to AWS, you can test the Data Analyst Agent locally to verify functionality:
1. Set the required environment variable and start the local agent server:
```bash
export PROJECT_ID="agentcore-data-analyst-assistant"
python3 app.py
```
This launches a local server on port 8080 that simulates the AgentCore runtime environment.
2. In a different terminal, create a session ID for conversation tracking:
```bash
export SESSION_ID=$(uuidgen)
```
3. Test the agent with example queries using curl:
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello world!", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "what is the structure of your data available?!", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Which developers tend to get the best reviews?", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Give me a summary of our conversation", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
## Deploy the Strands Agent with Amazon Bedrock AgentCore
Deploy your agent to AWS with these simple steps:
1. Configure the agent deployment accepting default values when prompted:
```bash
agentcore configure \
--entrypoint app.py \
--name agentcoredataanalystassistant \
-er $AGENT_CORE_ROLE_EXECUTION \
--disable-memory \
--deployment-type container
```
2. Launch the agent infrastructure:
```bash
agentcore launch --env PROJECT_ID="agentcore-data-analyst-assistant"
```
## Testing the Deployed Agent
Create a session ID for conversation tracking and test your deployed agent:
```bash
export SESSION_ID=$(uuidgen)
```
Test with these example queries:
```bash
agentcore invoke '{
"prompt": "Hello world!",
"session_id": "'$SESSION_ID'",
"last_k_turns": 20
}'
```
```bash
agentcore invoke '{
"prompt": "what is the structure of your data available?!",
"session_id": "'$SESSION_ID'",
"last_k_turns": 20
}'
```
```bash
agentcore invoke '{
"prompt": "Which developers tend to get the best reviews?",
"session_id": "'$SESSION_ID'",
"last_k_turns": 20
}'
```
```bash
agentcore invoke '{
"prompt": "Give me a summary of our conversation",
"session_id": "'$SESSION_ID'",
"last_k_turns": 20
}'
```
**Expected Behavior**: The agent responds as "Gus," a video game sales data analyst assistant who provides information about the video_games_sales_units database (64,016 game titles from 1971-2024), analyzes developer review scores, and maintains conversation context across interactions.
## Next Step
You can now proceed to the **[Front-End Implementation - Integrating AgentCore with a Ready-to-Use Data Analyst Assistant Application](../amplify-video-games-sales-assistant-agentcore-strands/))**.
## Cleaning-up Resources (Optional)
To avoid unnecessary charges, delete the AgentCore run environment from the AWS Console.
## Thank You
## License
This project is licensed under the Apache-2.0 License.
@@ -15,7 +15,7 @@ The application consists of two main components:
- **Amazon Bedrock AgentCore Integration:**:
- Uses your AgentCore deployment for data analysis and natural language processing
- The application invokes the Amazon Bedrock AgentCore for interacting with the assistant
- Directly invokes Claude 3.7 Sonnet model for chart generation and visualization
- Directly invokes Claude Haiku 4.5 model for chart generation and visualization
> [!IMPORTANT]
> This sample application is for demonstration purposes only and is not production-ready. Please validate the code against your organization's security best practices.
@@ -100,28 +100,48 @@ amplify push
> [!NOTE]
> This creates a Cognito User Pool and Identity Pool in your AWS account for user authentication. AWS credentials for the Front-End Application are automatically managed through Cognito.
## Get CDK Output Values
Get the required values from your CDK project outputs. These values are needed for configuring AuthRole permissions and environment variables:
``` bash
# Set the stack name environment variable
export STACK_NAME=CdkDataAnalystAssistantAgentcoreStrandsStack
# Get the values from CDK outputs
export QUESTION_ANSWERS_TABLE_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableName'].OutputValue" --output text)
export QUESTION_ANSWERS_TABLE_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableArn'].OutputValue" --output text)
export AGENT_RUNTIME_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AgentRuntimeArn'].OutputValue" --output text)
export AGENT_ENDPOINT_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AgentEndpointName'].OutputValue" --output text)
cat << EOF
# DynamoDB Resources
QUESTION_ANSWERS_TABLE_NAME: ${QUESTION_ANSWERS_TABLE_NAME}
QUESTION_ANSWERS_TABLE_ARN: ${QUESTION_ANSWERS_TABLE_ARN}
# AgentCore Resources
AGENT_RUNTIME_ARN: ${AGENT_RUNTIME_ARN}
AGENT_ENDPOINT_NAME: ${AGENT_ENDPOINT_NAME}
EOF
```
## Configure AuthRole Permissions
After authentication deployment, you need to grant your authenticated users permission to access AWS services.
1. **Find your AuthRole**: Go to AWS Console → IAM → Roles → Search for amplify-daabedrockagentcore-dev-*-authRole
1. **Find your AuthRole**: Go to AWS Console → IAM → Roles → Search for `amplify-daabedrockagentcore-dev-*-authRole`
2. **Get the DynamoDB Table ARN**: From your CDK project outputs, get the `QuestionAnswersTableName` value:
2. **Add an inline policy**: Click on the role → **Add permissions** → **Create inline policy** → Select **JSON** tab
``` bash
# Set the stack name environment variable
export STACK_NAME=CdkAgentcoreStrandsDataAnalystAssistantStack
3. **Copy the policy below** and replace the following placeholders with your actual values:
# Get the DynamoDB table name and construct the ARN
export QUESTION_ANSWERS_TABLE_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableName'].OutputValue" --output text)
export QUESTION_ANSWERS_TABLE_ARN="arn:aws:dynamodb:$(aws configure get region):$(aws sts get-caller-identity --query Account --output text):table/$QUESTION_ANSWERS_TABLE_NAME"
echo "Table ARN: $QUESTION_ANSWERS_TABLE_ARN"
```
| Placeholder | Replace With | Example |
|-------------|--------------|---------|
| `<account_id>` | Your AWS Account ID (12-digit number) | `123456789012` |
| `<question_answers_table_arn>` | `QUESTION_ANSWERS_TABLE_ARN` from CDK outputs above | `arn:aws:dynamodb:us-east-1:123456789012:table/QuestionAnswers-xxx` |
| `<agent_runtime_arn>` | `AGENT_RUNTIME_ARN` from CDK outputs above | `arn:aws:bedrock-agentcore:us-east-1:123456789012:runtime/data-analyst-xxx` |
3. **Add this policy** (replace `<account_id>` with your AWS account ID, `<question_answers_table_arn>` with the ARN from step 2, and `<agent_arn>` with your AgentCore runtime ARN):
> [!NOTE]
> The AgentCore runtime ARN has been pre-configured based on your current deployment. If you're using a different AgentCore runtime, update the ARN in the BedrockAgentCorePermissions section accordingly.
**Policy to copy (replace placeholders):**
``` json
{
@@ -153,14 +173,16 @@ echo "Table ARN: $QUESTION_ANSWERS_TABLE_ARN"
"Effect": "Allow",
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": [
"<agent_arn>",
"<agent_arn>/runtime-endpoint/*"
"<agent_runtime_arn>",
"<agent_runtime_arn>/runtime-endpoint/*"
]
}
]
}
```
4. **Save the policy** with a name like `DataAnalystAssistantPermissions`
## Configure Environment Variables
Rename the file **src/sample.env.js** to **src/env.js**:
@@ -169,22 +191,7 @@ Rename the file **src/sample.env.js** to **src/env.js**:
mv src/sample.env.js src/env.js
```
### Get CDK Output Values
First, get the required values from your CDK project outputs:
``` bash
# Set the stack name environment variable
export STACK_NAME=CdkAgentcoreStrandsDataAnalystAssistantStack
# Get the DynamoDB table name and AgentCore Role ARN from CDK outputs
export QUESTION_ANSWERS_TABLE_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableName'].OutputValue" --output text)
echo "Table Name: $QUESTION_ANSWERS_TABLE_NAME"
```
### Update Environment Variables
In your **src/env.js** update the following environment variables:
In your **src/env.js** update the following environment variables using the CDK output values from above:
- **QUESTION_ANSWERS_TABLE_NAME**: Use the value from the command above
- **AGENT_RUNTIME_ARN**: Your AgentCore runtime ARN (format: "arn:aws:bedrock-agentcore:region:account:runtime/runtime-name")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 434 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

@@ -1,136 +0,0 @@
# Back-End Deployment - Data Source and Configuration Management Deployment with CDK
Deploy the back-end infrastructure for a Data Analyst Assistant for Video Game Sales using **[AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/)**.
> [!NOTE]
> **Working Directory**: Make sure you are in the `cdk-agentcore-strands-data-analyst-assistant/` folder before starting this tutorial. All commands in this guide should be executed from this directory.
## Overview
This tutorial deploys the foundational AWS services required for the video game sales data analyst agent with the following key components:
- **IAM AgentCore Execution Role**: Comprehensive permissions for Amazon Bedrock AgentCore execution, including access to Bedrock models, RDS Data API, DynamoDB, and Secrets Manager
- **VPC with Public and Private Subnets**: Network isolation and security for database resources with NAT Gateway for outbound connectivity
- **Amazon Aurora Serverless v2 PostgreSQL**: Scalable database cluster storing video game sales data with RDS Data API integration and encryption
- **Amazon DynamoDB**: Single table for tracking SQL query results with pay-per-request billing
- **AWS Secrets Manager**: Secure storage for database credentials
- **Amazon S3**: Import bucket for loading data into Aurora PostgreSQL with lifecycle policies
- **VPC Gateway Endpoints**: Cost-effective access to S3 and DynamoDB services
- **SSM Parameter Store**: Configuration management for AgentCore runtime parameters
> [!IMPORTANT]
> Remember to clean up resources after testing to avoid unnecessary costs by following the clean-up steps provided.
## Prerequisites
Before you begin, ensure you have:
* AWS Account and appropriate IAM permissions for services deployment
* **Development Environment**:
* Python 3.10 or later installed
* **[AWS CDK Installed](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)**
* Run this command to create a service-linked role for RDS:
```bash
aws iam create-service-linked-role --aws-service-name rds.amazonaws.com
```
## AWS Deployment
Navigate to the CDK project folder and install dependencies:
```bash
npm install
```
Deploy the infrastructure:
```bash
cdk deploy
```
Default Parameters:
- **ProjectId**: "agentcore-data-analyst-assistant" - Project identifier used for naming resources
- **DatabaseName**: "video_games_sales" - Name of the database
Deployed Resources:
- **VPC**: Public/private subnets, NAT Gateway, security groups, VPC endpoints
- **Aurora PostgreSQL Serverless v2**: Database cluster with RDS Data API
- **DynamoDB**: Table for SQL query results
- **S3**: Bucket for data imports with lifecycle policies
- **Secrets Manager**: Database credentials storage
- **IAM**: AgentCore execution role with Bedrock, RDS, DynamoDB permissions
- **SSM Parameter Store**: Configuration parameters
- `/<projectId>/SECRET_ARN`: Database secret ARN
- `/<projectId>/AURORA_RESOURCE_ARN`: Aurora cluster ARN
- `/<projectId>/DATABASE_NAME`: Database name
- `/<projectId>/QUESTION_ANSWERS_TABLE`: DynamoDB question answers table name
- `/<projectId>/MAX_RESPONSE_SIZE_BYTES`: Maximum response size in bytes (1MB)
- `/<projectId>/MEMORY_ID`: AgentCore Memory ID for the Agent
These parameters are automatically retrieved by the Strands Agent to establish database connections and configure agent behavior.
> [!IMPORTANT]
> Enhance AI safety and compliance by implementing **[Amazon Bedrock Guardrails](https://aws.amazon.com/bedrock/guardrails/)** for your AI applications with the seamless integration offered by **[Strands Agents SDK](https://strandsagents.com/latest/user-guide/safety-security/guardrails/)**.
## Load Sample Data into PostgreSQL Database
1. Install required Python dependencies:
``` bash
pip install boto3
```
2. Set up the required environment variables:
``` bash
# Set the stack name environment variable
export STACK_NAME=CdkAgentcoreStrandsDataAnalystAssistantStack
# Retrieve the output values and store them in environment variables
export SECRET_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='SecretARN'].OutputValue" --output text)
export DATA_SOURCE_BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='DataSourceBucketName'].OutputValue" --output text)
export AURORA_SERVERLESS_DB_CLUSTER_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AuroraServerlessDBClusterARN'].OutputValue" --output text)
export AGENT_CORE_ROLE_EXECUTION=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AgentCoreMyRoleARN'].OutputValue" --output text)
export MEMORY_ID_SSM_PARAMETER=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='MemoryIdSSMParameter'].OutputValue" --output text)
cat << EOF
STACK_NAME: ${STACK_NAME}
SECRET_ARN: ${SECRET_ARN}
DATA_SOURCE_BUCKET_NAME: ${DATA_SOURCE_BUCKET_NAME}
AURORA_SERVERLESS_DB_CLUSTER_ARN: ${AURORA_SERVERLESS_DB_CLUSTER_ARN}
AGENT_CORE_ROLE_EXECUTION: ${AGENT_CORE_ROLE_EXECUTION}
MEMORY_ID_SSM_PARAMETER: ${MEMORY_ID_SSM_PARAMETER}
EOF
```
3. Load sample data into PostgreSQL:
``` bash
python3 resources/create-sales-database.py
```
The script uses the **[video_games_sales_no_headers.csv](./resources/database/video_games_sales_no_headers.csv)** as the data source.
> [!NOTE]
> The data source provided contains information from [Video Game Sales](https://www.kaggle.com/datasets/asaniczka/video-game-sales-2024) which is made available under the [ODC Attribution License](https://opendatacommons.org/licenses/odbl/1-0/).
## Next Step
You can now proceed to the **[Agent Deployment - Strands Agent Infrastructure Deployment with AgentCore](../agentcore-strands-data-analyst-assistant/))**.
## Cleaning-up Resources (Optional)
To avoid unnecessary charges, delete the CDK stack:
``` bash
cdk destroy
```
## Thank You
## License
This project is licensed under the Apache-2.0 License.
@@ -0,0 +1,248 @@
# Data Analyst Assistant - Amazon Bedrock AgentCore and Data Source Deployment with CDK
Deploy the complete infrastructure for a Data Analyst Assistant for Video Game Sales using **[AWS Cloud Development Kit (CDK)](https://aws.amazon.com/cdk/)** and **[Amazon Bedrock AgentCore](https://docs.aws.amazon.com/bedrock/latest/userguide/agentcore.html)**.
> [!NOTE]
> **Working Directory**: Make sure you are in the `cdk-data-analyst-assistant-agentcore-strands/` folder before starting this tutorial. All commands in this guide should be executed from this directory.
## Overview
This CDK stack deploys a complete data analyst assistant powered by Amazon Bedrock AgentCore with the following components:
### Amazon Bedrock AgentCore Resources
- **AgentCore Memory**: Short-term memory for maintaining conversation context with 7-day event expiration
- **AgentCore Runtime**: Container-based runtime hosting the Strands Agent with ARM64 architecture
- **AgentCore Runtime Endpoint**: HTTP endpoint for invoking the data analyst assistant
### Data Source and VPC Infrastructure
- **Amazon Aurora Serverless v2 PostgreSQL**: Scalable database cluster (v17.4) with RDS Data API enabled and storage encryption
- **Amazon DynamoDB**: Table for tracking SQL query results with pay-per-request billing
- **AWS Secrets Manager**: Secure storage for database credentials
- **Amazon S3**: Import bucket for loading data into Aurora PostgreSQL with 7-day lifecycle policy
- **SSM Parameter Store**: Configuration parameters for AgentCore runtime
- **VPC with Public and Private Subnets**: Network isolation with NAT Gateway for outbound connectivity
- **Security Groups**: Database access control with self-referencing rule for PostgreSQL (port 5432)
- **VPC Gateway Endpoints**: Cost-effective access to S3 and DynamoDB services
> [!IMPORTANT]
> Remember to clean up resources after testing to avoid unnecessary costs by following the clean-up steps provided.
## Prerequisites
Before you begin, ensure you have:
* AWS Account and appropriate IAM permissions for services deployment
* **Development Environment**:
* Python 3.10 or later installed
* Node.js and npm installed
* Docker installed and running (required for building the agent container image)
* **[AWS CDK Installed](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html)**
* Run this command to create a service-linked role for RDS. This role is required for Aurora Serverless v2 to manage resources on your behalf. New AWS accounts that haven't used RDS before may not have this role, which can cause CDK deployment failures:
```bash
aws iam create-service-linked-role --aws-service-name rds.amazonaws.com
```
> [!NOTE]
> If the role already exists, you will see the message: `Service role name AWSServiceRoleForRDS has been taken in this account`. This is expected and you can proceed with the deployment.
## AWS Deployment
Navigate to the CDK project folder and install dependencies:
```bash
npm install
```
Deploy the infrastructure:
```bash
cdk deploy
```
Default Parameters:
- **ProjectId**: "data-analyst-assistant-agentcore" - Project identifier used for naming resources
- **DatabaseName**: "video_games_sales" - Name of the database
- **BedrockModelId**: "global.anthropic.claude-haiku-4-5-20251001-v1:0" - Bedrock model ID for the agent
### Deployed Resources
**AgentCore Resources:**
- AgentCore Memory with 7-day event expiration
- AgentCore Runtime (container-based, ARM64)
- AgentCore Runtime Endpoint
- ECR repository with agent container image
**Data Infrastructure:**
- VPC with public/private subnets, NAT Gateway, security groups, VPC endpoints
- Aurora PostgreSQL Serverless v2 (v17.4) with RDS Data API enabled
- DynamoDB table for SQL query results
- S3 bucket for data imports with lifecycle policies
- Secrets Manager for database credentials
**Configuration:**
- SSM Parameter Store parameters:
- `/<projectId>/SECRET_ARN`: Database secret ARN
- `/<projectId>/AURORA_RESOURCE_ARN`: Aurora cluster ARN
- `/<projectId>/DATABASE_NAME`: Database name
- `/<projectId>/QUESTION_ANSWERS_TABLE`: DynamoDB table name
- `/<projectId>/MAX_RESPONSE_SIZE_BYTES`: Maximum response size (1MB)
- `/<projectId>/BEDROCK_MODEL_ID`: Bedrock model ID for the agent
These parameters are automatically retrieved by the Strands Agent via environment variables (`PROJECT_ID`, `MEMORY_ID`, `BEDROCK_MODEL_ID`) to establish database connections and configure agent behavior.
### Stack Outputs
After deployment, the stack exports:
- `MemoryId`: AgentCore Memory ID
- `AuroraServerlessDBClusterARN`: Aurora cluster ARN
- `SecretARN`: Database credentials secret ARN
- `DataSourceBucketName`: S3 import bucket name
- `QuestionAnswersTableName`: DynamoDB table name
- `QuestionAnswersTableArn`: DynamoDB table ARN
- `AgentRuntimeArn`: AgentCore runtime ARN
- `AgentEndpointName`: AgentCore runtime endpoint name
> [!IMPORTANT]
> Enhance AI safety and compliance by implementing **[Amazon Bedrock Guardrails](https://aws.amazon.com/bedrock/guardrails/)** for your AI applications with the seamless integration offered by **[Strands Agents SDK](https://strandsagents.com/latest/user-guide/safety-security/guardrails/)**.
## Set Up Environment Variables
After deployment, set up the required environment variables. These are needed for loading sample data and local testing:
```bash
# Set the stack name environment variable
export STACK_NAME=CdkDataAnalystAssistantAgentcoreStrandsStack
# Retrieve the output values and store them in environment variables
# Project configuration
export PROJECT_ID=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Parameters[?ParameterKey=='ProjectId'].ParameterValue" --output text)
export BEDROCK_MODEL_ID=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Parameters[?ParameterKey=='BedrockModelId'].ParameterValue" --output text)
# AgentCore resources
export MEMORY_ID=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='MemoryId'].OutputValue" --output text)
export AGENT_RUNTIME_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AgentRuntimeArn'].OutputValue" --output text)
export AGENT_ENDPOINT_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AgentEndpointName'].OutputValue" --output text)
# Database resources
export SECRET_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='SecretARN'].OutputValue" --output text)
export AURORA_SERVERLESS_DB_CLUSTER_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='AuroraServerlessDBClusterARN'].OutputValue" --output text)
# DynamoDB resources
export QUESTION_ANSWERS_TABLE_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableName'].OutputValue" --output text)
export QUESTION_ANSWERS_TABLE_ARN=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='QuestionAnswersTableArn'].OutputValue" --output text)
# S3 resources
export DATA_SOURCE_BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query "Stacks[0].Outputs[?OutputKey=='DataSourceBucketName'].OutputValue" --output text)
cat << EOF
# Stack Configuration
STACK_NAME: ${STACK_NAME}
PROJECT_ID: ${PROJECT_ID}
BEDROCK_MODEL_ID: ${BEDROCK_MODEL_ID}
# AgentCore Resources
MEMORY_ID: ${MEMORY_ID}
AGENT_RUNTIME_ARN: ${AGENT_RUNTIME_ARN}
AGENT_ENDPOINT_NAME: ${AGENT_ENDPOINT_NAME}
# Database Resources
SECRET_ARN: ${SECRET_ARN}
AURORA_SERVERLESS_DB_CLUSTER_ARN: ${AURORA_SERVERLESS_DB_CLUSTER_ARN}
# DynamoDB Resources
QUESTION_ANSWERS_TABLE_NAME: ${QUESTION_ANSWERS_TABLE_NAME}
QUESTION_ANSWERS_TABLE_ARN: ${QUESTION_ANSWERS_TABLE_ARN}
# S3 Resources
DATA_SOURCE_BUCKET_NAME: ${DATA_SOURCE_BUCKET_NAME}
EOF
```
## Load Sample Data into PostgreSQL Database
1. Install required Python dependencies:
```bash
pip install boto3
```
2. Load sample data into PostgreSQL:
```bash
python3 resources/create-sales-database.py
```
The script uses the **[video_games_sales_no_headers.csv](./resources/database/video_games_sales_no_headers.csv)** as the data source.
> [!NOTE]
> The data source provided contains information from [Video Game Sales](https://www.kaggle.com/datasets/asaniczka/video-game-sales-2024) which is made available under the [ODC Attribution License](https://opendatacommons.org/licenses/odbl/1-0/).
## Local Testing
Before deploying to AWS, you can test the Data Analyst Agent locally to verify functionality:
1. Navigate to the agent folder and start the local agent server:
```bash
cd data-analyst-assistant-agentcore-strands
python3 app.py
```
This launches a local server on port 8080 that simulates the AgentCore runtime environment.
2. In a different terminal, create a session ID for conversation tracking:
```bash
export SESSION_ID=$(uuidgen)
```
3. Test the agent with example queries using curl:
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello world!", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "what is the structure of your data available?!", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Which developers tend to get the best reviews?", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
```bash
curl -X POST http://localhost:8080/invocations \
-H "Content-Type: application/json" \
-d '{"prompt": "Give me a summary of our conversation", "session_id": "'$SESSION_ID'", "last_k_turns": 20}'
```
## Invoking the Agent
Once deployed and data is loaded, you can invoke the agent using the AgentCore Runtime Endpoint. The endpoint name is available in the stack outputs as `AgentEndpointName`.
## Next Step
You can now proceed to the **[Front-End Implementation - Integrating AgentCore with a Ready-to-Use Data Analyst Assistant Application](../amplify-video-games-sales-assistant-agentcore-strands/))**.
## Cleaning-up Resources (Optional)
To avoid unnecessary charges, delete the CDK stack:
```bash
cdk destroy
```
## License
This project is licensed under the Apache-2.0 License.
@@ -1,9 +1,9 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib/core';
import { CdkAgentcoreStrandsDataAnalystAssistantStack } from '../cdklib/cdk-agentcore-strands-data-analyst-assistant-stack';
import { CdkDataAnalystAssistantAgentcoreStrandsStack } from '../cdklib/cdk-data-analyst-assistant-agentcore-strands-stack';
const app = new cdk.App();
new CdkAgentcoreStrandsDataAnalystAssistantStack(app, 'CdkAgentcoreStrandsDataAnalystAssistantStack', {
new CdkDataAnalystAssistantAgentcoreStrandsStack(app, 'CdkDataAnalystAssistantAgentcoreStrandsStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */
@@ -1,5 +1,5 @@
{
"app": "npx ts-node --prefer-ts-exts bin/cdk-agentcore-strands-data-analyst-assistant.ts",
"app": "npx ts-node --prefer-ts-exts bin/cdk-data-analyst-assistant-agentcore-strands.ts",
"watch": {
"include": [
"**"
@@ -19,8 +19,11 @@ import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets';
import * as path from 'path';
import { aws_bedrockagentcore as bedrockagentcore } from 'aws-cdk-lib';
export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
export class CdkDataAnalystAssistantAgentcoreStrandsStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
@@ -32,7 +35,7 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
const projectId = new cdk.CfnParameter(this, "ProjectId", {
type: "String",
description: "Project identifier used for naming resources",
default: "agentcore-data-analyst-assistant",
default: "data-analyst-assistant-agentcore",
});
// Name of the Aurora PostgreSQL database
@@ -42,6 +45,13 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
default: "video_games_sales",
});
// Bedrock model ID for the agent
const bedrockModelId = new cdk.CfnParameter(this, "BedrockModelId", {
type: "String",
description: "The Bedrock model ID for the agent",
default: "global.anthropic.claude-haiku-4-5-20251001-v1:0",
});
// ================================
// DYNAMODB TABLES
// ================================
@@ -111,6 +121,13 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
}
);
// Allow inbound PostgreSQL traffic from the same security group
sg_db.addIngressRule(
sg_db,
ec2.Port.tcp(5432),
"Allow PostgreSQL access from within the same security group"
);
// Database credentials stored in AWS Secrets Manager
const databaseUsername = "postgres";
const secret = new rds.DatabaseSecret(this, "AssistantSecret", {
@@ -123,6 +140,33 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
assumedBy: new iam.ServicePrincipal("rds.amazonaws.com"),
});
// ================================
// S3 STORAGE
// ================================
// S3 bucket containing data for import into Aurora PostgreSQL
const importBucket = new s3.Bucket(this, "ImportBucket", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
lifecycleRules: [
{
expiration: cdk.Duration.days(7), // Auto-delete objects after 7 days
},
],
});
// Grant S3 access to the Aurora role for data imports
auroraS3Role.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"],
resources: [
importBucket.bucketArn,
`${importBucket.bucketArn}/*`,
],
})
);
// Aurora PostgreSQL Serverless v2 cluster containing video games sales data
let cluster = new rds.DatabaseCluster(this, "AssistantCluster", {
engine: rds.DatabaseClusterEngine.auroraPostgres({
@@ -360,19 +404,7 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
}
);
// Grant S3 access to the role
auroraS3Role.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"],
resources: [
`arn:aws:s3:::${projectId.valueAsString}-${this.region}-${this.account}-import`,
`arn:aws:s3:::${projectId.valueAsString}-${this.region}-${this.account}-import/*`,
],
})
);
// Add additional RDS permissions
// Add additional RDS permissions to Aurora S3 role
auroraS3Role.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
@@ -389,21 +421,69 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
);
// ================================
// S3 STORAGE
// DOCKER IMAGE ASSET
// ================================
// S3 bucket containing data for import into Aurora PostgreSQL
const importBucket = new s3.Bucket(this, "ImportBucket", {
bucketName: `${projectId.valueAsString}-${this.region}-${this.account}-import`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
lifecycleRules: [
{
expiration: cdk.Duration.days(7), // Auto-delete objects after 7 days
},
],
// Build and push Docker image automatically during CDK deployment
// DockerImageAsset creates and manages its own ECR repository
const dockerImageAsset = new ecr_assets.DockerImageAsset(this, 'RuntimeDockerImage', {
directory: path.join(__dirname, '../data-analyst-assistant-agentcore-strands'),
platform: ecr_assets.Platform.LINUX_ARM64
});
// ================================
// BEDROCK AGENTCORE MEMORY
// ================================
// Short-term memory for AgentCore to maintain conversation context
const uniqueSuffix = cdk.Names.uniqueId(this).slice(-8).toLowerCase().replace(/[^a-z0-9]/g, '');
const agentMemory = new bedrockagentcore.CfnMemory(this, 'AgentMemory', {
name: `DataAnalystAssistantMemory_${uniqueSuffix}`,
eventExpiryDuration: 7, // Events expire after 7 days
memoryExecutionRoleArn: agentCoreRole.roleArn,
description: 'Short-term memory for data analyst assistant conversations',
});
// ================================
// BEDROCK AGENTCORE RUNTIME
// ================================
// AgentCore Runtime with container type for the data analyst assistant
const agentRuntime = new bedrockagentcore.CfnRuntime(this, 'AgentRuntime', {
agentRuntimeName: `DataAnalystRuntime_${uniqueSuffix}`,
agentRuntimeArtifact: {
containerConfiguration: {
containerUri: dockerImageAsset.imageUri,
},
},
networkConfiguration: {
networkMode: 'PUBLIC',
},
roleArn: agentCoreRole.roleArn,
description: 'Container runtime for video games sales data analyst assistant',
environmentVariables: {
PROJECT_ID: projectId.valueAsString,
MEMORY_ID: agentMemory.attrMemoryId,
BEDROCK_MODEL_ID: bedrockModelId.valueAsString,
},
});
agentRuntime.addDependency(agentMemory);
// ================================
// BEDROCK AGENTCORE RUNTIME ENDPOINT
// ================================
// Runtime endpoint for invoking the data analyst assistant
const runtimeEndpoint = new bedrockagentcore.CfnRuntimeEndpoint(this, 'RuntimeEndpoint', {
agentRuntimeId: agentRuntime.attrAgentRuntimeId,
name: `DataAnalystEndpoint_${uniqueSuffix}`,
description: 'Endpoint for invoking the video games sales data analyst assistant',
});
// Endpoint depends on runtime being created first
runtimeEndpoint.addDependency(agentRuntime);
// ================================
// SSM PARAMETERS
// ================================
@@ -444,10 +524,10 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
type: 'String'
});
new ssm.CfnParameter(this, 'MemoryIdParam', {
name: `/${projectId.valueAsString}/MEMORY_ID`,
value: "AssistantAgentMemoryIdToBeCreated",
description: 'Memory ID for the agent',
new ssm.CfnParameter(this, 'BedrockModelIdParam', {
name: `/${projectId.valueAsString}/BEDROCK_MODEL_ID`,
value: bedrockModelId.valueAsString,
description: 'Bedrock model ID for the agent',
type: 'String'
});
@@ -481,18 +561,29 @@ export class CdkAgentcoreStrandsDataAnalystAssistantStack extends cdk.Stack {
exportName: `${projectId.valueAsString}-QuestionAnswersTableName`,
});
new cdk.CfnOutput(this, "AgentCoreMyRoleARN", {
value: agentCoreRole.roleArn,
description: "The ARN of the AgentCoreMyRole",
exportName: `${projectId.valueAsString}-AgentCoreMyRoleARN`,
new cdk.CfnOutput(this, "QuestionAnswersTableArn", {
value: rawQueryResults.tableArn,
description: "The ARN of the DynamoDB table for storing query results",
exportName: `${projectId.valueAsString}-QuestionAnswersTableArn`,
});
new cdk.CfnOutput(this, "MemoryIdSSMParameter", {
value: `/${projectId.valueAsString}/MEMORY_ID`,
description: "The SSM parameter name for the memory ID",
exportName: `${projectId.valueAsString}-MemoryIdSSMParameter`,
new cdk.CfnOutput(this, "AgentRuntimeArn", {
value: agentRuntime.attrAgentRuntimeArn,
description: "The ARN of the AgentCore runtime",
exportName: `${projectId.valueAsString}-AgentRuntimeArn`,
});
new cdk.CfnOutput(this, "AgentEndpointName", {
value: runtimeEndpoint.name,
description: "The name of the AgentCore runtime endpoint",
exportName: `${projectId.valueAsString}-AgentEndpointName`,
});
new cdk.CfnOutput(this, "MemoryId", {
value: agentMemory.attrMemoryId,
description: "The ID of the AgentCore Memory",
exportName: `${projectId.valueAsString}-MemoryId`,
});
}
}
@@ -0,0 +1,45 @@
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
WORKDIR /app
# All environment variables in one layer
ENV UV_SYSTEM_PYTHON=1 \
UV_COMPILE_BYTECODE=1 \
UV_NO_PROGRESS=1 \
PYTHONUNBUFFERED=1 \
DOCKER_CONTAINER=1 \
AWS_REGION=us-east-1 \
AWS_DEFAULT_REGION=us-east-1
COPY requirements.txt requirements.txt
# Install from requirements file
RUN uv pip install -r requirements.txt
RUN uv pip install aws-opentelemetry-distro>=0.10.1
# Signal that this is running in Docker for host binding logic
ENV DOCKER_CONTAINER=1
# Create non-root user
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 9000
EXPOSE 8000
EXPOSE 8080
# Health check using the /ping endpoint
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/ping', timeout=5)" || exit 1
# Copy entire project (respecting .dockerignore)
COPY . .
# Use the full module path
CMD ["opentelemetry-instrument", "python", "-m", "app"]
@@ -0,0 +1,55 @@
# Data Analyst Assistant - Strands Agent
A video games sales data analyst assistant built with the **[Strands Agents SDK](https://strandsagents.com/)** and powered by **[Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/)**.
## Overview
This agent provides an intelligent data analyst assistant specialized in video game sales analysis. It leverages Amazon Bedrock Claude models for natural language processing, Aurora Serverless PostgreSQL for data storage, and AgentCore Memory for conversation context management.
## Features
- Natural language to SQL query conversion
- Video game sales data analysis and insights
- Conversation memory and context awareness via AgentCore Memory
- Real-time streaming responses
- Comprehensive error handling and logging
## Agent Tools
| Tool | Type | Description |
|------|------|-------------|
| `get_tables_information` | Custom | Retrieves metadata about database tables, including structure, columns, and relationships |
| `execute_sql_query` | Custom | Executes SQL queries against the PostgreSQL database based on natural language questions |
| `current_time` | Native (Strands) | Provides current date and time information based on user's timezone |
## Project Structure
```
data-analyst-assistant-agentcore-strands/
├── app.py # Main application entry point
├── Dockerfile # Container configuration for AgentCore Runtime
├── instructions.txt # Agent system prompt and behavior configuration
├── requirements.txt # Python dependencies
├── src/
│ ├── tools/ # Custom tool implementations
│ └── utils/ # Utility functions and helpers
└── resources/ # Additional resources
```
## Configuration
The agent uses the following environment variables:
| Variable | Description |
|----------|-------------|
| `PROJECT_ID` | Project identifier for SSM parameter retrieval |
| `MEMORY_ID` | AgentCore Memory ID for conversation context |
| `BEDROCK_MODEL_ID` | Bedrock model ID (default: `global.anthropic.claude-haiku-4-5-20251001-v1:0`) |
## Model Provider
- **Amazon Bedrock** with Claude Haiku 4.5 (default: `global.anthropic.claude-haiku-4-5-20251001-v1:0`)
## License
This project is licensed under the Apache-2.0 License.
@@ -20,7 +20,6 @@ from uuid import uuid4
# Bedrock Agent Core imports
from bedrock_agentcore import BedrockAgentCoreApp
from bedrock_agentcore.memory import MemoryClient
from strands import Agent, tool
from strands_tools import current_time
from strands.models import BedrockModel
@@ -30,7 +29,6 @@ from src.tools import get_tables_information, run_sql_query
from src.utils import (
save_raw_query_result,
load_file_content,
load_config,
get_agentcore_memory_messages,
MemoryHookProvider,
)
@@ -39,59 +37,13 @@ from src.utils import (
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("personal-agent")
# Retrieve AgentCore Memory ID
memory_id = os.environ.get("MEMORY_ID")
# Load configuration from SSM Parameter Store
# Get PROJECT_ID from environment variable to construct SSM parameter paths
PROJECT_ID = os.environ.get("PROJECT_ID", "agentcore-data-analyst-assistant")
# Load all configuration from SSM
try:
config = load_config()
print("✅ CONFIGURATION LOADED FROM SSM")
print("-" * 50)
print(f"🔧 Project ID: {PROJECT_ID}")
print(f"📊 Database: {config.get('DATABASE_NAME')}")
print("-" * 50)
except Exception as e:
print("❌ CONFIGURATION LOAD ERROR")
print("-" * 50)
print(f"🚨 Error: {e}")
print(f"🔧 Project ID: {PROJECT_ID}")
print("-" * 50)
# Set empty config as fallback
config = {}
# Initialize AgentCore Memory configuration
try:
print("\n" + "=" * 70)
print("🚀 INITIALIZING VIDEO GAMES SALES ANALYST ASSISTANT")
print("=" * 70)
print("📋 Loading configuration from AWS Systems Manager...")
# Retrieve memory ID from config
memory_id = config.get("MEMORY_ID")
# Validate memory ID configuration
if not memory_id or memory_id.strip() == "":
error_msg = "Memory ID not found in configuration. Please create AgentCore Memory first."
print(f"❌ Configuration Error: {error_msg}")
logger.error(error_msg)
raise ValueError(error_msg)
print(f"✅ Memory ID retrieved: {memory_id}")
# Initialize AgentCore Memory Client
print("🧠 Connecting to AgentCore Memory service...")
client = MemoryClient()
print("✅ Memory client connected successfully")
print("=" * 70 + "\n")
except Exception as e:
print(f"💥 INITIALIZATION FAILED: {str(e)}")
print("=" * 70 + "\n")
logger.error(f"Failed to initialize AgentCore Memory: {e}")
raise
# Retrieve Bedrock Model ID
bedrock_model_id_env = os.environ.get(
"BEDROCK_MODEL_ID", "global.anthropic.claude-haiku-4-5-20251001-v1:0"
)
# Initialize the Bedrock Agent Core app
@@ -244,7 +196,8 @@ async def agent_invocation(payload):
Expected payload structure:
{
model_id="global.anthropic.claude-haiku-4-5-20251001-v1:0",
"prompt": "Your video game sales analysis question",
"prompt_uuid": "optional-unique-prompt-identifier",
"user_timezone": "US/Pacific",
"session_id": "optional-conversation-session-id",
"user_id": "optional-user-identifier",
@@ -260,9 +213,6 @@ async def agent_invocation(payload):
"prompt",
"No prompt found in input, please guide customer to create a json payload with prompt key",
)
bedrock_model_id = payload.get(
"bedrock_model_id", "global.anthropic.claude-haiku-4-5-20251001-v1:0"
)
prompt_uuid = payload.get("prompt_uuid", str(uuid4()))
user_timezone = payload.get("user_timezone", "US/Pacific")
session_id = payload.get("session_id", str(uuid4()))
@@ -275,7 +225,7 @@ async def agent_invocation(payload):
print(
f"💬 User Query: {user_message[:100]}{'...' if len(user_message) > 100 else ''}"
)
print(f"🤖 Claude Model: {bedrock_model_id}")
print(f"🤖 Claude Model: {bedrock_model_id_env}")
print(f"🆔 Prompt UUID: {prompt_uuid}")
print(f"🌍 User Timezone: {user_timezone}")
print(f"🔗 Conversation ID: {session_id}")
@@ -284,14 +234,14 @@ async def agent_invocation(payload):
print("-" * 80)
# Initialize Claude model for video game sales analysis
print(f"🧠 Initializing Claude model for analysis: {bedrock_model_id}")
bedrock_model = BedrockModel(model_id=bedrock_model_id)
print(f"🧠 Initializing Claude model for analysis: {bedrock_model_id_env}")
bedrock_model = BedrockModel(model_id=bedrock_model_id_env)
print("✅ Claude model ready for video game sales analysis")
print("-" * 80)
print("🧠 Retrieving conversation context from AgentCore Memory...")
agentcore_messages = get_agentcore_memory_messages(
client, memory_id, user_id, session_id, last_k_turns
memory_id, user_id, session_id, last_k_turns
)
print("📋 CONVERSATION CONTEXT LOADED:")
@@ -331,9 +281,7 @@ async def agent_invocation(payload):
messages=agentcore_messages,
model=bedrock_model,
system_prompt=system_prompt,
hooks=[
MemoryHookProvider(client, memory_id, user_id, session_id, last_k_turns)
],
hooks=[MemoryHookProvider(memory_id, user_id, session_id, last_k_turns)],
tools=[
get_tables_information,
current_time,
@@ -3,5 +3,5 @@ strands-agents-tools
typing
boto3
bedrock-agentcore
bedrock-agentcore-starter-toolkit>=0.1.34
bedrock-agentcore-starter-toolkit
botocore
@@ -18,29 +18,35 @@ from bedrock_agentcore.memory import MemoryClient
from botocore.exceptions import ClientError
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)
# Default configuration
DEFAULT_MEMORY_NAME = "AssistantAgentMemory"
DEFAULT_EXPIRY_DAYS = 7
def create_memory(memory_name: str = DEFAULT_MEMORY_NAME, expiry_days: int = DEFAULT_EXPIRY_DAYS,
parameter_store_name: Optional[str] = None) -> Optional[str]:
def create_memory(
memory_name: str = DEFAULT_MEMORY_NAME,
expiry_days: int = DEFAULT_EXPIRY_DAYS,
parameter_store_name: Optional[str] = None,
) -> Optional[str]:
"""
Create a new memory resource for the agent and store the memory ID in parameter store
Args:
memory_name (str): Name for the memory resource
expiry_days (int): Retention period for short-term memory
parameter_store_name (str): Name of the parameter store to update with memory ID
Returns:
str: Memory ID if successful, None otherwise
"""
logger.info(f"Creating memory resource: {memory_name}")
client = MemoryClient()
try:
# Create memory resource for short-term conversation storage
memory = client.create_memory_and_wait(
@@ -49,23 +55,25 @@ def create_memory(memory_name: str = DEFAULT_MEMORY_NAME, expiry_days: int = DEF
description="Short-term memory for data analyst assistant",
event_expiry_days=expiry_days, # Retention period for short-term memory (up to 365 days)
)
memory_id = memory['id']
memory_id = memory["id"]
logger.info(f"✅ Created memory: {memory_id}")
# Store memory ID in parameter store if parameter_store_name is provided
if parameter_store_name:
try:
ssm_client = boto3.client('ssm')
ssm_client = boto3.client("ssm")
ssm_client.put_parameter(
Name=parameter_store_name,
Value=memory_id,
Type='String',
Overwrite=True
Type="String",
Overwrite=True,
)
logger.info(
f"✅ Stored memory ID in parameter store: {parameter_store_name}"
)
logger.info(f"✅ Stored memory ID in parameter store: {parameter_store_name}")
except Exception as e:
logger.error(f"❌ Failed to store memory ID in parameter store: {e}")
return memory_id
except ClientError as e:
logger.info(f"❌ ERROR: {e}")
@@ -74,32 +82,34 @@ def create_memory(memory_name: str = DEFAULT_MEMORY_NAME, expiry_days: int = DEF
# Log any errors during memory creation
logger.error(f"❌ ERROR: {e}")
import traceback
traceback.print_exc()
return None
def list_memories() -> List[Dict[str, Any]]:
"""
List all available memory resources
Returns:
List[Dict]: List of memory resources
"""
logger.info("Listing memory resources...")
client = MemoryClient()
try:
memories = client.list_memories()
logger.info(f"Found {len(memories)} memory resources:")
if memories:
print("\n📋 Memory Resources:")
print("-" * 60)
for i, memory in enumerate(memories, 1):
memory_id = memory.get('id', 'N/A')
memory_name = memory.get('name', 'N/A')
status = memory.get('status', 'N/A')
created_time = memory.get('createdTime', 'N/A')
memory_id = memory.get("id", "N/A")
memory_name = memory.get("name", "N/A")
status = memory.get("status", "N/A")
created_time = memory.get("createdTime", "N/A")
print(f"{i}. Name: {memory_name}")
print(f" ID: {memory_id}")
print(f" Status: {status}")
@@ -107,46 +117,56 @@ def list_memories() -> List[Dict[str, Any]]:
print("-" * 60)
else:
print("No memory resources found.")
return memories
except Exception as e:
logger.error(f"❌ ERROR listing memories: {e}")
import traceback
traceback.print_exc()
return []
def main():
"""Main function to handle command line arguments"""
if len(sys.argv) < 2:
print("Usage: python3 memory_manager.py [create|list]")
print(" create <memory_name> <parameter_store_name> - Create a new memory resource")
print(
" create <memory_name> <parameter_store_name> - Create a new memory resource"
)
print(" list - List all existing memory resources")
sys.exit(1)
action = sys.argv[1].lower()
if action == 'create':
if action == "create":
if len(sys.argv) != 4:
print("Usage: python3 memory_manager.py create <memory_name> <parameter_store_name>")
print(
"Usage: python3 memory_manager.py create <memory_name> <parameter_store_name>"
)
print(" <memory_name> - Name for the memory resource")
print(" <parameter_store_name> - Name of the parameter store to update with memory ID")
print(
" <parameter_store_name> - Name of the parameter store to update with memory ID"
)
sys.exit(1)
memory_name = sys.argv[2]
parameter_store_name = sys.argv[3]
print(f"🚀 Creating memory resource: {memory_name}")
print(f"📝 Parameter store name: {parameter_store_name}")
memory_id = create_memory(memory_name=memory_name, parameter_store_name=parameter_store_name)
memory_id = create_memory(
memory_name=memory_name, parameter_store_name=parameter_store_name
)
if memory_id:
print(f"✅ Memory created successfully!")
print("✅ Memory created successfully!")
print(f"Memory ID: {memory_id}")
print(f"Memory ID stored in parameter store: {parameter_store_name}")
else:
print("❌ Failed to create memory")
sys.exit(1)
elif action == 'list':
elif action == "list":
print("📋 Listing memory resources...")
memories = list_memories()
if not memories:
@@ -156,5 +176,6 @@ def main():
print("Available actions: create, list")
sys.exit(1)
if __name__ == "__main__":
main()
main()
@@ -28,7 +28,6 @@ class MemoryHookProvider(HookProvider):
initializes and saving messages as they are added to the conversation.
Attributes:
memory_client: Client for interacting with Bedrock Agent Core memory
memory_id: ID of the memory resource
actor_id: ID of the user/actor
session_id: ID of the current conversation session
@@ -37,7 +36,6 @@ class MemoryHookProvider(HookProvider):
def __init__(
self,
memory_client: MemoryClient,
memory_id: str,
actor_id: str,
session_id: str,
@@ -47,13 +45,12 @@ class MemoryHookProvider(HookProvider):
Initialize the memory hook provider.
Args:
memory_client: Client for interacting with Bedrock Agent Core memory
memory_id: ID of the memory resource
actor_id: ID of the user/actor
session_id: ID of the current conversation session
last_k_turns: Number of conversation turns to retrieve from history (default: 20)
"""
self.memory_client = memory_client
self.memory_client = MemoryClient()
self.memory_id = memory_id
self.actor_id = actor_id
self.session_id = session_id
@@ -15,7 +15,6 @@ logger = logging.getLogger("agentcore-memory-utils")
def get_agentcore_memory_messages(
memory_client: MemoryClient,
memory_id: str,
actor_id: str,
session_id: str,
@@ -28,7 +27,6 @@ def get_agentcore_memory_messages(
and formats them in the standard message format with role and content structure.
Args:
memory_client: Client for interacting with Bedrock Agent Core memory
memory_id: ID of the memory resource
actor_id: ID of the user/actor
session_id: ID of the current conversation session
@@ -45,6 +43,8 @@ def get_agentcore_memory_messages(
Exception: If there's an error retrieving messages from memory
"""
try:
# Initialize memory client
memory_client = MemoryClient()
# Pretty console output for memory retrieval start
print("\n" + "=" * 70)
print("🧠 AGENTCORE MEMORY RETRIEVAL")
@@ -9,7 +9,6 @@ Parameters:
- SECRET_ARN: ARN of the AWS Secrets Manager secret containing database credentials
- AURORA_RESOURCE_ARN: ARN of the Aurora Serverless cluster
- DATABASE_NAME: Name of the database to connect to
- MEMORY_ID: AgentCore Memory ID for conversation context management
- QUESTION_ANSWERS_TABLE: DynamoDB table for storing query results
- MAX_RESPONSE_SIZE_BYTES: Maximum size of query responses in bytes (default: 25600)
"""
@@ -79,7 +78,6 @@ def load_config():
"DATABASE_NAME",
"QUESTION_ANSWERS_TABLE",
"MAX_RESPONSE_SIZE_BYTES",
"MEMORY_ID",
]
config = {}
@@ -105,7 +103,6 @@ def load_config():
"SECRET_ARN",
"AURORA_RESOURCE_ARN",
"DATABASE_NAME",
"MEMORY_ID",
]:
raise ValueError(
f"Required SSM parameter /{PROJECT_ID}/{key} not found"
@@ -1,8 +1,8 @@
{
"name": "cdk-agentcore-strands-data-analyst-assistant",
"name": "cdk-data-analyst-assistant-agentcore-strands",
"version": "0.1.0",
"bin": {
"cdk-agentcore-strands-data-analyst-assistant": "bin/cdk-agentcore-strands-data-analyst-assistant.js"
"cdk-data-analyst-assistant-agentcore-strands": "bin/cdk-data-analyst-assistant-agentcore-strands.js"
},
"scripts": {
"build": "tsc",
@@ -13,9 +13,9 @@
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "22.7.9",
"aws-cdk": "2.1031.1",
"jest": "^29.7.0",
"ts-jest": "^29.2.5",
"aws-cdk": "2.1031.2",
"ts-node": "^10.9.2",
"typescript": "~5.6.3"
},
@@ -15,7 +15,6 @@ local_file_path = "resources/database/video_games_sales_no_headers.csv"
s3_file_name = "video_games_sales_no_headers.csv"
try:
# Upload file to S3
s3_client = boto3.client("s3")
s3_client.upload_file(
@@ -30,7 +29,7 @@ try:
client = boto3.client("rds-data")
# Create table
query1 = """ CREATE TABLE video_games_sales_units (
query1 = """ CREATE TABLE IF NOT EXISTS video_games_sales_units (
title TEXT,
console TEXT,
genre TEXT,
@@ -56,7 +55,7 @@ try:
print("Query response: " + str(response))
# Create AWS S3 extension
query2 = "CREATE EXTENSION aws_s3 CASCADE;"
query2 = "CREATE EXTENSION IF NOT EXISTS aws_s3 CASCADE;"
response = client.execute_statement(
resourceArn=aurora_serverless_db_cluster_arn,
@@ -69,12 +68,13 @@ try:
print("Query: " + query2)
print("Query response: " + str(response))
# Import data from S3
# Import data from S3 using IAM role credentials
# Note: This uses the IAM role associated with the Aurora cluster via s3ImportRole
query3 = f"""
SELECT aws_s3.table_import_from_s3(
'video_games_sales_units',
'title,console,genre,publisher,developer,critic_score,total_sales,na_sales,jp_sales,pal_sales,other_sales,release_date',
'DELIMITER ''|''',
'(FORMAT csv, DELIMITER ''|'', HEADER false)',
aws_commons.create_s3_uri('{data_source_bucket_name}', '{s3_file_name}', '{region}')
); """
@@ -1,13 +1,13 @@
// import * as cdk from 'aws-cdk-lib/core';
// import { Template } from 'aws-cdk-lib/assertions';
// import * as CdkAgentcoreStrandsDataAnalystAssistant from '../lib/cdk-agentcore-strands-data-analyst-assistant-stack';
// import * as CdkDataAnalystAssistantAgentcoreStrands from '../lib/cdk-data-analyst-assistant-agentcore-strands-stack';
// example test. To run these tests, uncomment this file along with the
// example resource in lib/cdk-agentcore-strands-data-analyst-assistant-stack.ts
// example resource in lib/cdk-data-analyst-assistant-agentcore-strands-stack.ts
test('SQS Queue Created', () => {
// const app = new cdk.App();
// // WHEN
// const stack = new CdkAgentcoreStrandsDataAnalystAssistant.CdkAgentcoreStrandsDataAnalystAssistantStack(app, 'MyTestStack');
// const stack = new CdkDataAnalystAssistantAgentcoreStrands.CdkDataAnalystAssistantAgentcoreStrandsStack(app, 'MyTestStack');
// // THEN
// const template = Template.fromStack(stack);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

After

Width:  |  Height:  |  Size: 434 KiB