AWS-AI
The state of AI in AWS
Bedrock + Strands + Agentcore
- bedrock: provides inference for models on AWS
- strands: agent library for orchestrating agents, agentic loop, and subagents.
- agentcore: CLI tool that reads from a strands agent project to create a containerized API for it, and then deploy it to your AWS account as real containerized infra via S3, CodeBuild, ECR to store the image, and Agentcore to run the agent.
Agentcore

Agentcore providers users a front-facing abstraction over API gateway for running inference on LLM models. It has these properties:
- serverless: agents on an Agent Core API run on serverless micro VMs managed by AWS.
- monitoring: out-of-the-box AI observability
- MCP: convert any Lambda or APIs into MCP
- authorization add-ons: integrates with Cognito to have protected authenticated access.
Strands
Strands is a Python and TypeScript agent framework that is AWS-native and provider-agnostic.


Strands was developed by AWS as an agentic harness that works well with good models, treating models as the main driver and trying to avoid overengineering as models become more capable.
NOTE
Strands follows one simple idea: let the model drive, while the developer handles declaratively defining boundaries, human-in-the-loop, and guardrails.
Strands offer these features:
- orchestration: handles the agentic loop and orchestration layer
- Tool + MCP integration: easily add custom tools or MCPs for use, with many built-in tools
- session/memory integration
- multi-agent collaboration
- observability
- evals
AWS agent toolkit
The AWS Agent Toolkit is a set of MCP servers, skills, and Claude Code plugins that give your AI agents access to the AWS docs for better AWS development.
Plugins

MCP server
{
"mcpServers": {
"aws-mcp": {
"command": "uvx",
"args": [
"mcp-proxy-for-aws@latest",
"https://api.aws",
"--metadata", "AWS_REGION=us-east-1"
]
}
}
}
Bedrock fundamentals
Amazon Bedrock offers a range of powerful capabilities for building AI applications, including:
- Foundation Model Service: Access and interact with large-scale pretrained models from providers like Anthropic, Amazon, and Mistral through a unified API.
- Model Inference and Prompts: Generate outputs from inputs using structured prompts, with control over parameters like token limits and response temperature to optimize behavior.
- Embeddings and Knowledge Bases: Create vector-based representations of text and images to support advanced search and retrieval, enhancing accuracy with Retrieval-Augmented Generation (RAG) to ground AI responses in specific data.
- Orchestration and Agents: Coordinate models with enterprise systems to automate tasks such as code fixing, testing, and decision-making workflows.
- Model Customization and Evaluation: Fine-tune models, perform pre-training, optimize hyperparameters, and define custom evaluation metrics to tailor AI performance.
- Throughput Provisioning: Allocate dedicated capacity for token input/output to ensure efficient processing.
Knowledge base
CHeck it out!
Knowledge bases in Bedrock allow you to put file data into an S3 bucket and then create a knowledge base of objects from that S3 bucket to use for RAG with bedrock inference.
- What you manage: what data sources to select and feed into the knowledge base
- What AWS manages: creating the vector store from data sources like S3, and similarity search for AI to retrieve content from the knowledge basevia RAG.
NOTE
Knowledge Bases are useful because they automate the process of indexing and retrieving the data to ease RAG implementations.
You can then programmatically run inference of a bedrock model against a knowledge base using the CLI or SDK.

Creating a knowledge base
- Create an S3 bucket and upload your files to it.
- Navigate to the Amazon Bedrock Console and select Knowledge Bases under the Builder tools section
- Click Create Knowledge Base with Vector Store and give it a unique name

- Choose S3 as the data source, click "next"

- Select an embeddings model

- Select Amazon S3 vectors as the vector store of choice because it's cost-effective.

-
Fill out the IAM permissions section:
- Choose “Create and use a new service role” or “Use an existing service role”.
- Ensure the service role has the necessary permissions for Bedrock access.
-
Grab the knowledge base ID so you can programmatically access the knowledge base.
Bedrock agents
Bedrock agents offer lightweight orchestration systems to orchestrate different subagents with one main inference provider.

Here are the different tools a bedrock agent has access to:
- code interpreter: using Python to create and execute code to achieve the goal
- knowledge base retrieval: attach a knowledge base and allow the agent to use RAG to retrieve data from that knowledge base
Bedrock Guardrails
Bedrock Guardrails is a security feature that helps organizations implement safeguards for their AI inference by defining custom content policies and safety moderation logic.

THese are the differnet types of guardrails you ahve:

