Create your first agent with AWS Strands
Build, run, and extend a simple AI agent using the AWS Strands Agents SDK — from a one-tool agent to one that calls your own Python functions.
AWS Strands is an open-source Python SDK for building AI agents with a small amount of code — you define the model and the tools, and the agent handles the reasoning loop (deciding which tool to call, calling it, and feeding the result back) for you. This guide builds a minimal agent, then extends it with a custom tool.
What you'll end up with
- A Strands agent running locally that can reason and call tools
- A built-in tool (calculator) wired up with zero extra code
- A custom Python function turned into a tool the agent can call
- Examples for Anthropic and OpenAI, each with a cheaper and a higher-cost model
- A practical understanding of what agents cost and how to keep spend under control
NOTE
This guide assumes Python 3.10+ and an API key for a model provider such as Anthropic or OpenAI (see Step 5). Strands defaults to Amazon Bedrock, so if you'd rather use AWS models, follow the Bedrock instructions in Step 5 instead.
Step 1 — Install the SDK
Create a virtual environment
python -m venv .venv
source .venv/bin/activateInstall Strands
pip install strands-agents strands-agents-toolsstrands-agents is the core SDK; strands-agents-tools ships a library of ready-made tools (calculator, web search, file I/O, shell access, and more) so you don't have to write everything yourself.
Step 2 — Write your first agent
Create agent.py. The examples in Steps 2 to 4 use Agent() with no model, which means Amazon Bedrock. If you're not using AWS, set up a provider from Step 5 first and pass it in with Agent(model=model, ...).
from strands import Agent
from strands_tools import calculator
agent = Agent(tools=[calculator])
response = agent("What is 847 * 12, and then divide that by 4?")
print(response)Run it:
python agent.pyThe agent reads the prompt, decides it needs the calculator tool, calls it (possibly more than once, for each arithmetic step), and returns a natural-language answer referencing the result. You don't write any of the "decide which tool, call it, parse the result" logic — that's the agent loop Strands provides.
TIP
By default, Agent() uses Bedrock's Claude models. To see exactly what the agent is thinking and which tools it's calling, pass callback_handler=None to suppress the default streaming output, or leave it on (the default) to watch the reasoning happen in your terminal in real time.
Step 3 — Add a custom tool
Built-in tools cover common cases, but most real agents need to call your own code — a database lookup, an internal API, a business rule. Define a Python function and decorate it with @tool:
from strands import Agent, tool
@tool
def get_shipping_cost(weight_kg: float, destination: str) -> str:
"""Calculate shipping cost based on package weight and destination.
Args:
weight_kg: Package weight in kilograms.
destination: Destination country code, e.g. "US", "AU".
"""
base_rate = 5.00
per_kg_rate = {"US": 2.50, "AU": 4.00}.get(destination, 6.00)
cost = base_rate + (weight_kg * per_kg_rate)
return f"${cost:.2f}"
agent = Agent(tools=[get_shipping_cost])
response = agent("How much would it cost to ship a 3.2kg package to Australia?")
print(response)NOTE
The docstring matters — Strands uses it (along with the type hints) to generate the tool's description and parameter schema for the model, so the agent knows when and how to call it. Write the docstring as if explaining the function to the model, not just to a future developer.
Step 4 — Combine multiple tools
Agents become useful when they can choose between several tools based on the request:
from strands import Agent, tool
from strands_tools import calculator, current_time
@tool
def get_shipping_cost(weight_kg: float, destination: str) -> str:
"""Calculate shipping cost based on package weight and destination."""
base_rate = 5.00
per_kg_rate = {"US": 2.50, "AU": 4.00}.get(destination, 6.00)
return f"${(base_rate + weight_kg * per_kg_rate):.2f}"
agent = Agent(tools=[calculator, current_time, get_shipping_cost])
response = agent(
"What time is it right now, and how much to ship a 5kg package to the US?"
)
print(response)The model decides on its own that this one prompt needs two separate tool calls — current_time and get_shipping_cost — and combines both results into a single answer.
Step 5 — Switch model providers
Strands isn't locked to Bedrock. Every provider is a model object you pass to Agent(model=...), so switching is a one-line change and the rest of your agent (tools, prompts) stays the same. Install the extras for the providers you want:
pip install 'strands-agents[anthropic,openai]'Set your keys as environment variables rather than putting them in code:
export ANTHROPIC_API_KEY="..."
export OPENAI_API_KEY="..."Choosing a model tier
Each provider offers a small, cheap, fast model and a larger, more capable, more expensive one. Pick per task, not per project.
| Provider | Cheaper option | Higher-cost option | Good fit |
|---|---|---|---|
| Anthropic | claude-haiku-4-5-20251001 | claude-sonnet-5-5 (or claude-opus-5-5 for the hardest tasks) | Haiku for simple tool routing and extraction; Sonnet/Opus for multi-step reasoning |
| OpenAI | gpt-4o-mini | gpt-4o | Mini for high-volume, simple tasks; the full model for harder reasoning |
| Amazon Bedrock | A Haiku-class or Nova Lite/Micro model | A Sonnet-class model | Same trade-off, billed through your AWS account |
NOTE
Model names and prices change often. Check each provider's current model list and pricing page before you commit to one, and treat the table above as a starting point, not a quote.
Amazon Bedrock (AWS models)
Use this if you want AWS-hosted models, or if you're running Agent() with no model, since Bedrock is the default. You need an AWS account with model access enabled for the models you want in the Bedrock console.
aws configureStrands picks up credentials the same way the AWS CLI and SDKs do: through aws configure, environment variables, or an instance role. You also need the region where you enabled model access.
from strands import Agent
from strands.models import BedrockModel
from strands_tools import calculator
model = BedrockModel(
model_id="your-bedrock-model-id", # copy the ID from the Bedrock console
region_name="us-east-1",
)
agent = Agent(model=model, tools=[calculator])
print(agent("What's 15% of 2,340?"))Anthropic
import os
from strands import Agent
from strands.models.anthropic import AnthropicModel
from strands_tools import calculator
cheap = AnthropicModel(
client_args={"api_key": os.environ["ANTHROPIC_API_KEY"]},
model_id="claude-haiku-4-5-20251001",
max_tokens=1024,
)
premium = AnthropicModel(
client_args={"api_key": os.environ["ANTHROPIC_API_KEY"]},
model_id="claude-sonnet-5-5",
max_tokens=2048,
)
agent = Agent(model=cheap, tools=[calculator])
print(agent("What's 15% of 2,340?"))OpenAI
import os
from strands import Agent
from strands.models.openai import OpenAIModel
from strands_tools import calculator
cheap = OpenAIModel(
client_args={"api_key": os.environ["OPENAI_API_KEY"]},
model_id="gpt-4o-mini",
params={"max_tokens": 1024},
)
premium = OpenAIModel(
client_args={"api_key": os.environ["OPENAI_API_KEY"]},
model_id="gpt-4o",
params={"max_tokens": 2048},
)
agent = Agent(model=cheap, tools=[calculator])
print(agent("What's 15% of 2,340?"))Use a cheap model first, escalate when needed
A practical pattern is to run everything on the cheap model and only use the premium one for requests that need it:
def make_agent(hard_task: bool) -> Agent:
return Agent(model=premium if hard_task else cheap, tools=[calculator])WARNING
Never hardcode API keys in source files you might commit. Read them from environment variables or a secrets manager, and rotate any key that has ever been committed.
Cost awareness
Agents cost more than a single chat call, and the cost is easy to underestimate. Providers bill per token (input and output, with output typically priced higher), and an agent makes several model calls for one question.
Why agent costs add up
- One question means many calls. Each tool call is another round trip to the model: the model asks for a tool, your code runs it, and the result goes back to the model.
- Context is resent every time. Each round trip includes the system prompt, tool definitions, conversation history and earlier tool results. Input tokens grow with every step.
- Verbose tool output is expensive. A tool that returns a whole database row or web page puts all of it into the next call's input. Return only what the model needs.
- A confused agent can loop. Vague tool descriptions lead to repeated or wrong tool calls, and each one costs money.
- Bigger models cost more per token. Premium models can cost many times more per token than the small ones, so using one for a simple task is the most common waste.
Measure what you spend
Strands records token usage on the result, so you can see what each request cost in tokens:
result = agent("What time is it, and how much to ship 5kg to the US?")
print(result.metrics.get_summary())
print(result.metrics.accumulated_usage) # input, output and total tokensMultiply the token counts by your provider's current per-token prices to get a dollar estimate. Log this per request in development so you know your cost per task before you ship.
Ways to keep cost down
- Start with the cheapest model while you build and debug tools, and move up a tier only where quality demands it. Most iteration cost is repeated debugging calls, not production traffic.
- Set
max_tokens(shown above) so one response can't run on, and keep system prompts short. - Keep tool results small. Truncate or summarise large outputs before returning them.
- Write precise tool docstrings so the model picks the right tool on the first try.
- Trim long conversations. Don't carry the whole history forward when the task doesn't need it.
- Use prompt caching where your provider supports it, since it can sharply reduce the cost of repeated system prompts and tool definitions.
- Use batch APIs for non-urgent bulk work, which providers usually price lower than real-time calls.
Put guardrails around spend
- Set monthly budgets and usage alerts in the Anthropic and OpenAI consoles, and use AWS Budgets for Bedrock.
- Use separate API keys per project or environment so you can see and cut off whichever one is overspending.
- Add rate limits and per-user quotas before exposing an agent to the public. Otherwise anyone can run up your bill.
- Test with a hard cap first. Run a small batch, check the token totals, then scale up.
TIP
Before launch, estimate your monthly cost: average tokens per request × requests per day × 30 × price per token. Do it once for the cheap model and once for the premium one, and the gap will tell you where routing is worth the effort.
Next steps
From here, consider:
- Deploying the agent behind an API endpoint (AWS Lambda or a small container) so other services can call it
- Adding the
strands-agents-toolsretrieval tools to ground responses in your own documents - Writing evaluation cases for your custom tools' docstrings — ambiguous tool descriptions are the most common cause of an agent calling the wrong tool