e746bf7764
adding scripts for agentcore features; jupyter notebooks moved to workshops; reorganising folders
1010 lines
38 KiB
YAML
1010 lines
38 KiB
YAML
AWSTemplateFormatVersion: "2010-09-09"
|
|
Description: "Multi-Agent AgentCore deployment - Two agents where agent1 orchestrates and calls agent2"
|
|
|
|
# ============================================================================
|
|
# PARAMETERS SECTION
|
|
# ============================================================================
|
|
Parameters:
|
|
# Agent Configuration
|
|
Agent1Name:
|
|
Type: String
|
|
Default: "OrchestratorAgent"
|
|
Description: "Name for the orchestrator agent runtime (agent1)"
|
|
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9_]{0,47}$"
|
|
ConstraintDescription: "Must start with a letter, max 48 characters, alphanumeric and underscores only"
|
|
|
|
Agent2Name:
|
|
Type: String
|
|
Default: "SpecialistAgent"
|
|
Description: "Name for the specialist agent runtime (agent2)"
|
|
AllowedPattern: "^[a-zA-Z][a-zA-Z0-9_]{0,47}$"
|
|
ConstraintDescription: "Must start with a letter, max 48 characters, alphanumeric and underscores only"
|
|
|
|
# Container Configuration
|
|
ImageTag:
|
|
Type: String
|
|
Default: "latest"
|
|
Description: "Tag for the Docker images"
|
|
|
|
# Network Configuration
|
|
NetworkMode:
|
|
Type: String
|
|
Default: "PUBLIC"
|
|
Description: "Network mode for AgentCore resources"
|
|
AllowedValues:
|
|
- PUBLIC
|
|
- PRIVATE
|
|
|
|
# ECR Configuration
|
|
ECRRepositoryName:
|
|
Type: String
|
|
Default: "multi-agent"
|
|
Description: "Base name of the ECR repositories"
|
|
|
|
# ============================================================================
|
|
# METADATA SECTION
|
|
# ============================================================================
|
|
Metadata:
|
|
AWS::CloudFormation::Interface:
|
|
ParameterGroups:
|
|
- Label:
|
|
default: "Agent Configuration"
|
|
Parameters:
|
|
- Agent1Name
|
|
- Agent2Name
|
|
- NetworkMode
|
|
- Label:
|
|
default: "Container Configuration"
|
|
Parameters:
|
|
- ECRRepositoryName
|
|
- ImageTag
|
|
ParameterLabels:
|
|
Agent1Name:
|
|
default: "Agent 1 Name (Orchestrator)"
|
|
Agent2Name:
|
|
default: "Agent 2 Name (Specialist)"
|
|
NetworkMode:
|
|
default: "Network Mode"
|
|
ECRRepositoryName:
|
|
default: "ECR Repository Base Name"
|
|
ImageTag:
|
|
default: "Image Tag"
|
|
|
|
# ============================================================================
|
|
# RESOURCES SECTION
|
|
# ============================================================================
|
|
Resources:
|
|
# ========================================================================
|
|
# ECR MODULE - Container Registry
|
|
# ========================================================================
|
|
|
|
ECRRepositoryAgent1:
|
|
Type: AWS::ECR::Repository
|
|
DeletionPolicy: Delete
|
|
UpdateReplacePolicy: Delete
|
|
Properties:
|
|
RepositoryName: !Sub "${AWS::StackName}-${ECRRepositoryName}-agent1"
|
|
ImageTagMutability: IMMUTABLE
|
|
EmptyOnDelete: true
|
|
ImageScanningConfiguration:
|
|
ScanOnPush: true
|
|
RepositoryPolicyText:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: AllowPullFromAccount
|
|
Effect: Allow
|
|
Principal:
|
|
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
|
|
Action:
|
|
- ecr:BatchGetImage
|
|
- ecr:GetDownloadUrlForLayer
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-ecr-repository-agent1"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: ECR
|
|
|
|
ECRRepositoryAgent2:
|
|
Type: AWS::ECR::Repository
|
|
DeletionPolicy: Delete
|
|
UpdateReplacePolicy: Delete
|
|
Properties:
|
|
RepositoryName: !Sub "${AWS::StackName}-${ECRRepositoryName}-agent2"
|
|
ImageTagMutability: IMMUTABLE
|
|
EmptyOnDelete: true
|
|
ImageScanningConfiguration:
|
|
ScanOnPush: true
|
|
RepositoryPolicyText:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: AllowPullFromAccount
|
|
Effect: Allow
|
|
Principal:
|
|
AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
|
|
Action:
|
|
- ecr:BatchGetImage
|
|
- ecr:GetDownloadUrlForLayer
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-ecr-repository-agent2"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: ECR
|
|
|
|
# ========================================================================
|
|
# IAM MODULE - Security and Permissions
|
|
# ========================================================================
|
|
|
|
# Agent1 Execution Role (with permissions to invoke Agent2)
|
|
Agent1ExecutionRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: !Sub "${AWS::StackName}-agent1-execution-role"
|
|
AssumeRolePolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: AssumeRolePolicy
|
|
Effect: Allow
|
|
Principal:
|
|
Service: bedrock-agentcore.amazonaws.com
|
|
Action: sts:AssumeRole
|
|
Condition:
|
|
StringEquals:
|
|
aws:SourceAccount: !Ref AWS::AccountId
|
|
ArnLike:
|
|
aws:SourceArn: !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:*"
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess
|
|
Policies:
|
|
- PolicyName: Agent1ExecutionPolicy
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: ECRImageAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:BatchGetImage
|
|
- ecr:GetDownloadUrlForLayer
|
|
- ecr:BatchCheckLayerAvailability
|
|
Resource: !GetAtt ECRRepositoryAgent1.Arn
|
|
- Sid: ECRTokenAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:GetAuthorizationToken
|
|
Resource: "*"
|
|
- Sid: CloudWatchLogs
|
|
Effect: Allow
|
|
Action:
|
|
- logs:DescribeLogStreams
|
|
- logs:CreateLogGroup
|
|
- logs:DescribeLogGroups
|
|
- logs:CreateLogStream
|
|
- logs:PutLogEvents
|
|
Resource: "*"
|
|
- Sid: XRayTracing
|
|
Effect: Allow
|
|
Action:
|
|
- xray:PutTraceSegments
|
|
- xray:PutTelemetryRecords
|
|
- xray:GetSamplingRules
|
|
- xray:GetSamplingTargets
|
|
Resource: "*"
|
|
- Sid: CloudWatchMetrics
|
|
Effect: Allow
|
|
Resource: "*"
|
|
Action: cloudwatch:PutMetricData
|
|
Condition:
|
|
StringEquals:
|
|
cloudwatch:namespace: bedrock-agentcore
|
|
- Sid: GetAgentAccessToken
|
|
Effect: Allow
|
|
Action:
|
|
- bedrock-agentcore:GetWorkloadAccessToken
|
|
- bedrock-agentcore:GetWorkloadAccessTokenForJWT
|
|
- bedrock-agentcore:GetWorkloadAccessTokenForUserId
|
|
Resource:
|
|
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:workload-identity-directory/default"
|
|
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:workload-identity-directory/default/workload-identity/*"
|
|
- Sid: BedrockModelInvocation
|
|
Effect: Allow
|
|
Action:
|
|
- bedrock:InvokeModel
|
|
- bedrock:InvokeModelWithResponseStream
|
|
Resource: "*"
|
|
- Sid: InvokeAgent2Runtime
|
|
Effect: Allow
|
|
Action:
|
|
- bedrock-agentcore:InvokeAgentRuntime
|
|
Resource: !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:runtime/*"
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-agent1-execution-role"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: IAM
|
|
|
|
# Agent2 Execution Role (basic permissions)
|
|
Agent2ExecutionRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: !Sub "${AWS::StackName}-agent2-execution-role"
|
|
AssumeRolePolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: AssumeRolePolicy
|
|
Effect: Allow
|
|
Principal:
|
|
Service: bedrock-agentcore.amazonaws.com
|
|
Action: sts:AssumeRole
|
|
Condition:
|
|
StringEquals:
|
|
aws:SourceAccount: !Ref AWS::AccountId
|
|
ArnLike:
|
|
aws:SourceArn: !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:*"
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/BedrockAgentCoreFullAccess
|
|
Policies:
|
|
- PolicyName: Agent2ExecutionPolicy
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: ECRImageAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:BatchGetImage
|
|
- ecr:GetDownloadUrlForLayer
|
|
- ecr:BatchCheckLayerAvailability
|
|
Resource: !GetAtt ECRRepositoryAgent2.Arn
|
|
- Sid: ECRTokenAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:GetAuthorizationToken
|
|
Resource: "*"
|
|
- Sid: CloudWatchLogs
|
|
Effect: Allow
|
|
Action:
|
|
- logs:DescribeLogStreams
|
|
- logs:CreateLogGroup
|
|
- logs:DescribeLogGroups
|
|
- logs:CreateLogStream
|
|
- logs:PutLogEvents
|
|
Resource: "*"
|
|
- Sid: XRayTracing
|
|
Effect: Allow
|
|
Action:
|
|
- xray:PutTraceSegments
|
|
- xray:PutTelemetryRecords
|
|
- xray:GetSamplingRules
|
|
- xray:GetSamplingTargets
|
|
Resource: "*"
|
|
- Sid: CloudWatchMetrics
|
|
Effect: Allow
|
|
Resource: "*"
|
|
Action: cloudwatch:PutMetricData
|
|
Condition:
|
|
StringEquals:
|
|
cloudwatch:namespace: bedrock-agentcore
|
|
- Sid: GetAgentAccessToken
|
|
Effect: Allow
|
|
Action:
|
|
- bedrock-agentcore:GetWorkloadAccessToken
|
|
- bedrock-agentcore:GetWorkloadAccessTokenForJWT
|
|
- bedrock-agentcore:GetWorkloadAccessTokenForUserId
|
|
Resource:
|
|
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:workload-identity-directory/default"
|
|
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:workload-identity-directory/default/workload-identity/*"
|
|
- Sid: BedrockModelInvocation
|
|
Effect: Allow
|
|
Action:
|
|
- bedrock:InvokeModel
|
|
- bedrock:InvokeModelWithResponseStream
|
|
Resource: "*"
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-agent2-execution-role"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: IAM
|
|
|
|
# CodeBuild Service Role
|
|
CodeBuildRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: !Sub "${AWS::StackName}-codebuild-role"
|
|
AssumeRolePolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service: codebuild.amazonaws.com
|
|
Action: sts:AssumeRole
|
|
Policies:
|
|
- PolicyName: CodeBuildPolicy
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: CloudWatchLogs
|
|
Effect: Allow
|
|
Action:
|
|
- logs:CreateLogGroup
|
|
- logs:CreateLogStream
|
|
- logs:PutLogEvents
|
|
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/*"
|
|
- Sid: ECRAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:BatchCheckLayerAvailability
|
|
- ecr:GetDownloadUrlForLayer
|
|
- ecr:BatchGetImage
|
|
- ecr:GetAuthorizationToken
|
|
- ecr:PutImage
|
|
- ecr:InitiateLayerUpload
|
|
- ecr:UploadLayerPart
|
|
- ecr:CompleteLayerUpload
|
|
Resource:
|
|
- !GetAtt ECRRepositoryAgent1.Arn
|
|
- !GetAtt ECRRepositoryAgent2.Arn
|
|
- "*"
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-codebuild-role"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: IAM
|
|
|
|
# Lambda Custom Resource Role
|
|
CustomResourceRole:
|
|
Type: AWS::IAM::Role
|
|
Properties:
|
|
RoleName: !Sub "${AWS::StackName}-custom-resource-role"
|
|
AssumeRolePolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Effect: Allow
|
|
Principal:
|
|
Service: lambda.amazonaws.com
|
|
Action: sts:AssumeRole
|
|
ManagedPolicyArns:
|
|
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
|
|
Policies:
|
|
- PolicyName: CustomResourcePolicy
|
|
PolicyDocument:
|
|
Version: "2012-10-17"
|
|
Statement:
|
|
- Sid: ECRAccess
|
|
Effect: Allow
|
|
Action:
|
|
- ecr:ListImages
|
|
- ecr:BatchDeleteImage
|
|
- ecr:GetAuthorizationToken
|
|
- ecr:BatchGetImage
|
|
- ecr:GetDownloadUrlForLayer
|
|
- ecr:PutImage
|
|
- ecr:InitiateLayerUpload
|
|
- ecr:UploadLayerPart
|
|
- ecr:CompleteLayerUpload
|
|
Resource:
|
|
- !GetAtt ECRRepositoryAgent1.Arn
|
|
- !GetAtt ECRRepositoryAgent2.Arn
|
|
- Sid: CodeBuildAccess
|
|
Effect: Allow
|
|
Action:
|
|
- codebuild:StartBuild
|
|
- codebuild:BatchGetBuilds
|
|
- codebuild:BatchGetProjects
|
|
Resource:
|
|
- !GetAtt Agent1ImageBuildProject.Arn
|
|
- !GetAtt Agent2ImageBuildProject.Arn
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-custom-resource-role"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: IAM
|
|
|
|
# ========================================================================
|
|
# LAMBDA MODULE - Custom Resources
|
|
# ========================================================================
|
|
|
|
CodeBuildTriggerFunction:
|
|
Type: AWS::Lambda::Function
|
|
Properties:
|
|
FunctionName: !Sub "${AWS::StackName}-codebuild-trigger"
|
|
Description: "Triggers CodeBuild projects as CloudFormation custom resource"
|
|
Handler: index.handler
|
|
Role: !GetAtt CustomResourceRole.Arn
|
|
Runtime: python3.9
|
|
Timeout: 900
|
|
Code:
|
|
ZipFile: |
|
|
import boto3
|
|
import cfnresponse
|
|
import json
|
|
import logging
|
|
import time
|
|
|
|
logger = logging.getLogger()
|
|
logger.setLevel(logging.INFO)
|
|
|
|
def handler(event, context):
|
|
logger.info('Received event: %s', json.dumps(event))
|
|
|
|
try:
|
|
if event['RequestType'] == 'Delete':
|
|
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
|
|
return
|
|
|
|
project_name = event['ResourceProperties']['ProjectName']
|
|
wait_for_completion = event['ResourceProperties'].get('WaitForCompletion', 'true').lower() == 'true'
|
|
|
|
logger.info(f"Attempting to start CodeBuild project: {project_name}")
|
|
logger.info(f"Wait for completion: {wait_for_completion}")
|
|
|
|
# Start the CodeBuild project
|
|
codebuild = boto3.client('codebuild')
|
|
|
|
# First, verify the project exists
|
|
try:
|
|
project_info = codebuild.batch_get_projects(names=[project_name])
|
|
if not project_info['projects']:
|
|
raise Exception(f"CodeBuild project '{project_name}' not found")
|
|
logger.info(f"CodeBuild project '{project_name}' found")
|
|
except Exception as e:
|
|
logger.error(f"Error checking project existence: {str(e)}")
|
|
raise
|
|
|
|
response = codebuild.start_build(projectName=project_name)
|
|
build_id = response['build']['id']
|
|
|
|
logger.info(f"Successfully started build: {build_id}")
|
|
|
|
if not wait_for_completion:
|
|
cfnresponse.send(event, context, cfnresponse.SUCCESS, {
|
|
'BuildId': build_id,
|
|
'Status': 'STARTED'
|
|
})
|
|
return
|
|
|
|
# Wait for the build to complete
|
|
max_wait_time = context.get_remaining_time_in_millis() / 1000 - 30 # Leave 30s buffer
|
|
start_time = time.time()
|
|
|
|
while True:
|
|
if time.time() - start_time > max_wait_time:
|
|
error_message = f"Build {build_id} timed out"
|
|
logger.error(error_message)
|
|
cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': error_message})
|
|
return
|
|
|
|
build_response = codebuild.batch_get_builds(ids=[build_id])
|
|
build_status = build_response['builds'][0]['buildStatus']
|
|
|
|
if build_status == 'SUCCEEDED':
|
|
logger.info(f"Build {build_id} succeeded")
|
|
cfnresponse.send(event, context, cfnresponse.SUCCESS, {
|
|
'BuildId': build_id,
|
|
'Status': build_status
|
|
})
|
|
return
|
|
elif build_status in ['FAILED', 'FAULT', 'STOPPED', 'TIMED_OUT']:
|
|
error_message = f"Build {build_id} failed with status: {build_status}"
|
|
logger.error(error_message)
|
|
|
|
# Get build logs for debugging
|
|
try:
|
|
logs_info = build_response['builds'][0].get('logs', {})
|
|
if logs_info.get('groupName') and logs_info.get('streamName'):
|
|
logger.info(f"Build logs available in CloudWatch")
|
|
except Exception as log_error:
|
|
logger.warning(f"Could not get log information: {log_error}")
|
|
|
|
cfnresponse.send(event, context, cfnresponse.FAILED, {
|
|
'Error': error_message,
|
|
'BuildId': build_id
|
|
})
|
|
return
|
|
|
|
logger.info(f"Build {build_id} status: {build_status}")
|
|
time.sleep(30) # Check every 30 seconds
|
|
|
|
except Exception as e:
|
|
logger.error('Error: %s', str(e))
|
|
cfnresponse.send(event, context, cfnresponse.FAILED, {
|
|
'Error': str(e)
|
|
})
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-codebuild-trigger"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: Lambda
|
|
|
|
# ========================================================================
|
|
# CODEBUILD MODULE - Container Image Building
|
|
# ========================================================================
|
|
|
|
# Agent2 Build Project (build first as it's independent)
|
|
Agent2ImageBuildProject:
|
|
Type: AWS::CodeBuild::Project
|
|
Properties:
|
|
Name: !Sub "${AWS::StackName}-agent2-build"
|
|
Description: !Sub "Build agent2 Docker image for ${AWS::StackName}"
|
|
ServiceRole: !GetAtt CodeBuildRole.Arn
|
|
Artifacts:
|
|
Type: NO_ARTIFACTS
|
|
Environment:
|
|
Type: ARM_CONTAINER
|
|
ComputeType: BUILD_GENERAL1_LARGE
|
|
Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
|
|
PrivilegedMode: true
|
|
EnvironmentVariables:
|
|
- Name: AWS_DEFAULT_REGION
|
|
Value: !Ref AWS::Region
|
|
- Name: AWS_ACCOUNT_ID
|
|
Value: !Ref AWS::AccountId
|
|
- Name: IMAGE_REPO_NAME
|
|
Value: !Ref ECRRepositoryAgent2
|
|
- Name: IMAGE_TAG
|
|
Value: !Ref ImageTag
|
|
- Name: STACK_NAME
|
|
Value: !Ref AWS::StackName
|
|
Source:
|
|
Type: NO_SOURCE
|
|
BuildSpec: |
|
|
version: 0.2
|
|
phases:
|
|
pre_build:
|
|
commands:
|
|
- echo Logging in to Amazon ECR...
|
|
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
|
build:
|
|
commands:
|
|
- echo Build started on `date`
|
|
- echo Building the Docker image for agent2 ARM64...
|
|
|
|
# Create requirements.txt
|
|
- |
|
|
cat > requirements.txt << 'EOF'
|
|
strands-agents
|
|
boto3>=1.40.0
|
|
botocore>=1.40.0
|
|
bedrock-agentcore
|
|
EOF
|
|
|
|
# Create agent2.py - specialist agent that handles specific tasks
|
|
- |
|
|
cat > agent2.py << 'EOF'
|
|
from strands import Agent
|
|
import os
|
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
|
|
app = BedrockAgentCoreApp()
|
|
|
|
def create_specialist_agent() -> Agent:
|
|
"""Create a specialist agent that handles specific analytical tasks"""
|
|
system_prompt = """You are a specialist analytical agent.
|
|
You are an expert at analyzing data and providing detailed insights.
|
|
When asked questions, provide thorough, well-reasoned responses with specific details.
|
|
Focus on accuracy and completeness in your answers."""
|
|
|
|
return Agent(
|
|
system_prompt=system_prompt,
|
|
name="SpecialistAgent"
|
|
)
|
|
|
|
@app.entrypoint
|
|
async def invoke(payload=None):
|
|
"""Main entrypoint for agent2"""
|
|
try:
|
|
# Get the query from payload
|
|
query = payload.get("prompt", "Hello") if payload else "Hello"
|
|
|
|
# Create and use the specialist agent
|
|
agent = create_specialist_agent()
|
|
response = agent(query)
|
|
|
|
return {
|
|
"status": "success",
|
|
"agent": "agent2",
|
|
"response": response.message['content'][0]['text']
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"agent": "agent2",
|
|
"error": str(e)
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|
|
EOF
|
|
|
|
# Create Dockerfile
|
|
- |
|
|
cat > Dockerfile << 'EOF'
|
|
FROM public.ecr.aws/docker/library/python:3.11-slim
|
|
WORKDIR /app
|
|
|
|
COPY requirements.txt requirements.txt
|
|
RUN pip install -r requirements.txt
|
|
RUN pip install aws-opentelemetry-distro>=0.10.1
|
|
|
|
# Region will be set by AgentCore runtime environment automatically
|
|
|
|
# Create non-root user
|
|
RUN useradd -m -u 1000 bedrock_agentcore
|
|
USER bedrock_agentcore
|
|
|
|
EXPOSE 8080
|
|
EXPOSE 8000
|
|
|
|
COPY . .
|
|
|
|
CMD ["opentelemetry-instrument", "python", "-m", "agent2"]
|
|
EOF
|
|
|
|
# Build the image
|
|
- echo Building ARM64 image...
|
|
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
|
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
|
|
|
post_build:
|
|
commands:
|
|
- echo Build completed on `date`
|
|
- echo Pushing the Docker image...
|
|
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
|
- echo ARM64 Docker image pushed successfully
|
|
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-agent2-build"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: CodeBuild
|
|
|
|
# Agent1 Build Project (orchestrator that calls agent2)
|
|
Agent1ImageBuildProject:
|
|
Type: AWS::CodeBuild::Project
|
|
Properties:
|
|
Name: !Sub "${AWS::StackName}-agent1-build"
|
|
Description: !Sub "Build agent1 Docker image for ${AWS::StackName}"
|
|
ServiceRole: !GetAtt CodeBuildRole.Arn
|
|
Artifacts:
|
|
Type: NO_ARTIFACTS
|
|
Environment:
|
|
Type: ARM_CONTAINER
|
|
ComputeType: BUILD_GENERAL1_LARGE
|
|
Image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
|
|
PrivilegedMode: true
|
|
EnvironmentVariables:
|
|
- Name: AWS_DEFAULT_REGION
|
|
Value: !Ref AWS::Region
|
|
- Name: AWS_ACCOUNT_ID
|
|
Value: !Ref AWS::AccountId
|
|
- Name: IMAGE_REPO_NAME
|
|
Value: !Ref ECRRepositoryAgent1
|
|
- Name: IMAGE_TAG
|
|
Value: !Ref ImageTag
|
|
- Name: STACK_NAME
|
|
Value: !Ref AWS::StackName
|
|
Source:
|
|
Type: NO_SOURCE
|
|
BuildSpec: |
|
|
version: 0.2
|
|
phases:
|
|
pre_build:
|
|
commands:
|
|
- echo Logging in to Amazon ECR...
|
|
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
|
|
build:
|
|
commands:
|
|
- echo Build started on `date`
|
|
- echo Building the Docker image for agent1 ARM64...
|
|
|
|
# Create requirements.txt
|
|
- |
|
|
cat > requirements.txt << 'EOF'
|
|
strands-agents
|
|
boto3>=1.40.0
|
|
botocore>=1.40.0
|
|
bedrock-agentcore
|
|
EOF
|
|
|
|
# Create agent1.py - orchestrator agent with tool to call agent2
|
|
- |
|
|
cat > agent1.py << 'EOF'
|
|
from strands import Agent, tool
|
|
from typing import Dict, Any
|
|
import boto3
|
|
import json
|
|
import os
|
|
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
|
|
app = BedrockAgentCoreApp()
|
|
|
|
# Environment variable for Agent2 ARN (will be set by CloudFormation)
|
|
AGENT2_ARN = os.getenv('AGENT2_ARN', '')
|
|
|
|
def invoke_agent2(query: str) -> str:
|
|
"""Helper function to invoke agent2 using boto3"""
|
|
import uuid
|
|
try:
|
|
# Get region from environment or use default
|
|
region = os.getenv('AWS_REGION', 'us-west-2')
|
|
agentcore_client = boto3.client('bedrock-agentcore', region_name=region)
|
|
|
|
# Invoke agent2 runtime (using AWS sample format)
|
|
response = agentcore_client.invoke_agent_runtime(
|
|
agentRuntimeArn=AGENT2_ARN,
|
|
qualifier="DEFAULT",
|
|
payload=json.dumps({"prompt": query})
|
|
)
|
|
|
|
# Handle streaming response (text/event-stream)
|
|
if "text/event-stream" in response.get("contentType", ""):
|
|
result = ""
|
|
for line in response["response"].iter_lines(chunk_size=10):
|
|
if line:
|
|
line = line.decode("utf-8")
|
|
# Remove 'data: ' prefix if present
|
|
if line.startswith("data: "):
|
|
line = line[6:]
|
|
result += line
|
|
return result
|
|
|
|
# Handle JSON response
|
|
elif response.get("contentType") == "application/json":
|
|
content = []
|
|
for chunk in response.get("response", []):
|
|
content.append(chunk.decode('utf-8'))
|
|
response_data = json.loads(''.join(content))
|
|
return json.dumps(response_data)
|
|
|
|
# Handle other response types
|
|
else:
|
|
response_body = response['response'].read()
|
|
return response_body.decode('utf-8')
|
|
|
|
except Exception as e:
|
|
import traceback
|
|
error_details = traceback.format_exc()
|
|
return f"Error invoking agent2: {str(e)}\nDetails: {error_details}"
|
|
|
|
@tool
|
|
def call_specialist_agent(query: str) -> Dict[str, Any]:
|
|
"""
|
|
Call the specialist agent (agent2) for detailed analysis or complex tasks.
|
|
Use this tool when you need expert analysis or detailed information.
|
|
|
|
Args:
|
|
query: The question or task to send to the specialist agent
|
|
|
|
Returns:
|
|
The specialist agent's response
|
|
"""
|
|
result = invoke_agent2(query)
|
|
return {
|
|
"status": "success",
|
|
"content": [{"text": result}]
|
|
}
|
|
|
|
def create_orchestrator_agent() -> Agent:
|
|
"""Create the orchestrator agent with the tool to call agent2"""
|
|
system_prompt = """You are an orchestrator agent.
|
|
You can handle simple queries directly, but for complex analytical tasks,
|
|
you should delegate to the specialist agent using the call_specialist_agent tool.
|
|
|
|
Use the specialist agent when:
|
|
- The query requires detailed analysis
|
|
- The query is about complex topics
|
|
- The user explicitly asks for expert analysis
|
|
|
|
Handle simple queries (greetings, basic questions) yourself."""
|
|
|
|
return Agent(
|
|
tools=[call_specialist_agent],
|
|
system_prompt=system_prompt,
|
|
name="OrchestratorAgent"
|
|
)
|
|
|
|
@app.entrypoint
|
|
async def invoke(payload=None):
|
|
"""Main entrypoint for agent1"""
|
|
try:
|
|
# Get the query from payload
|
|
query = payload.get("prompt", "Hello, how are you?") if payload else "Hello, how are you?"
|
|
|
|
# Create and use the orchestrator agent
|
|
agent = create_orchestrator_agent()
|
|
response = agent(query)
|
|
|
|
return {
|
|
"status": "success",
|
|
"agent": "agent1",
|
|
"response": response.message['content'][0]['text']
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"agent": "agent1",
|
|
"error": str(e)
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
app.run()
|
|
EOF
|
|
|
|
# Create Dockerfile
|
|
- |
|
|
cat > Dockerfile << 'EOF'
|
|
FROM public.ecr.aws/docker/library/python:3.11-slim
|
|
WORKDIR /app
|
|
|
|
COPY requirements.txt requirements.txt
|
|
RUN pip install -r requirements.txt
|
|
RUN pip install aws-opentelemetry-distro>=0.10.1
|
|
|
|
# Region will be set by AgentCore runtime environment automatically
|
|
|
|
# Create non-root user
|
|
RUN useradd -m -u 1000 bedrock_agentcore
|
|
USER bedrock_agentcore
|
|
|
|
EXPOSE 8080
|
|
EXPOSE 8000
|
|
|
|
COPY . .
|
|
|
|
CMD ["opentelemetry-instrument", "python", "-m", "agent1"]
|
|
EOF
|
|
|
|
# Build the image
|
|
- echo Building ARM64 image...
|
|
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
|
|
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
|
|
|
post_build:
|
|
commands:
|
|
- echo Build completed on `date`
|
|
- echo Pushing the Docker image...
|
|
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
|
|
- echo ARM64 Docker image pushed successfully
|
|
|
|
Tags:
|
|
- Key: Name
|
|
Value: !Sub "${AWS::StackName}-agent1-build"
|
|
- Key: StackName
|
|
Value: !Ref AWS::StackName
|
|
- Key: Module
|
|
Value: CodeBuild
|
|
|
|
# CUSTOM RESOURCE - Trigger Agent2 Image Build
|
|
TriggerAgent2ImageBuild:
|
|
Type: Custom::CodeBuildTrigger
|
|
DependsOn:
|
|
- ECRRepositoryAgent2
|
|
- Agent2ImageBuildProject
|
|
- CodeBuildTriggerFunction
|
|
Properties:
|
|
ServiceToken: !GetAtt CodeBuildTriggerFunction.Arn
|
|
ProjectName: !Ref Agent2ImageBuildProject
|
|
WaitForCompletion: "true"
|
|
|
|
# CUSTOM RESOURCE - Trigger Agent1 Image Build
|
|
TriggerAgent1ImageBuild:
|
|
Type: Custom::CodeBuildTrigger
|
|
DependsOn:
|
|
- ECRRepositoryAgent1
|
|
- Agent1ImageBuildProject
|
|
- CodeBuildTriggerFunction
|
|
Properties:
|
|
ServiceToken: !GetAtt CodeBuildTriggerFunction.Arn
|
|
ProjectName: !Ref Agent1ImageBuildProject
|
|
WaitForCompletion: "true"
|
|
|
|
# ========================================================================
|
|
# AGENTCORE MODULE - Runtime Resources
|
|
# ========================================================================
|
|
|
|
# Agent2 Runtime (deploy first as agent1 depends on it)
|
|
Agent2Runtime:
|
|
Type: AWS::BedrockAgentCore::Runtime
|
|
DependsOn:
|
|
- TriggerAgent2ImageBuild
|
|
Properties:
|
|
AgentRuntimeName: !Sub
|
|
- "${StackNameUnderscore}_${Agent2Name}"
|
|
- StackNameUnderscore: !Join ["_", !Split ["-", !Ref "AWS::StackName"]]
|
|
AgentRuntimeArtifact:
|
|
ContainerConfiguration:
|
|
ContainerUri: !Sub "${ECRRepositoryAgent2.RepositoryUri}:${ImageTag}"
|
|
RoleArn: !GetAtt Agent2ExecutionRole.Arn
|
|
NetworkConfiguration:
|
|
NetworkMode: !Ref NetworkMode
|
|
Description: !Sub "Specialist agent runtime for ${AWS::StackName}"
|
|
|
|
# Agent1 Runtime (orchestrator with agent2 ARN as environment variable)
|
|
Agent1Runtime:
|
|
Type: AWS::BedrockAgentCore::Runtime
|
|
DependsOn:
|
|
- TriggerAgent1ImageBuild
|
|
- Agent2Runtime
|
|
Properties:
|
|
AgentRuntimeName: !Sub
|
|
- "${StackNameUnderscore}_${Agent1Name}"
|
|
- StackNameUnderscore: !Join ["_", !Split ["-", !Ref "AWS::StackName"]]
|
|
AgentRuntimeArtifact:
|
|
ContainerConfiguration:
|
|
ContainerUri: !Sub "${ECRRepositoryAgent1.RepositoryUri}:${ImageTag}"
|
|
RoleArn: !GetAtt Agent1ExecutionRole.Arn
|
|
NetworkConfiguration:
|
|
NetworkMode: !Ref NetworkMode
|
|
Description: !Sub "Orchestrator agent runtime for ${AWS::StackName}"
|
|
EnvironmentVariables:
|
|
AGENT2_ARN: !GetAtt Agent2Runtime.AgentRuntimeArn
|
|
|
|
# ============================================================================
|
|
# OUTPUTS SECTION
|
|
# ============================================================================
|
|
Outputs:
|
|
# AGENT1 (ORCHESTRATOR) OUTPUTS
|
|
Agent1RuntimeId:
|
|
Description: "ID of agent1 (orchestrator) runtime"
|
|
Value: !GetAtt Agent1Runtime.AgentRuntimeId
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent1RuntimeId"
|
|
|
|
Agent1RuntimeArn:
|
|
Description: "ARN of agent1 (orchestrator) runtime"
|
|
Value: !GetAtt Agent1Runtime.AgentRuntimeArn
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent1RuntimeArn"
|
|
|
|
Agent1ECRRepositoryUri:
|
|
Description: "URI of the ECR repository for agent1"
|
|
Value: !GetAtt ECRRepositoryAgent1.RepositoryUri
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent1ECRRepositoryUri"
|
|
|
|
Agent1ExecutionRoleArn:
|
|
Description: "ARN of agent1 execution role"
|
|
Value: !GetAtt Agent1ExecutionRole.Arn
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent1ExecutionRoleArn"
|
|
|
|
# AGENT2 (SPECIALIST) OUTPUTS
|
|
Agent2RuntimeId:
|
|
Description: "ID of agent2 (specialist) runtime"
|
|
Value: !GetAtt Agent2Runtime.AgentRuntimeId
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent2RuntimeId"
|
|
|
|
Agent2RuntimeArn:
|
|
Description: "ARN of agent2 (specialist) runtime"
|
|
Value: !GetAtt Agent2Runtime.AgentRuntimeArn
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent2RuntimeArn"
|
|
|
|
Agent2ECRRepositoryUri:
|
|
Description: "URI of the ECR repository for agent2"
|
|
Value: !GetAtt ECRRepositoryAgent2.RepositoryUri
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent2ECRRepositoryUri"
|
|
|
|
Agent2ExecutionRoleArn:
|
|
Description: "ARN of agent2 execution role"
|
|
Value: !GetAtt Agent2ExecutionRole.Arn
|
|
Export:
|
|
Name: !Sub "${AWS::StackName}-Agent2ExecutionRoleArn"
|