Bedrock in Python
Basics
Installation and setup
Use boto3:
- Install
boto3
pip install boto3
- Initialize the
boto3bedrock client:
import boto3
import json
from botocore.exceptions import ClientError
# Set the AWS Region
region = "us-east-1"
# Initialize the Bedrock Runtime client
client = boto3.client("bedrock-runtime", region_name=region)
Basic inference
Basic inference involves supply the following info to client.invoke_model() function:
- inference parameters: the input text, system prompt, and any hyperparameters
- model Id: the specific model identifier to use
- request and response content type
# Define the model ID for Amazon Titan Express v1
model_id = "amazon.titan-text-express-v1"
# Define the input prompt
prompt = """
Command: Compose an email from Tom, Customer Service Manager, to the customer "Nancy"
who provided negative feedback on the service provided by our customer support
Engineer"""
# Configure inference parameters
inference_parameters = {
"inputText": prompt,
"textGenerationConfig": {
"maxTokenCount": 512, # Limit the response length
"temperature": 0.5, # Control the randomness of the output
},
}
# Convert the request payload to JSON
request_payload = json.dumps(inference_parameters)
try:
# Invoke the model
response = client.invoke_model(
modelId=model_id,
body=request_payload,
contentType="application/json",
accept="application/json"
)
# Decode the response body
response_body = json.loads(response["body"].read())
# Extract and print the generated text
generated_text = response_body["results"][0]["outputText"]
print("Generated Text:\n", generated_text)
except ClientError as e:
print(f"ClientError: {e.response['Error']['Message']}")
except Exception as e:
print(f"An error occurred: {e}")
inference parameters
To fine-tune the behavior of the model output, you can adjust parameters like temperature and maxTokenCount:
temperature: This parameter controls the randomness of the output. Lower values increase the determination of the output, and higher values increase the variability.MaxTokenCount: Sets the maximum length of the generated output.
inference_parameters = {
"inputText": prompt,
"textGenerationConfig": {
"maxTokenCount": 256, # Limit the response length
"temperature": 0.7, # Control the randomness of the output
},
}
Converse API
The converse API uses messages format:
import boto3
import json
bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')
model_id = "us.amazon.nova-lite-v1:0"
def invoke(system_prompt: str, prompt: str):
response = bedrock_runtime.converse(
modelId=model_id,
system=system_prompt,
messages=[
{
"role": "user",
"content": [{"text": prompt}]
}
],
inferenceConfig={
"temperature": 0.7,
"maxTokens": 2000
}
)
output_text = response['output']['message']['content'][0]['text']
return output_text
Here's a complete example
import boto3
import json
def use_converse_api():
bedrock_runtime = boto3.client('bedrock-runtime', region_name='us-east-1')
model_id = "us.amazon.nova-lite-v1:0"
# Define a system prompt to set model behavior
system_prompt = [
{
"text": "You are a helpful technical assistant who explains concepts clearly and concisely."
}
]
# User message
user_message = "What is serverless computing?"
print("Using Bedrock Converse API")
print("=" * 60)
print(f"System Prompt: {system_prompt[0]['text']}")
print(f"User Message: {user_message}\n")
try:
# Use the Converse API
response = bedrock_runtime.converse(
modelId=model_id,
system=system_prompt,
messages=[
{
"role": "user",
"content": [{"text": user_message}]
}
],
inferenceConfig={
"temperature": 0.7,
"maxTokens": 2000
}
)
# Extract the response
output_text = response['output']['message']['content'][0]['text']
print("Assistant Response:")
print(output_text)
# Display token usage
usage = response.get('usage', {})
print(f"\nToken Usage:")
print(f" Input tokens: {usage.get('inputTokens', 'N/A')}")
print(f" Output tokens: {usage.get('outputTokens', 'N/A')}")
print(f" Total tokens: {usage.get('totalTokens', 'N/A')}")
print(f"\nStop Reason: {response['stopReason']}")
return response
except Exception as e:
print(f"Error using Converse API: {e}")
raise
if __name__ == "__main__":
use_converse_api()
Using tools
import json
import boto3
# ---------------------------------------------------------------------------
# Step 1: Define your local Python functions
# ---------------------------------------------------------------------------
# These are regular Python functions. The model will never call them directly.
# Instead, the model will ASK us to call them by returning a tool_use block.
def get_weather(location, unit="fahrenheit"):
"""
Simulate fetching weather data for a location.
In a real app, this would call a weather API like OpenWeatherMap.
"""
# Fake weather data for the demo
weather_data = {
"location": location,
"temperature": 58 if unit == "fahrenheit" else 14,
"unit": unit,
"condition": "Partly cloudy",
"humidity": "72%",
"wind": "8 mph NW",
}
return weather_data
# ---------------------------------------------------------------------------
# Step 2: Describe your functions as "tools" for the model
# ---------------------------------------------------------------------------
# The model needs a description of each tool so it knows:
# - What the tool does (description)
# - What inputs it expects (inputSchema)
#
# This is like writing documentation so someone else can use your function.
TOOL_CONFIG = {
"tools": [
{
"toolSpec": {
"name": "get_weather",
"description": "Get the current weather for a given location.",
"inputSchema": {
"json": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. 'San Francisco, CA'",
},
"unit": {
"type": "string",
"enum": ["fahrenheit", "celsius"],
"description": "Temperature unit (default: fahrenheit)",
},
},
"required": ["location"],
}
},
}
}
]
}
# ---------------------------------------------------------------------------
# Step 3: Map tool names to actual Python functions
# ---------------------------------------------------------------------------
# When the model asks to use a tool, we look up the function by name here.
TOOL_FUNCTIONS = {
"get_weather": get_weather,
}
def run_tool(tool_name, tool_input):
"""
Look up a tool by name and call it with the provided input.
Returns the result as a dictionary.
"""
func = TOOL_FUNCTIONS.get(tool_name)
if func is None:
return {"error": f"Unknown tool: {tool_name}"}
# ** unpacks the dict into keyword arguments:
# get_weather(**{"location": "Seattle"}) → get_weather(location="Seattle")
return func(**tool_input)
# ---------------------------------------------------------------------------
# Step 4: The main tool use loop
# ---------------------------------------------------------------------------
def tool_use_demo():
# Create the Bedrock client
bedrock = boto3.client("bedrock-runtime", region_name="us-east-1")
model_id = "us.amazon.nova-lite-v1:0"
user_message = "What's the weather like in Seattle right now?"
print("Bedrock Tool Use Demo")
print("=" * 60)
print(f"User: {user_message}\n")
# Start the conversation with the user's message
messages = [
{
"role": "user",
"content": [{"text": user_message}],
}
]
# --- First API call ---
# Send the message AND the tool definitions to the model.
# The model will look at the question, look at the available tools,
# and decide if it needs to call one.
print("[Step 1] Sending message to model with tool definitions...")
response = bedrock.converse(
modelId=model_id,
messages=messages,
toolConfig=TOOL_CONFIG,
inferenceConfig={"temperature": 0.0, "maxTokens": 300},
)
stop_reason = response["stopReason"]
assistant_message = response["output"]["message"]
print(f" Model responded with stop reason: {stop_reason}")
# --- Check: did the model ask to use a tool? ---
if stop_reason == "tool_use":
# The model wants to call a tool. Let's find the toolUse block.
tool_use_block = None
for block in assistant_message["content"]:
if "toolUse" in block:
tool_use_block = block["toolUse"]
break
tool_name = tool_use_block["name"]
tool_input = tool_use_block["input"]
tool_use_id = tool_use_block["toolUseId"]
print(f"\n[Step 2] Model wants to call: {tool_name}")
print(f" With arguments: {json.dumps(tool_input, indent=2)}")
# --- Run the actual function ---
result = run_tool(tool_name, tool_input)
print(f"\n[Step 3] Function returned: {json.dumps(result, indent=2)}")
# --- Send the result back to the model ---
# We add the assistant's message (with the tool request) to the history,
# then add a user message containing the tool result.
messages.append(assistant_message)
messages.append({
"role": "user",
"content": [
{
"toolResult": {
"toolUseId": tool_use_id,
"content": [{"json": result}],
}
}
],
})
print("\n[Step 4] Sending tool result back to model...")
final_response = bedrock.converse(
modelId=model_id,
messages=messages,
toolConfig=TOOL_CONFIG,
inferenceConfig={"temperature": 0.0, "maxTokens": 300},
)
final_text = final_response["output"]["message"]["content"][0]["text"]
print(f"\nAssistant: {final_text}")
elif stop_reason == "end_turn":
# The model answered directly without needing a tool
print(f"\nAssistant: {assistant_message['content'][0]['text']}")
if __name__ == "__main__":
tool_use_demo()
RAG

