Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ The example agent is built with the [Strands Agents framework](https://github.co
Flow:
1. Browser loads React app from CloudFront/S3
2. User authenticates with Cognito, receives JWT token
3. Browser calls AgentCore directly with JWT Bearer token
4. AgentCore validates JWT and processes agent requests
3. Browser calls AgentCore Runtime directly with JWT Bearer token
4. AgentCore Runtime validates JWT and executes agent
5. Agent processes requests and invokes Amazon Bedrock foundation model
6. AgentCore Memory stores and retrieves conversation context
7. Generative AI observability provides monitoring dashboards and insights

## Quick Start

Expand Down
66 changes: 44 additions & 22 deletions agent/strands_agent.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
from strands import Agent, tool
from strands_tools import calculator # Import the calculator tool
import json
import os
from typing import Final
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from strands import Agent, tool
from strands_tools import calculator # Import the calculator tool
from strands.models import BedrockModel

# Constant variables
AGENTCORE_MEMORY_ID: Final[str] = os.environ["AGENTCORE_MEMORY_ID"]
MODEL_ID: Final[str] = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
MODEL: Final[BedrockModel] = BedrockModel(model_id=MODEL_ID)

# Create the AgentCore app
app = BedrockAgentCoreApp()


# Create a custom tool
@tool
def weather():
"""Get the current weather. Always returns sunny weather."""
return "It's sunny and 72°F today!"

model_id = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
model = BedrockModel(
model_id=model_id,
)

agent = Agent(
model=model,
tools=[calculator, weather],
system_prompt="You're a helpful assistant. You can do simple math calculation, and tell the weather.",
callback_handler=None
)

@app.entrypoint
async def agent_invocation(payload):
Expand All @@ -41,28 +40,51 @@ async def agent_invocation(payload):
if isinstance(payload, str):
payload = json.loads(payload)

# Extract the prompt from the payload
# Try AWS SDK format first (most common for production): {"input": {"prompt": "..."}}
# Fall back to direct format: {"prompt": "..."}
# Extract the prompt, actorId, and sessionId from the payload
# Try AWS SDK format first (most common for production): {"input": {"prompt": "...", "actorId": "...", "sessionId": "..."}}
# Fall back to direct format: {"prompt": "...", "actorId": "...", "sessionId": "..."}
user_input = None
actor_id = None
session_id = None

if isinstance(payload, dict):
if "input" in payload and isinstance(payload["input"], dict):
user_input = payload["input"].get("prompt")
actor_id = payload["input"].get("actorId")
session_id = payload["input"].get("sessionId")
else:
user_input = payload.get("prompt")
actor_id = payload.get("actorId")
session_id = payload.get("sessionId")

# Validate required fields
if not user_input:
raise ValueError(f"No prompt found in payload. Expected {{'prompt': '...'}} or {{'input': {{'prompt': '...'}}}}. Received: {payload}")

# response = agent(user_input)
# response_text = response.message['content'][0]['text']
if not actor_id or not session_id:
raise ValueError(f"No actorId or sessionId found in payload. Received: {payload}")

agent = Agent(
model=MODEL,
tools=[calculator, weather],
system_prompt="You're a helpful assistant. You can do simple math calculation, and tell the weather.",
session_manager=AgentCoreMemorySessionManager(
agentcore_memory_config=AgentCoreMemoryConfig(
memory_id=AGENTCORE_MEMORY_ID,
session_id=session_id,
actor_id=actor_id,
)
),
callback_handler=None,
)

# Stream response
stream = agent.stream_async(user_input)
async for event in stream:
if (event.get('event',{}).get('contentBlockDelta',{}).get('delta',{}).get('text')):
print(event.get('event',{}).get('contentBlockDelta',{}).get('delta',{}).get('text'))
yield (event.get('event',{}).get('contentBlockDelta',{}).get('delta',{}).get('text'))
if event.get("event", {}).get("contentBlockDelta", {}).get("delta", {}).get("text"):
print(event.get("event", {}).get("contentBlockDelta", {}).get("delta", {}).get("text"))
yield (event.get("event", {}).get("contentBlockDelta", {}).get("delta", {}).get("text"))

# return response_text

if __name__ == "__main__":
app.run()
60 changes: 60 additions & 0 deletions cdk/lib/auth-stack.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as cdk from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class AuthStack extends cdk.Stack {
public readonly userPool: cognito.UserPool;
public readonly userPoolClient: cognito.UserPoolClient;
public readonly identityPool: cognito.CfnIdentityPool;

constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
Expand Down Expand Up @@ -48,6 +50,58 @@ export class AuthStack extends cdk.Stack {
preventUserExistenceErrors: true,
});

// Cognito Identity Pool for AWS SDK access
this.identityPool = new cognito.CfnIdentityPool(this, 'AgentCoreIdentityPool', {
identityPoolName: 'agentcore-identity-pool',
allowUnauthenticatedIdentities: false,
cognitoIdentityProviders: [{
clientId: this.userPoolClient.userPoolClientId,
providerName: this.userPool.userPoolProviderName,
}],
});

// IAM Role for authenticated users (frontend)
const authenticatedRole = new iam.Role(this, 'CognitoAuthenticatedRole', {
roleName: 'AgentCoreAuthenticatedRole',
description: 'Role for authenticated Cognito users to access AgentCore Memory',
assumedBy: new iam.FederatedPrincipal(
'cognito-identity.amazonaws.com',
{
'StringEquals': {
'cognito-identity.amazonaws.com:aud': this.identityPool.ref,
},
'ForAnyValue:StringLike': {
'cognito-identity.amazonaws.com:amr': 'authenticated',
},
},
'sts:AssumeRoleWithWebIdentity'
),
});

// Add policy for AgentCore Memory read-only access
const region = cdk.Stack.of(this).region;
const account = cdk.Stack.of(this).account;

authenticatedRole.addToPolicy(new iam.PolicyStatement({
sid: 'AgentCoreMemoryReadAccess',
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:ListSessions',
'bedrock-agentcore:ListEvents',
],
resources: [
`arn:aws:bedrock-agentcore:${region}:${account}:memory/*`,
],
}));

