Releases: strands-agents/sdk-python
v1.23.0
What's Changed
- fix(mcp): prevent agent hang by checking session closure state by @Ratish1 in #1396
- ci: update sphinx-rtd-theme requirement from <2.0.0,>=1.0.0 to >=1.0.0,<4.0.0 by @dependabot[bot] in #1466
- ci: update websockets requirement from <16.0.0,>=15.0.0 to >=15.0.0,<17.0.0 by @dependabot[bot] in #1451
- Update ruff configuration to apply pyupgrade to modernize python syntax by @maxrabin in #1336
- fix(agent): extract text from citationsContent in AgentResult.str by @tmokmss in #1489
- Expose input messages to BeforeInvocationEvent hook by @Unshure in #1474
- interrupts - graph - hook based by @pgrayy in #1478
- fix: Swap unit test sleeps with explicit signaling by @zastrowm in #1497
- Fix PEP 563 incompatibility with @tool decorated tools by @zastrowm in #1494
- feat: override service name by OTEL_SERVICE_NAME env by @okamototk in #1400
- fix(bedrock): disable thinking mode when forcing tool_choice by @strands-agent in #1495
- fix(a2a): use a2a artifact update event by @brycewcole in #1401
- Add parallel reading support to S3SessionManager.list_messages() by @CrysisDeu in #1186
- feat(steering): allow steering on AfterModelCallEvents by @dbschmigelski in #1429
- fix: provide unique toolUseId for gemini models by @AirswitchAsa in #1201
- gemini - tool_use_id_to_name - local by @pgrayy in #1521
- fix(litellm): handle missing usage attribute on ModelResponseStream by @dbschmigelski in #1520
- feat(agent): add configurable retry_strategy for model calls by @zastrowm in #1424
- fix(swarm): accumulate execution_time across interrupt/resume cycles by @pgrayy in #1502
- Feat: graduate multiagent hook events from experimental by @JackYPCOnline in #1498
- Nova Sonic 2 support for BidiAgent by @lanazhang in #1476
- fix(tests): reduce flakiness in guardrail redact output test by @dbschmigelski in #1505
New Contributors
- @maxrabin made their first contribution in #1336
- @tmokmss made their first contribution in #1489
- @okamototk made their first contribution in #1400
- @strands-agent made their first contribution in #1495
- @brycewcole made their first contribution in #1401
- @CrysisDeu made their first contribution in #1186
- @AirswitchAsa made their first contribution in #1201
- @lanazhang made their first contribution in #1476
Full Changelog: v1.22.0...v1.23.0
v1.22.0
Major Features
MCP Resource Operations - PR#1117
The MCP client now supports resource operations, enabling agents to list, read, and work with resources provided by MCP servers. This includes static resources, binary resources, and parameterized resource templates.
from strands.tools.mcp import MCPClient
with MCPClient(server_transport) as client:
# List available resources
resources = client.list_resources_sync()
for resource in resources.resources:
print(f"Resource: {resource.name} at {resource.uri}")
# Read a specific resource
content = client.read_resource_sync("file://documents/report.txt")
text = content.contents[0].text
# List resource templates (parameterized resources)
templates = client.list_resource_templates_sync()
for template in templates.resourceTemplates:
print(f"Template: {template.uriTemplate}")Bedrock Guardrails Latest Message Option - PR#1224
Bedrock models now support the guardrail_latest_message parameter, which sends only the latest user message to AWS Bedrock Guardrails for evaluation instead of the entire conversation history. This reduces token usage and enables conversation recovery after guardrail interventions.
from strands.models.bedrock import BedrockModel
model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
guardrail_id="your-guardrail-id",
guardrail_version="DRAFT",
guardrail_latest_message=True # Only evaluate the latest user message
)See the Bedrock Guardrails documentation for more details.
LiteLLM Non-Streaming Support - PR#512
The LiteLLM model provider now correctly handles non-streaming responses, fixing an issue where stream=False would raise an error. Both streaming and non-streaming modes now work seamlessly.
from strands.models.litellm import LiteLLMModel
# Use non-streaming mode for simpler response handling
model = LiteLLMModel(
model_id="gpt-3.5-turbo",
params={"stream": False}
)
# Works correctly now - no more ValueError
agent = Agent(model=model)
result = agent("What is 2+2?")Major Bug Fixes
-
Concurrent Agent Invocations - PR#1453
Fixed critical agent state corruption when multiple concurrent invocations occurred on the same agent instance. A newConcurrencyExceptionis now raised to prevent concurrent invocations and protect agent state integrity. -
Gemini Empty Stream Handling - PR#1420
Fixed UnboundLocalError crash when Gemini returns an empty event stream by properly initializing variables before the stream loop. -
Deprecation Warning on Import - PR#1380
Fixed unwanted deprecation warnings appearing when importingstrandsby using lazy__getattr__to emit warnings only when deprecated aliases are actually accessed.
What's Changed
- docs: update github agent action to reference S3_SESSION_BUCKET by @dbschmigelski in #1418
- feat: provide extra command content as the the prompt to the agent by @zastrowm in #1419
- [FEATURE] add MCP resource operations in MCP Tools by @xiehust in #1117
- fix: import errors for models with optional imports by @mehtarac in #1384
- add BidiGeminiLiveModel and BidiOpenAIRealtimeModel to the init by @mehtarac in #1383
- bidi - async - remove cancelling call by @pgrayy in #1357
- feat(bedrock): add guardrail_latest_message option by @aiancheruk in #1224
- fix(gemini): UnboundLocal Exception Fix by @emattiza in #1420
- fix! Litellm handle non streaming response fix for issue #477 by @schleidl in #512
- feat(agent-interface): introduce AgentBase Protocol as the interface for agent classes to implement by @awsarron in #1126
- ci: update pytest requirement from <9.0.0,>=8.0.0 to >=8.0.0,<10.0.0 in the dev-dependencies group by @dependabot[bot] in #1161
- feat: pass invocation_state to model providers by @tirth14 in #1414
- Add Security.md file by @yonib05 in #1454
- chore: Update release notes sop by @zastrowm in #1456
- fix(integ): make calculator tool more robust to LLM output variations by @cagataycali in #1445
- fix: resolve string formatting error in MCP client error handling by @cagataycali in #1446
- bidi - move 3.12 check to nova sonic module by @pgrayy in #1439
- ci: update sphinx requirement from <9.0.0,>=5.0.0 to >=5.0.0,<10.0.0 by @dependabot[bot] in #1426
- fix: add concurrency protection to prevent parallel invocations from corrupting agent state by @zastrowm in #1453
- fix(mcp): propagate contextvars to background thread by @cagataycali in #1444
- Update to opus 4.5 by @Unshure in #1471
New Contributors
- @aiancheruk made their first contribution in #1224
- @emattiza made their first contribution in #1420
- @schleidl made their first contribution in #512
- @tirth14 made their first contribution in #1414
Full Changelog: v1.21.0...v1.22.0
v1.21.0
Major Features
Custom HTTP Client Support for OpenAI and Gemini - PR#1366
The OpenAI and Gemini model providers now accept a pre-configured client via the client parameter, enabling connection pooling, proxy configuration, custom timeouts, and centralized observability across all model requests. The client is reused for all requests and its lifecycle is managed by the caller, not the model provider.
from strands.models.openai import OpenAIModel
import httpx
# Create custom client with proxy and timeout configuration
custom_client = httpx.AsyncClient(
proxy="http://proxy.example.com:8080",
timeout=60.0
)
model = OpenAIModel(model_id="gpt-4o-mini", client=custom_client)Gemini Built-in Tools (Google Search, Code Execution) - PR#1050
Gemini models now support native Google tools like GoogleSearch and CodeExecution through the gemini_tools parameter. These tools integrate directly with Gemini's API without requiring custom function implementations, enabling agents to search the web and execute code natively.
from strands.models.gemini import GeminiModel
from google.genai import types
model = GeminiModel(
model_id="gemini-2.0-flash-exp",
gemini_tools=[types.Tool(google_search=types.GoogleSearch())]
)Hook-based Model Retry on Exceptions - PR#1405
Hooks can now retry model invocations by setting event.retry = True in the AfterModelCallEvent handler, enabling custom retry logic for transient errors, rate limits, or quality checks. This provides fine-grained control over model retry behavior beyond basic exception handling.
class RetryOnServiceUnavailable(HookProvider):
def __init__(self, max_retries=3):
self.max_retries = max_retries
self.retry_count = 0
def register_hooks(self, registry, **kwargs):
registry.add_callback(BeforeInvocationEvent, self.reset_counts)
registry.add_callback(AfterModelCallEvent, self.handle_retry)
def reset_counts(self, event = None):
self.retry_count = 0
async def handle_retry(self, event):
if event.exception:
if "ServiceUnavailable" in str(event.exception):
logger.info("ServiceUnavailable encountered")
count = self.retry_count
if count < self.max_retries:
logger.info("Retrying model call")
self.retry_count = count + 1
event.retry = True
await asyncio.sleep(2 ** count) # Exponential backoff
else:
# reset counts in the succesful case
self.reset_counts(None)Per-Turn Conversation Management - PR#1374
Conversation managers now support mid-execution management via the per_turn parameter, applying conversation window management before each model call rather than only at the end. This prevents context window overflow during multi-turn conversations with tools or long responses.
from strands import Agent
from strands.agent.conversation_manager import SlidingWindowConversationManager
# Enable management before every model call
manager = SlidingWindowConversationManager(
per_turn=True,
window_size=40
)
# Or manage every N turns
manager = SlidingWindowConversationManager(
per_turn=3, # Manage every 3 model calls
window_size=40
)
agent = Agent(model=model, conversation_manager=manager)Agent Invocation Metrics - PR#1387
Metrics now track per-invocation data through agent_invocations and latest_agent_invocation properties, providing granular insight into each agent call's performance, token usage, and execution time. This enables detailed performance analysis for multi-invocation workflows.
from strands import Agent
agent = Agent(model=model)
result = agent("Analyze this data")
# Access invocation-level metrics
latest = result.metrics.latest_agent_invocation
print(f"Cycles: {len(latest.cycles)}")
print(f"Tokens: {latest.usage}")
# Access all invocations
for invocation in result.metrics.agent_invocations:
print(f"Invocation usage: {invocation.usage}")ToolRegistry Replace Method - PR#1182
The ToolRegistry now supports replacing existing tools via the replace() method, enabling dynamic tool updates without re-registering all tools. This is particularly useful for hot-reloading tool implementations or updating tools based on runtime conditions.
from strands.tools.registry import ToolRegistry
from strands import tool
registry = ToolRegistry()
@tool
def calculate(x: int, y: int) -> int:
"""Calculate sum."""
return x + y
registry.register_tool(calculate)
# Later, replace with updated implementation
@tool
def calculate(x: int, y: int) -> int:
"""Calculate product (new implementation)."""
return x * y
registry.replace(calculate)Web and Search Result Citations - PR#1344
Citations now support web locations (WebLocation) and search result positions (SearchResultLocation), enabling agents to reference specific URLs and search result indices when grounding responses in retrieved information.
from strands.types.citations import WebLocation, SearchResultLocation
# Create web location citation
web_citation: WebLocation = {
"url": "https://docs.example.com/api",
"domain": "docs.example.com"
}
# Create search result citation
search_citation: SearchResultLocation = {
"searchResultIndex": 0,
"start": 150,
"end": 320
}A2A Server FastAPI/Starlette Customization - PR#1250
A2A servers now support passing additional kwargs to FastAPI and Starlette app constructors via app_kwargs, enabling customization of documentation URLs, debug settings, middleware, and other application-level configuration.
from strands.multiagent.a2a import A2AServer
server = A2AServer(agent)
# Customize FastAPI app
app = server.to_fastapi_app(
app_kwargs={
"title": "My Custom Agent API",
"docs_url": None, # Disable docs
"redoc_url": None # Disable redoc
}
)
# Customize Starlette app
app = server.to_starlette_app(
app_kwargs={"debug": True}
)Major Bug Fixes
-
Citation Streaming and Union Type Fix - PR#1341
FixedCitationLocationto properly handle union types and correctly join citation chunks during streaming, resolving issues where citations were malformed or lost in streamed responses. -
Usage Metrics Double Counting - PR#1327
Fixed telemetry double counting of usage metrics, ensuring accurate token usage tracking and preventing inflated cost calculations. -
Tools Returning Image Content - PR#1079
Fixed OpenAI model provider to properly support tools that return image content, enabling vision-capable tools to work correctly. -
Deprecation Warning Timing - PR#1380
Fixed deprecation warnings to only emit when deprecated aliases are actually accessed, eliminating spurious warnings for users not using deprecated features.
What's Changed
- Add issue-responder action agent by @afarntrog in #1319
- feat(a2a): support passing additional keyword arguments to FastAPI and Starlette constructors by @snooyen in #1250
- feat(tools): add replace method to ToolRegistry by @Ratish1 in #1182
- feat: add meta field support to MCP tool results by @vamgan in #1237
- fix: remove unnecessary None from dict.get() calls by @Ratish1 in #956
- chore: Expose Status from .base for easier imports by @zastrowm in #1356
- fix: CitationLocation is UnionType, and correctly joining citation chunks when streaming is being used by @ericfzhu in #1341
- fix(telemetry): prevent double counting of usage metrics by @rajib76 in #1327
- feat(citations): Add support for web and search result citations by @danilop in #1344
- feat: add gemini_tools field to GeminiModel with validation and tests by @pshiko in #1050
- Port PR guidelines from sdk-typescript by @zastrowm in #1373
- feat: allow custom-client for OpenAIModel and GeminiModel by @poshinchen in #1366
- fix: Pass CODECOV_TOKENS through for code-coverage stats by @zastrowm in #1385
- ci: bump actions/checkout from 5 to 6 by @dependabot[bot] in #1222
- ci: update pytest-asyncio requirement from <1.3.0,>=1.0.0 to >=1.0.0,<1.4.0 by @dependabot[bot] in #1166
- ci: bump actions/upload-artifact from 4 to 6 by @dependabot[bot] ...
v1.20.0
Major Features
Swarm Interrupts for Human-in-the-Loop - PR#1193
Swarm multi-agent systems now support interrupts, enabling Human-in-the-Loop patterns for approval workflows and user interaction during agent execution. Interrupts can be triggered via BeforeNodeCallEvent hooks or directly within agent tools using ToolContext.
from strands import Agent, tool
from strands.experimental.hooks.multiagent import BeforeNodeCallEvent
from strands.hooks import HookProvider
from strands.multiagent import Swarm
from strands.multiagent.base import Status
# Example 1: Interrupt via Hook
class ApprovalHook(HookProvider):
def register_hooks(self, registry):
registry.add_callback(BeforeNodeCallEvent, self.approve)
def approve(self, event):
response = event.interrupt("approval", reason=f"{event.node_id} needs approval")
if response != "APPROVE":
event.cancel_node = "rejected"
swarm = Swarm([agent1, agent2], hooks=[ApprovalHook()])
result = swarm("Task requiring approval")
# Handle interrupts
while result.status == Status.INTERRUPTED:
for interrupt in result.interrupts:
user_input = input(f"{interrupt.reason}: ")
responses = [{"interruptResponse": {"interruptId": interrupt.id, "response": user_input}}]
result = swarm(responses)
# Example 2: Interrupt via Tool
@tool(context=True)
def get_user_info(tool_context: ToolContext) -> str:
response = tool_context.interrupt("user_info", reason="need user name")
return f"User: {response}"
user_agent = Agent(name="user", tools=[get_user_info])
swarm = Swarm([user_agent])
result = swarm("Who is the user?")
# Resume with interrupt response as shown aboveSee the interrupts documentation for more details.
AgentResult Access in AfterInvocationEvent - PR#1125
Hooks can now access the complete AgentResult in AfterInvocationEvent, enabling post-invocation actions based on the agent's output, stop reason, and metrics. This enhancement allows for richer observability and custom handling of agent results.
from strands import Agent
from strands.hooks import AfterInvocationEvent, HookProvider
class ResultLoggingHook(HookProvider):
def register_hooks(self, registry):
registry.add_callback(AfterInvocationEvent, self.log_result)
def log_result(self, event: AfterInvocationEvent):
# Access the complete AgentResult
if event.result:
print(f"Stop reason: {event.result.stop_reason}")
print(f"Tokens used: {event.result.usage}")
print(f"Response: {event.result.text}")
agent = Agent(hooks=[ResultLoggingHook()])
result = agent("What is 2+2?")Major Bug Fixes
-
Structured Output Display Fix - PR#1290
FixedAgentResult.__str__()to return structured output JSON when no text content is present, resolving issues whereprint(agent_result)showed empty output and structured output was lost in multi-agent graph propagation. -
Tool Spec Composition Keywords Fix - PR#1301
Fixed tool specification handling for JSON Schema composition keywords (anyOf, oneOf, allOf, not), preventing models from incorrectly returning string-encoded JSON for optional parameters likeOptional[List[str]]. -
MCP Client Resource Leak Fix - PR#1321
Fixed file descriptor leak in MCP client by properly closing the asyncio event loop, preventing resource exhaustion in multi-tenant applications that create many MCP clients.
All Changes
- Remove toolResult message when toolUse is missing due to pagination in session management by @afarntrog in #1274
- interrupts - swarm by @pgrayy in #1193
- fix(agent): Return structured output JSON when AgentResult has no text by @afarntrog in #1290
- bidi - fix record direct tool call by @pgrayy in #1300
- Update doc strings to eliminate warnings in doc build by @zastrowm in #1284
- fix: fix broken tool spec with composition keywords by @mkmeral in #1301
- bidi - tests - lint by @pgrayy in #1307
- bidi - fix mypy errors by @pgrayy in #1308
- feat(hooks): add AgentResult to AfterInvocationEvent by @Ratish1 in #1125
- feat(docs): Create agent.md and docs folder by @mkmeral in #1312
- bidi - remove python 3.11+ features by @pgrayy in #1302
- fix: close mcp client event loop by @davidpadbury in #1321
New Contributors
- @davidpadbury made their first contribution in #1321
Full Changelog: v1.19.0...v1.20.0
v1.19.0
What's New
-
Bidirectional Agents (Experimental): This release introduces BidiAgent for real-time voice conversations with AI agents through persistent connections that support continuous audio streaming, natural interruptions, and concurrent tool execution. This experimental feature allows developers to build voice assistants and interactive applications with support for Amazon Nova Sonic, OpenAI Realtime API, and Google Gemini Live.
-
Steering (Experimental): Enables modular prompting with progressive disclosure for complex agent workflows through just-in-time feedback loops. Instead of front-loading all instructions into monolithic prompts, steering handlers provide contextual guidance that appears when relevant, maintaining agent effectiveness on multi-step tasks while preserving adaptive reasoning capabilities.
What's Changed
- fix: avoid KeyError in direct tool calls with context by @qmays-phdata in #1213
- fix: attached custom attributes to all spans by @poshinchen in #1235
- hooks - before node call - cancel node by @pgrayy in #1203
- interrupts - support falsey responses by @pgrayy in #1256
- Bidirectional Streaming Agent by @mehtarac in #1276
- mcp - elicitation - fix server request test by @pgrayy in #1281
- feat(steering): add experimental steering for modular prompting by @dbschmigelski in #1280
- test(steering): adjust integ test system prompts to reduce flakiness by @dbschmigelski in #1282
New Contributors
- @qmays-phdata made their first contribution in #1213
Full Changelog: v1.18.0...v1.19.0
v1.18.0
What's Changed
- multi agent input by @pgrayy in #1196
- interrupt - activate - set context separately by @pgrayy in #1194
- In PrintingCallbackHandler, make the verbose description and counting… by @marcbrooker in #1211
- fix: fix swarm session management integ test. by @JackYPCOnline in #1155
- move tool caller definition out of agent module by @pgrayy in #1215
- interrupt - interruptible multi agent hook interface by @pgrayy in #1207
- security(tool_loader): prevent tool name and sys modules collisions i… by @dbschmigelski in #1214
- fix(mcp): protect connection on non-fatal client side timeout error by @dbschmigelski in #1231
- fix(litellm): populate cacheWriteInputTokens from cache_creation_input_token not cache_creation_tokens by @dbschmigelski in #1233
- fix: fix integ test for mcp elicitation_server by @JackYPCOnline in #1234
New Contributors
- @marcbrooker made their first contribution in #1211
Full Changelog: v1.17.0...v1.18.0
v1.17.0
Strands Agents SDK v1.17.0 Release Notes
Features
Configurable Timeout for MCP Agent Tools - PR#1184
You can now set custom timeout values when creating MCP (Model Context Protocol) agent tools, providing better control over tool execution time limits and improving reliability when working with external MCP servers.
from datetime import timedelta
from strands.tools.mcp import MCPAgentTool
# Create MCP tool with custom 30-second timeout
mcp_tool = MCPAgentTool(
...,
timeout=timedelta(seconds=30)
)
agent = Agent(tools=[mcp_tool])This feature is especially useful when working with MCP servers that may have varying response times, allowing you to fine-tune timeout behavior for different use cases.
Bug Fixes
-
Swarm Handoff Timing - PR#1147
Fixed swarm handoff behavior to only switch to the handoff node after the current node completes execution. Previously, the switch occurred mid-execution, causing incorrect event emissions and invalid swarm state when tools were interrupted concurrently with handoff tools. -
LiteLLM Stream Parameter Validation - PR#1183
Added validation for thestreamparameter in LiteLLM to prevent TypeError whenstream=Falseis provided. The SDK now properly handles both streaming and non-streaming responses with clear error messaging. -
Optional MetadataEvent Fields - PR#1187
Fixed handling of MetadataEvents when custom model implementations omit optionalusageormetricsfields. The SDK now provides sensible defaults, preventing KeyError exceptions and enabling greater flexibility for custom model providers. -
A2A Protocol File Data Decoding - PR#1195
Fixed A2A (Agent-to-Agent) executor to properly base64 decode file bytes from A2A messages before passing to Strands agents. Previously, agents were receiving base64-encoded strings instead of actual binary file content.
All changes
- feat: allow setting a timeout when creating MCPAgentTool by @AnirudhKonduru in #1184
- fix(litellm): add validation for stream parameter in LiteLLM by @dbschmigelski in #1183
- fix(event_loop): handle MetadataEvents without optional usage and metrics by @dbschmigelski in #1187
- swarm - switch to handoff node only after current node stops by @pgrayy in #1147
- fix(a2a): base64 decode byte data before placing in ContentBlocks by @dbschmigelski in #1195
New Contributors
- @AnirudhKonduru made their first contribution in #1184
Full Changelog: v1.16.0...v1.17.0
v1.16.0
Major Features
Async Hooks Support - PR#1119
Hooks now support asynchronous callbacks, allowing your hook code to run concurrently with other async tasks without blocking the event loop. This is particularly beneficial for async agent invocations and scenarios where hooks perform I/O operations.
import asyncio
from strands import Agent
from strands.hooks import BeforeInvocationEvent, HookProvider, HookRegistry
class AsyncHook(HookProvider):
def register_hooks(self, registry: HookRegistry, **kwargs) -> None:
registry.add_callback(BeforeInvocationEvent, self.async_callback)
async def async_callback(self, event: BeforeInvocationEvent) -> None:
# Perform async operations without blocking the event loop
await asyncio.sleep(1)
print("Hook executed asynchronously!")
agent = Agent(hooks=[AsyncHook()])
await agent.invoke_async("Hello!")Thread Context Sharing - PR#1146
Context variables (contextvars) are now automatically copied from the main thread to agent threads when using synchronous invocations. This ensures that context-dependent tools and hooks work correctly.
from contextvars import ContextVar
from strands import Agent, tool
request_id = ContextVar('request_id')
@tool
def my_tool():
# Context variables are now accessible within tools
current_request_id = request_id.get()
return f"Processing request: {current_request_id}"
request_id.set("abc-123")
agent = Agent(tools=[my_tool])
response = agent.invoke_async("Use my tool") # Context is properly propagatedEnhanced Telemetry with Tool Definitions - PR#1113
Tool definitions are now included in OpenTelemetry traces via semantic convention opt-in, providing better observability into agent tool usage, following the OpenTelemetry semantic conventions for GenAI.
Opt-in via environment variable:
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_tool_definitionsfrom strands import Agent, tool
from strands_tools import calculator
# Tool definition will appear in telemetry traces
agent = Agent(tools=[calculator])
agent("What is 5 + 3?")String Descriptions in Annotated Tool Parameters - PR#1089
You can now use simple string descriptions directly in Annotated type hints for tool parameters, improving code readability and reducing boilerplate.
from typing import Annotated
from strands import tool
@tool
def get_weather(
location: Annotated[str, "The city and state, e.g., San Francisco, CA"],
units: Annotated[str, "Temperature units: 'celsius' or 'fahrenheit'"] = "celsius"
):
"""Get weather for a location."""
return f"Weather in {location}: 72°{units[0].upper()}"
# Previously required more verbose Field() syntax or using a doc-stringMajor Bug Fixes
- Anthropic "Prompt Too Long" Error Handling - PR#1137
The SDK now properly handles and surfaces Anthropic's "prompt is too long" errors, making it easier to diagnose and fix context window issues. - MCP Server 5xx Error Resilience - PR#1169
The SDK no longer hangs when Model Context Protocol (MCP) servers return 5xx errors, improving reliability when working with external services. - Gemini Non-JSON Error Message Handling - PR#1062
The Gemini model provider now gracefully handles non-JSON error responses, preventing unexpected crashes.
All changes
- fix(models/gemini): handle non-JSON error messages from Gemini API by @Ratish1 in #1062
- fix: Handle "prompt is too long" from Anthropic by @zastrowm in #1137
- feat(telemetry): Add tool definitions to traces via semconv opt-in by @Ratish1 in #1113
- fix: Strip argument sections out of inputSpec top-level description by @zastrowm in #1142
- share thread context by @pgrayy in #1146
- async hooks by @pgrayy in #1119
- feat(tools): Support string descriptions in Annotated parameters by @Ratish1 in #1089
- chore(telemetry): updated opt-in attributes to internal by @poshinchen in #1152
- feat(models): allow SystemContentBlocks in LiteLLMModel by @dbschmigelski in #1141
- share interrupt state by @pgrayy in #1148
- fix: Don't hang when MCP server returns 5xx by @zastrowm in #1169
- fix(models): allow setter on system_prompt and system_prompt_content by @dbschmigelski in #1171
Full Changelog: v1.15.0...v1.16.0
v1.15.0
Major Features
SystemContentBlock Support for Provider-Agnostic Caching - PR#1112
System prompts now support SystemContentBlock arrays, enabling provider-agnostic caching and advanced multi-prompt system configurations. Cache points can be defined explicitly within system content.
from strands import Agent
from strands.types.content import SystemContentBlock
# Define system content with cache points
system_content: list[SystemContentBlock] = [
{"text": "You are a helpful assistant with extensive knowledge."},
{"text": "Your responses should be concise and accurate."},
{"text": "Always cite sources."},
{"cachePoint": {"type": "default"}},
]
agent = Agent(system_prompt=system_content)
agent('What is the capital of Franch?')Multi-Agent Session Management and Persistence - PR#1071, PR#1110
Multi-agent systems now support session management and persistence, enabling agents to maintain state across invocations and support long-running workflows.
from strands import Agent
from strands.multiagent import GraphBuilder
from strands.multiagent.base import Status
from strands.session import FileSessionManager
def build_graph(max_nodes: int):
session_manager = FileSessionManager(session_id="my_session_1", storage_dir="./sessions")
builder = GraphBuilder()
builder.add_node(Agent(name="analyzer", system_prompt="Explain using 2 paragraphs. "))
builder.add_node(Agent(name="summarizer", system_prompt="Summerize and be concise. 10 words or less"))
builder.add_edge("analyzer", "summarizer")
builder.set_entry_point("analyzer")
builder.set_max_node_executions(max_nodes)
builder.set_session_manager(session_manager)
return builder.build()
# Simulate failure because after the first node we exceed max_nodes
result = await build_graph(max_nodes=1).invoke_async("Analyze why 2+2=4")
assert result.status == Status.FAILED
# Simulate that we're resuming from a failure with a fresh session - this picks at the summerizer
result = await build_graph(max_nodes=10).invoke_async("Analyze why 2+2=4")
assert result.status == Status.COMPLETEDAsync Streaming for Multi-Agent Systems - PR#961
Multi-agent systems now support stream_async, enabling real-time streaming of events from agent teams as they collaborate.
from strands import Agent
from strands.multiagent import GraphBuilder
# Create multi-agent graph
analyzer = Agent(name="analyzer")
processor = Agent(name="processor")
builder = GraphBuilder()
builder.add_node(analyzer)
builder.add_node(processor)
builder.add_edge("analyzer", "processor")
builder.set_entry_point("analyzer")
graph = builder.build()
# Stream events as agents process
async for event in graph.stream_async("Analyze this data"):
print(f"Event: {event.get('type', 'unknown')}")Major Bug Fixes
-
Guardrails Redaction Fix - PR#1072
Fixed input/output message redaction whenguardrails_trace="enabled_full", ensuring sensitive data is properly protected in traces. -
Tool Result Block Redaction - PR#1080
Properly redact tool result blocks to prevent conversation corruption when using content filtering or PII redaction. -
Orphaned Tool Use Fix - PR#1123
Fixed broken conversations caused by orphanedtoolUseblocks, improving reliability when tools fail or are interrupted. -
Reasoning Content Handling - PR#1099
DropreasoningContentfrom requests to prevent errors with providers that don't support extended thinking modes. -
Swarm Initialization Fix - PR#1107
Don't initialize agents during Swarm construction, preventing unnecessary resource allocation and improving startup performance. -
Structured Output Context Fix - PR#1128
AllowNonestructured output context in tool executors, fixing edge cases where tools don't require structured responses.
What's Changed
- fix: (bug): Drop reasoningContent from request by @mehtarac in #1099
- fix: Dont initialize an agent on swarm init by @Unshure in #1107
- feat: add multiagent session/repository management. by @JackYPCOnline in #1071
- feat(multiagent): Add stream_async by @mkmeral in #961
- Fix #1077: properly redact toolResult blocks to avoid corrupting the conversation by @leotac in #1080
- linting by @pgrayy in #1120
- Fix input/output message not redacted when guardrails_trace="enabled_full" by @leotac in #1072
- fix: Allow none structured output context in tool executors by @mkmeral in #1128
- fix: Fix broken converstaion with orphaned toolUse by @Unshure in #1123
- feat: Enable multiagent session persistent in Graph/Swarm by @JackYPCOnline in #1110
- feat(models): add SystemContentBlock support for provider-agnostic caching by @dbschmigelski in #1112
New Contributors
Full Changelog: v1.14.0...v1.15.0
v1.14.0
Major Features
Structured Output via Agentic Loop
Agents can now validate responses against predefined schemas using JSON Schema or Pydantic models. Validation occurs at response generation time with configurable retry behavior for non-conforming outputs.
agent = Agent()
result = agent(
"John Smith is a 30 year-old software engineer",
structured_output_model=PersonInfo
)
# Access the structured output from the result
person_info: PersonInfo = result.structured_output
See more in the docs for Structured Output.
Interrupting Agents
Interrupts now provide first-class support for Human-in-the-loop patterns in Strands. They can be raised in Hooks or directly in tool definitions. Related, MCP elicitation has been exposed on the MCPClient.
import json
from typing import Any
from strands import Agent, tool
from strands.hooks import BeforeToolCallEvent, HookProvider, HookRegistry
from my_project import delete_files, inspect_files
class ApprovalHook(HookProvider):
def __init__(self, app_name: str) -> None:
self.app_name = app_name
def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
registry.add_callback(BeforeToolCallEvent, self.approve)
def approve(self, event: BeforeToolCallEvent) -> None:
if event.tool_use["name"] != "delete_files":
return
approval = event.interrupt(f"{self.app_name}-approval", reason={"paths": event.tool_use["input"]["paths"]})
if approval.lower() != "y":
event.cancel_tool = "User denied permission to delete files"
agent = Agent(
hooks=[ApprovalHook("myapp")],
system_prompt="You delete files older than 5 days",
tools=[delete_files, inspect_files],
)
paths = ["a/b/c.txt", "d/e/f.txt"]
result = agent(f"paths=<{paths}>")
while True:
if result.stop_reason != "interrupt":
break
responses = []
for interrupt in result.interrupts:
if interrupt.name == "myapp-approval":
user_input = input(f"Do you want to delete {interrupt.reason["paths"]} (y/N): ")
responses.append({
"interruptResponse": {
"interruptId": interrupt.id,
"response": user_input
}
})
result = agent(responses)Managed MCP Connections
We've introduced MCP Connections via ToolProviders, an experimental interface that addresses the requirement to use context managers with MCP tools. The Agent now manages connection lifecycles automatically, enabling simpler syntax:
agent = Agent(tools=[stdio_mcp_client])
agent("do something")
While this feature is experimental, we aim to mark it as stable soon and welcome user testing of this and other new features.
Agent Config
Users can now define and create agents using configuration files or dictionaries:
{
"model": "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
"prompt": "You are a coding assistant. Help users write, debug, and improve their code. You have access to file operations and can execute shell commands when needed.",
"tools": ["strands_tools.file_read", "strands_tools.editor", "strands_tools.shell"]
}
All Changes
- models - litellm - start and stop reasoning by @pgrayy in #947
- feat: add experimental AgentConfig with comprehensive tool management by @mr-lee in #935
- fix(telemetry): make strands agent invoke_agent span as INTERNAL spanKind by @poshinchen in #1055
- feat: add multiagent hooks, add serialize & deserialize function to multiagent base & agent result by @JackYPCOnline in #1070
- feat: Add Structured Output as part of the agent loop by @afarntrog in #943
- integ tests - interrupts - remove asyncio marker by @pgrayy in #1045
- interrupt - docstring - fix formatting by @pgrayy in #1074
- ci: add pr size labeler by @dbschmigelski in #1082
- fix: Don't bail out if there are no tool_uses by @zastrowm in #1087
- feat(mcp): add experimental agent managed connection via ToolProvider by @dbschmigelski in #895
- fix (bug): retry on varying Bedrock throttlingexception cases by @mehtarac in #1096
- feat: skip model invocation when latest message contains ToolUse by @Unshure in #1068
- direct tool call - interrupt not allowed by @pgrayy in #1097
- mcp elicitation by @pgrayy in #1094
- fix(litellm): enhance structured output handling by @Arindam200 in #1021
- Transform invalid tool usages on sending, not on initial detection by @zastrowm in #1091
New Contributors
- @mr-lee made their first contribution in #935
- @Arindam200 made their first contribution in #1021
Full Changelog: v1.13.0...v1.14.0