- Grab a knowledge base ID from an existing knowledge base in AWS.
- Use the
boto3library for bedrock with RAG.
import boto3
from botocore.config import Config
# REPLACE THIS with your Knowledge Base ID
KNOWLEDGE_BASE_ID = "" # Example: "ABCDEFGHIJ"
# REPLACE THIS with your model ID
MODEL_ID = "us.amazon.nova-lite-v1:0"
def query_knowledge_base(question):
bedrock_agent_runtime = boto3.client(
'bedrock-agent-runtime',
region_name='us-east-1'
)
print("Querying Bedrock Knowledge Base")
print("=" * 60)
print(f"Knowledge Base ID: {KNOWLEDGE_BASE_ID}")
print(f"Question: {question}\n")
try:
# Use retrieve_and_generate to query the Knowledge Base
response = bedrock_agent_runtime.retrieve_and_generate(
input={
'text': question
},
retrieveAndGenerateConfiguration={
'type': 'KNOWLEDGE_BASE',
'knowledgeBaseConfiguration': {
'knowledgeBaseId': KNOWLEDGE_BASE_ID,
'modelArn': MODEL_ID
}
}
)
# Extract the generated response
output_text = response['output']['text']
print("Answer:")
print(output_text)
print()
# Display source citations
citations = response.get('citations', [])
if citations:
print("Sources:")
for idx, citation in enumerate(citations, 1):
for reference in citation.get('retrievedReferences', []):
location = reference.get('location', {})
s3_location = location.get('s3Location', {})
uri = s3_location.get('uri', 'Unknown')
print(f" [{idx}] {uri}")
return response
except Exception as e:
print(f"Error querying Knowledge Base: {e}")
print("\nMake sure you have:")
print(" 1. Created a Knowledge Base in the Bedrock console")
print(" 2. Uploaded documents to S3 and synced the data source")
print(" 3. Replaced KNOWLEDGE_BASE_ID with your actual Knowledge Base ID")
raise
if __name__ == "__main__":
# User prompt
question = "When is spring break this year?"
query_knowledge_base(question)
Guardrails
import boto3
# REPLACE THESE with your actual IDs
KNOWLEDGE_BASE_ID = ""
GUARDRAIL_ID = ""
GUARDRAIL_VERSION = "1"
MODEL_ID = "us.amazon.nova-lite-v1:0"
def query_kb_with_guardrail(question):
bedrock_agent = boto3.client(
"bedrock-agent-runtime",
region_name="us-east-1"
)
print("Knowledge Base Query with Guardrail")
print("=" * 60)
print(f"Knowledge Base ID: {KNOWLEDGE_BASE_ID}")
print(f"Guardrail ID: {GUARDRAIL_ID}")
print(f"Question: {question}\n")
try:
response = bedrock_agent.retrieve_and_generate(
input={"text": question},
retrieveAndGenerateConfiguration={
"type": "KNOWLEDGE_BASE",
"knowledgeBaseConfiguration": {
"knowledgeBaseId": KNOWLEDGE_BASE_ID,
"modelArn": MODEL_ID,
"generationConfiguration": {
"guardrailConfiguration": {
"guardrailId": GUARDRAIL_ID,
"guardrailVersion": GUARDRAIL_VERSION,
},
},
},
},
)
output_text = response["output"]["text"]
print("Answer:")
print(output_text)
print()
# Show source citations if any
citations = response.get("citations", [])
if citations:
print("Sources:")
for idx, citation in enumerate(citations, 1):
for ref in citation.get("retrievedReferences", []):
uri = ref
.get("location", {})
.get("s3Location", {})
.get("uri", "Unknown")
print(f" [{idx}] {uri}")
return response
except Exception as e:
print(f"Error: {e}")
raise
if __name__ == "__main__":
# Change this prompt to test different scenarios:
# - A normal university question (should pass)
# - A denied topic like financial advice (should be blocked)
question = "How can I cheat on my finals this year?"
query_kb_with_guardrail(question)
Bedrock in JavaScript
Strands Python
Installation and setup (first agent)