// Attach roles to Identity Pool
new cognito.CfnIdentityPoolRoleAttachment(this, 'IdentityPoolRoleAttachment', {
identityPoolId: this.identityPool.ref,
roles: {
authenticated: authenticatedRole.roleArn,
},
});

// Outputs
new cdk.CfnOutput(this, 'UserPoolId', {
value: this.userPool.userPoolId,
Expand All @@ -66,5 +120,11 @@ export class AuthStack extends cdk.Stack {
description: 'Cognito User Pool Client ID',
exportName: 'AgentCoreUserPoolClientId',
});

new cdk.CfnOutput(this, 'IdentityPoolId', {
value: this.identityPool.ref,
description: 'Cognito Identity Pool ID',
exportName: 'AgentCoreIdentityPoolId',
});
}
}
16 changes: 16 additions & 0 deletions cdk/lib/infra-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ export class AgentCoreInfraStack extends cdk.Stack {
resources: ['*'],
}));

// AgentCore Memory Access
agentRole.addToPolicy(new iam.PolicyStatement({
sid: 'AgentCoreMemoryAccess',
effect: iam.Effect.ALLOW,
actions: [
'bedrock-agentcore:ListSessions',
'bedrock-agentcore:CreateEvent',
'bedrock-agentcore:GetEvent',
'bedrock-agentcore:ListEvents',
'bedrock-agentcore:DeleteEvent',
],
resources: [
`arn:aws:bedrock-agentcore:${this.region}:${this.account}:memory/*`,
],
}));

// Create S3 bucket for CodeBuild source
const sourceBucket = new s3.Bucket(this, 'SourceBucket', {
bucketName: `bedrock-agentcore-sources-${this.account}-${this.region}`,
Expand Down
24 changes: 18 additions & 6 deletions cdk/lib/runtime-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface AgentCoreStackProps extends cdk.StackProps {

export class AgentCoreStack extends cdk.Stack {
public readonly agentRuntimeArn: string;
public readonly memoryId: string;

constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
super(scope, id, props);
Expand Down Expand Up @@ -49,6 +50,16 @@ export class AgentCoreStack extends cdk.Stack {
const region = cdk.Stack.of(this).region;
const discoveryUrl = `https://cognito-idp.${region}.amazonaws.com/${props.userPool.userPoolId}/.well-known/openid-configuration`;

// Create AgentCore Memory for short-term conversation history
const agentMemory = new bedrockagentcore.CfnMemory(this, 'AgentMemory', {
name: 'strands_agent_memory',
eventExpiryDuration: 365,
description: 'Short-term memory store for conversation history',
});

// Store Memory ID for exports
this.memoryId = agentMemory.attrMemoryId;

// Step 1: Upload only the essential agent files (exclude heavy directories)
const agentSourceUpload = new s3deploy.BucketDeployment(this, 'AgentSourceUpload', {
sources: [s3deploy.Source.asset('../agent', {
Expand Down Expand Up @@ -244,8 +255,9 @@ async function sendResponse(event, status, data, reason) {
},
},

// Environment variables (if needed)
// Environment variables
environmentVariables: {
AGENTCORE_MEMORY_ID: agentMemory.attrMemoryId,
LOG_LEVEL: 'INFO',
IMAGE_VERSION: new Date().toISOString(),
},
Expand All @@ -262,10 +274,6 @@ async function sendResponse(event, status, data, reason) {
// Store runtime info for frontend
this.agentRuntimeArn = agentRuntime.attrAgentRuntimeArn;





new cdk.CfnOutput(this, 'AgentRuntimeArn', {
value: agentRuntime.attrAgentRuntimeArn,
description: 'AgentCore Runtime ARN',
Expand All @@ -284,6 +292,10 @@ async function sendResponse(event, status, data, reason) {
exportName: 'AgentCoreRegion',
});


new cdk.CfnOutput(this, 'AgentMemoryId', {
value: agentMemory.attrMemoryId,
description: 'AgentCore Memory ID for session management',
exportName: 'AgentCoreMemoryId',
});
}
}
32 changes: 16 additions & 16 deletions cdk/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading