A development framework for building and managing multi-agent systems, providing tools and APIs to create, deploy, and orchestrate agent interactions and workflows. Supports both Bedrock and external LLMs (e.g., Anthropic Claude) , comes with 20+ prebuilt tools, that you do NOT have to code. For example:
current_time (gives current time)
use_aws (uses boto3 under the hood to list AWS resources)
http_request (figures out API calls automatically)
or use Frameworks like LangGraph, CrewAI, and LlamaIndex etc.
Event | When it fires
AgentInitializedEvent | When the agent finishes initialization
BeforeInvocationEvent | At the beginning of a new agent request
BeforeModelCallEvent | Before the model is invoked for inference
AfterModelCallEvent | After model invocation completes
BeforeToolCallEvent | Before a tool is invoked
AfterToolCallEvent | After tool invocation completes
MessageAddedEvent | When a message is added to the conversation history
AfterInvocationEvent | At the end of an agent request (success or failure)
# --- EMIT: AgentInitializedEvent (only when initiated) ---
# --- EMIT: BeforeInvocationEvent (only on first call) ---
# messages.append(users message) # User's message added to messages array
# --- EMIT: MessageAddedEvent ---
def event_loop_cycle(model, messages, tools, **kwargs):
# --- EMIT: BeforeModelCallEvent ---
response = model.invoke(messages, tools)
# --- EMIT: AfterModelCallEvent ---
if has_tool_use(response):
# Add assistant response to messages
messages.append(response)
# --- EMIT: MessageAddedEvent ---
for tool_use in response.tool_uses: # Strands supports both parallel and sequential tool execution
# --- EMIT: BeforeToolCallEvent ---
result = execute_tool(tool_use)
# --- EMIT: AfterToolCallEvent ---
# Add tool results to messages
messages.append(tool_results)
# --- EMIT: MessageAddedEvent ---
# RECURSE
return event_loop_cycle(model, messages, tools, **kwargs)
else:
return response
# messages.append(assistants message) # Assistant's final message added to messages array
# --- EMIT: MessageAddedEvent ---
# --- EMIT: AfterInvocationEvent ---
LLM returns free-form text based on system prompt instructions. This works for this use case where we have a chatbot that's invoking the other agent, but it's brittle—we're relying on the LLM to follow formatting guidelines, which is error-prone, especially with smaller models.
The Problem with free text LLM Output
Imagine you ask an LLM: "Tell me about a person"
The LLM might respond:
"The person's name is John, age 30, email is john@example.com"
OR "John is 30 years old and works as an engineer"
OR just random text
The format is inconsistent - sometimes it includes age, sometimes not. Sometimes it's JSON, sometimes plain text.
The Solution: Structured Output
Structured outputs solve this by forcing the agent to respond with a specific schema. Strands supports this via Pydantic models:
You define a Pydantic model with the fields you want
Pass it to the Agent via structured_output_model
After the event loop completes, Strands forces the LLM to "invoke" the model as a tool
The response is validated against the schema—if validation fails, Strands retries with the error in context
Step 1 :
class PersonInfo(BaseModel):
name: str
age: int
email: str
Step 2: Pass it to the Agent during initialization
from strands import Agent
agent = Agent(
name="My Agent",
model=model,
system_prompt="You are helpful...",
tools=[tool1, tool2],
structured_output_model=PersonInfo # ← THIS LINE
)