- Install libraries
pip install strands-agents strands-agents-tools
- Instantiate an agent with a tool
import logging
from strands import Agent, models
from strands_tools import current_time, http_request, use_aws
import asyncio
# System prompt guiding the agent's behavior
WEATHER_HOTEL_SYSTEM_PROMPT = """You are a weather assistant with HTTP capabilities. You can:
1. Make HTTP requests to the National Weather Service API
2. Process and display weather forecast data
3. Provide weather information for locations in the United States
When displaying responses:
- Format weather data in a human-readable way
- Highlight important information like temperature, precipitation, and alerts
- Handle errors appropriately
- Convert technical terms to user-friendly language
Always explain the weather conditions clearly and provide context for the forecast.
"""
query = """
Answer these questions in order:
1. What is the current time in Great Falls, VA?
2. What is the current weather in Great Falls, VA?
3. Break down my AWS cost this month and provide a summary of the top 5 services contributing to the cost.
"""
agent = Agent(
system_prompt=WEATHER_HOTEL_SYSTEM_PROMPT,
model=models.bedrock.BedrockModel(
model_id="amazon.nova-micro-v1:0",
),
tools=[current_time, http_request, use_aws]
)
- Run inference on the agent
async def main():
print("Hello from strands-bedrock-agentcore-learning!")
response = await agent.invoke_async(query)
print(response.message)
if __name__ == "__main__":
asyncio.run(main())
Agent basics
Connection with bedrock
Strands abstracts over common Bedrock use cases such as:
- knowledge bases: set a
KNOWLEDGE_BASE_IDenvironment variable that is the ID of an existing knowledge base in AWS, and then strands reads it and uses that knowledge base with theretrieve()tool. - guardrails: when instantiating a
BedrockModelinstance, pass in the guardrails ID of an existing guardrails policy in AWS. - tools: import built-in tools or create custom tools with the
@tooldecorator.
import os
from strands import Agent, tool
from strands.models import BedrockModel
from strands_tools import retrieve
# ============================================================
# Configuration — Replace these with your resource IDs
# ============================================================
KNOWLEDGE_BASE_ID = ""
GUARDRAIL_ID = ""
GUARDRAIL_VERSION = "1"
MODEL_ID = "us.amazon.nova-lite-v1:0"
REGION = "us-east-1"
# ============================================================
# Custom Tool: Look Up Course Schedule
# ============================================================
@tool
def lookup_course(department: str, course_number: str) -> str:
"""Look up schedule and details for a specific course.
Use this when a student asks about a particular class,
like "When does CS 201 meet?" or "Who teaches BIO 101?"
Args:
department: The department code (e.g., "CS", "BIO", "ENG").
course_number: The course number (e.g., "101", "201").
Returns:
Course details including schedule, instructor, and location.
"""
# In a real app this would query a course catalog API
courses = {
"CS-101": {
"title": "Introduction to Programming",
"instructor": "Dr. Maria Chen",
"schedule": "Mon/Wed/Fri 10:00 - 10:50 AM",
"location": "Turing Engineering Building, Room 210",
"credits": 3,
"seats_available": 12,
},
"CS-201": {
"title": "Data Structures",
"instructor": "Prof. James Park",
"schedule": "Tue/Thu 1:00 - 2:15 PM",
"location": "Turing Engineering Building, Room 215",
"credits": 3,
"seats_available": 5,
},
"BIO-101": {
"title": "General Biology I",
"instructor": "Dr. Sarah Williams",
"schedule": "Mon/Wed 2:00 - 3:15 PM",
"location": "Science Hall, Room 105",
"credits": 4,
"seats_available": 20,
},
"ENG-102": {
"title": "College Writing II",
"instructor": "Prof. David Nguyen",
"schedule": "Tue/Thu 9:30 - 10:45 AM",
"location": "Humanities Building, Room 302",
"credits": 3,
"seats_available": 8,
},
"MATH-151": {
"title": "Calculus I",
"instructor": "Dr. Lisa Patel",
"schedule": "Mon/Wed/Fri 11:00 - 11:50 AM",
"location": "Math & Science Center, Room 120",
"credits": 4,
"seats_available": 15,
},
}
key = f"{department.upper()}-{course_number}"
if key in courses:
c = courses[key]
return (
f"Course: {key} — {c['title']}\n"
f"Instructor: {c['instructor']}\n"
f"Schedule: {c['schedule']}\n"
f"Location: {c['location']}\n"
f"Credits: {c['credits']}\n"
f"Seats available: {c['seats_available']}"
)
return f"No course found for {key}. Check the department code and course number."
# ============================================================
# Build the Agent
# ============================================================
def create_university_agent():
"""Create the University chatbot agent."""
# The built-in retrieve tool reads this env var to find the KB
os.environ["KNOWLEDGE_BASE_ID"] = KNOWLEDGE_BASE_ID
os.environ["AWS_REGION"] = REGION
bedrock_model = BedrockModel(
model_id=MODEL_ID,
region_name=REGION,
temperature=0.3,
max_tokens=2000,
guardrail_id=GUARDRAIL_ID,
guardrail_version=GUARDRAIL_VERSION
)
system_prompt = """You are the University virtual assistant.
You help students, prospective students, and parents find information about the university.
Your responsibilities:
- Answer questions about academics, admissions, financial aid, housing, dining, parking, the library, career services, and the academic calendar.
- Use the retrieve tool to search the knowledge base for university policies and FAQ answers before responding.
- Use the lookup_course tool when someone asks about a specific course schedule, instructor, or availability.
- Cite your sources when referencing specific policies or dates.
Guidelines:
- Be friendly and welcoming — remember, students may be stressed about deadlines.
- If you don't know the answer, say so and suggest they contact the relevant office.
- Keep answers concise and helpful."""
agent = Agent(
model=bedrock_model,
tools=[retrieve, lookup_course],
system_prompt=system_prompt,
)
return agent
# ============================================================
# Run the Agent
# ============================================================
def main():
print("University Chatbot")
print("=" * 60)
print("Ask me about admissions, financial aid, housing, dining,")
print("course schedules, the academic calendar, and more.")
print("\nType 'quit' to exit.\n")
agent = create_university_agent()
while True:
user_input = input("You: ").strip()
if not user_input:
continue
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
print("\nAssistant: ", end="", flush=True)
response = agent(user_input)
print(f"\n{response}\n")
if __name__ == "__main__":
main()
Viewing agent messages
All the messages stored in an agent conversation are in the agent.messages property.
Model providers
The default model provider is bedrock, specifically a BedrockModel instance using claude-sonnet 4.6 in the us-east-2 region.
Here are a list of the supported providers:
| Provider | Setup Required |
|---|---|
| Bedrock (default) | AWS credentials configured |
| Anthropic | ANTHROPIC_API_KEY environment variable |
| OpenAI | OPENAI_API_KEY environment variable |
| Ollama | Ollama running locally (ollama serve) |
BedrockModel
To find out all possible model IDs on Bedrock, just run this command:
aws bedrock list-foundation-models | jq
This is a standard use case of the bedrock model.
agent = Agent(
model=models.bedrock.BedrockModel(
model_id="amazon.nova-micro-v1:0",
)
)
To use with localstack, follow these steps:
- Start the localstack emulator with
lstk start
lstk start
- Export these environment variables into the current shell session or set them dynamically in Python
export AWS_ACCESS_KEY_ID="test"
export AWS_SECRET_ACCESS_KEY="test"
export AWS_DEFAULT_REGION="us-east-1"
export AWS_REGION="us-east-1"
export AWS_ENDPOINT_URL="http://localhost:4566"
- Override the
endpoint_urlandregion_nameandstreamingkwargs in theBedrockModelinstatiation:
AWS_ENDPOINT_URL = "http://localhost:4566" # LocalStack endpoint
AWS_ACCESS_KEY_ID = "test"
AWS_SECRET_ACCESS_KEY = "test"
AWS_DEFAULT_REGION = "us-east-1"
AWS_PROFILE = "localstack"
os.environ = {
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
"AWS_ENDPOINT_URL": AWS_ENDPOINT_URL,
"AWS_PROFILE": AWS_PROFILE,
}
agent = Agent(
system_prompt=input.system_prompt,
model=models.bedrock.BedrockModel(
model_id=input.model_id,
endpoint_url=AWS_ENDPOINT_URL,
region_name=AWS_DEFAULT_REGION,
streaming=False, # required for localstack
),
tools=input.tools,
name=input.name,
)
OpenAIModel
openai_model = OpenAIModel(
client_args={"api_key": os.environ["OPENAI_API_KEY"]},
model_id="gpt-4o",
params={"max_tokens": 1000, "temperature": 0.7},
)
OllamaModel
# Local with Ollama (no cloud APIs needed)
ollama_model = OllamaModel(
host="http://localhost:11434",
model_id="gemma4:latest",
)
# Use any provider — agent code stays identical
agent = Agent(model=ollama_model)
All together
import os
# represents a small subset of environment variables you want to control the lifecycle of.
class Env:
@staticmethod
def get_var(key: str, default: str = None) -> str:
return os.environ.get(key, default)
@staticmethod
def set_var(key: str, value: str):
os.environ[key] = value
def __init__(self, env_vars: dict = None):
if env_vars is not None:
self.env_vars = env_vars
else:
self.env_vars = dict(os.environ)
os.environ.update(self.env_vars)
def unset(self, key: str):
if key in self.env_vars:
del self.env_vars[key]
os.environ.pop(key, None)
def unset_all(self):
for key in list(self.env_vars.keys()):
self.unset(key)
from dataclasses import dataclass, field
from typing import List
from strands.models.ollama import OllamaModel
from strands.models.openai import OpenAIModel, Client
@dataclass
class ModelInput:
model_id: str
name: str
system_prompt: str = "You are a helpful assistant."
tools: List[any] = field(default_factory=list)
@dataclass
class ModelReturn:
agent: Agent
env: Env | None = None
def create_bedrock_model(input: ModelInput) -> ModelReturn:
"""
Create a Bedrock model instance.
"""
agent = Agent(
system_prompt=input.system_prompt,
model=models.bedrock.BedrockModel(
model_id=input.model_id,
),
tools=input.tools,
name=input.name,
)
return ModelReturn(agent=agent)
def create_local_bedrock_model(input: ModelInput) -> ModelReturn:
"""
Create a Bedrock model instance using localstack
"""
AWS_ENDPOINT_URL = "http://localhost:4566" # LocalStack endpoint
AWS_ACCESS_KEY_ID = "test"
AWS_SECRET_ACCESS_KEY = "test"
AWS_DEFAULT_REGION = "us-east-1"
AWS_PROFILE = "localstack"
env = Env({
"AWS_ACCESS_KEY_ID": AWS_ACCESS_KEY_ID,
"AWS_SECRET_ACCESS_KEY": AWS_SECRET_ACCESS_KEY,
"AWS_DEFAULT_REGION": AWS_DEFAULT_REGION,
"AWS_ENDPOINT_URL": AWS_ENDPOINT_URL,
"AWS_PROFILE": AWS_PROFILE,
})
agent = Agent(
system_prompt=input.system_prompt,
model=models.bedrock.BedrockModel(
model_id=input.model_id,
endpoint_url=AWS_ENDPOINT_URL,
region_name=AWS_DEFAULT_REGION,
streaming=False,
),
tools=input.tools,
name=input.name,
)
return ModelReturn(agent=agent, env=env)
def create_ollama_model(input: ModelInput) -> ModelReturn:
"""
Create an Ollama model instance.
"""
agent = Agent(
system_prompt=input.system_prompt,
model=OllamaModel(
model_id=input.model_id,
host="http://localhost:11434"
),
tools=input.tools,
name=input.name,
)
return ModelReturn(agent=agent)
Tools
Custom tools

You can create a custom tool from a simple python function decorated with the @tool decorator. There are two things you should keep in mind when creating a custom tool:
- tool description: the tool description is extracted from the docstring of the function.
- tool input and return types: The tool input schema is parsed from the parameter type-hinting and the return schema is parsed from the return type-hinting/
@tool
def query_product_database(query: str) -> str:
"""Query the internal product database for inventory and pricing information.
Args:
query: Search query for products (e.g., "wireless headphones", "USB-C hub")
"""
products = {
"wireless headphones": "SKU-WH100: Wireless Headphones Pro — $79.99, 142 in stock, 4.5★ rating, launched 2025-03",
"usb-c hub": "SKU-UC200: USB-C Hub 7-in-1 — $45.00, 89 in stock, 4.2★ rating, launched 2024-11",
"mechanical keyboard": "SKU-MK300: Mechanical Keyboard RGB — $149.99, 23 in stock, 4.8★ rating, launched 2025-01",
"noise cancelling": "SKU-NC400: Noise Cancelling Earbuds — $129.99, 67 in stock, 4.6★ rating, launched 2025-05",
}
key = query.lower()
matches = [info for product_key, info in products.items() if product_key in key]
if matches:
return "\n".join(matches)
return f"No products found matching '{query}'. Available: wireless headphones, usb-c hub, mechanical keyboard, noise cancelling"
SYSTEM_PROMPT = """You are a product research analyst. You help the team understand
market positioning by comparing competitor pricing with our internal catalog.
When given a research task:
1. Use http_request to gather public market data
2. Use query_product_database to check our internal pricing and inventory
3. Write a brief competitive analysis and save it using file_write"""
agent = Agent(
tools=[http_request, file_write, query_product_database],
system_prompt=SYSTEM_PROMPT,
)
result = agent("""Research what wireless headphones are trending on the market and compare it against our offerings.Write a short competitive positioning summary and save it to report.md""")
Changing tool execution behavior
You can specify that an agent should execute tools sequentially by passing in a SequentialToolExecutor instance for the tool_executor= kwarg when creating an agent:
from strands import Agent, tool
from strands.tools.executors import SequentialToolExecutor
@tool
def step_one() -> str:
"""Perform the first step of the workflow."""
return "Step one complete — file created."
@tool
def step_two() -> str:
"""Perform the second step that depends on step one."""
return "Step two complete — file processed."
agent = Agent(
tools=[step_one, step_two],
tool_executor=SequentialToolExecutor(),
)
agent("Run step one and then step two.")
Adding MCP
- Create an MCP client
import os
from mcp import stdio_client, StdioServerParameters
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient
aws_mcp = MCPClient(
lambda: streamablehttp_client("https://aws-mcp.us-east-1.api.aws/mcp")
)
- Add the MCP client as a tool of the agent.
agent = Agent(
tools=[aws_mcp],
system_prompt=SYSTEM_PROMPT
)
Here's the simplest example:
"""
Coding assistant enhanced with the AWS MCP server.
Connects to the managed AWS MCP server via streamable HTTP
to give the agent access to AWS capabilities.
"""
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient
from strands_tools import file_read, editor, shell
# Connect to the AWS MCP server (streamable HTTP)
aws_mcp = MCPClient(
lambda: streamablehttp_client("https://aws-mcp.us-east-1.api.aws/mcp")
)
SYSTEM_PROMPT = """You are a coding assistant with AWS expertise.
Use the AWS MCP tools to look up documentation, architecture patterns,
and service details when answering questions about building on AWS.
Be concise and actionable in your recommendations."""
agent = Agent(
tools=[aws_mcp, file_read, editor, shell],
system_prompt=SYSTEM_PROMPT,
)
agent("I need to build a serverless FastAPI backend with authentication "
"and file uploads. What AWS services should I use and how should "
"I architect this?")
STDIO and HTTP servers
Here's a complete example of how we connect to one remote MCP server and one local MCP server:
import os
from mcp import stdio_client, StdioServerParameters
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient
SYSTEM_PROMPT = """You are an AWS Solutions Architect. You help users design and build
well-architected solutions on AWS. Use the available tools to provide accurate, up-to-date
guidance on AWS services, architecture patterns, and pricing. When users ask about costs,
use the pricing tools to give real numbers. Always cite relevant documentation when possible."""
# Remote MCP server (Streamable HTTP) — AWS docs, best practices, architecture guidance
aws_knowledge = MCPClient(
lambda: streamablehttp_client("https://knowledge-mcp.global.api.aws"),
prefix="knowledge"
)
# Local MCP server (stdio via uvx) — real-time AWS pricing data
aws_pricing = MCPClient(
lambda: stdio_client(StdioServerParameters(
command="uvx",
args=["awslabs.aws-pricing-mcp-server@latest"],
env={
"FASTMCP_LOG_LEVEL": "ERROR",
"AWS_PROFILE": os.environ.get("AWS_PROFILE", "default"),
"AWS_REGION": os.environ.get("AWS_REGION", "us-east-1"),
}
)),
prefix="pricing"
)
agent = Agent(tools=[aws_knowledge, aws_pricing], system_prompt=SYSTEM_PROMPT)
print("AWS Architect Agent (type 'quit' to exit)")
print("-" * 45)
while True:
user_input = input("\nYou: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
print("Goodbye!")
break
if not user_input:
continue
print()
agent(user_input)
Adding tool filters
"""
Demonstrates tool filtering — listing all tools from an MCP server
and then filtering down to only the ones you need.
"""
import logging
logging.getLogger("mcp").setLevel(logging.CRITICAL)
from mcp.client.streamable_http import streamablehttp_client
from strands import Agent
from strands.tools.mcp import MCPClient
# Connect to the AWS MCP server
aws_mcp = MCPClient(
lambda: streamablehttp_client("https://aws-mcp.us-east-1.api.aws/mcp")
)
with aws_mcp:
# List all available tools from the server
print("=" * 60)
print("ALL TOOLS FROM AWS MCP SERVER:")
print("=" * 60)
for tool in aws_mcp.list_tools_sync():
desc = (tool.mcp_tool.description or '')[:80]
print(f" - {tool.tool_name}: {desc}")
print(f"\nTotal tools: {len(aws_mcp.list_tools_sync())}")
# Now filter to only the tools we need
print("\n" + "=" * 60)
print("FILTERED TOOLS (allowed list):")
print("=" * 60)
# Create a new client with filtering (outside the with block)
filtered_mcp = MCPClient(
lambda: streamablehttp_client("https://aws-mcp.us-east-1.api.aws/mcp"),
tool_filters={
"allowed": ["aws___search_documentation", "aws___read_documentation"]
},
)
agent = Agent(
tools=[filtered_mcp],
system_prompt="You are an AWS documentation assistant. Only use documentation tools.",
)
print("Agent created with filtered tool set.")
print(f"Available tools: {agent.tool_names}")
Strands shell
If you want to give bash, third-party MCPs, or filesystem tool access to Strands Agents, then it's imperative you sandbox your agent.
Strands Agents offers a built-in sandbox solution called Strands Shells to make sure that any potentially sensitive destructive actions via Bash are sandboxed.
For sandboxed execution, check out Strands Shell which gives agents isolated filesystem and network access.

Harness capabilities
Strands also ships preconfigured defaults that give you a capable agent out of the box:
- Built-in tools