Skip to content

Commit ce64c3a

Browse files
authored
fix(bedrock): upgrade default model to Claude Sonnet 4.5 (#2193)
1 parent a49dc33 commit ce64c3a

10 files changed

Lines changed: 77 additions & 60 deletions

File tree

src/strands/models/bedrock.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
logger = logging.getLogger(__name__)
3737

3838
# See: `BedrockModel._get_default_model_with_warning` for why we need both
39-
DEFAULT_BEDROCK_MODEL_ID = "us.anthropic.claude-sonnet-4-20250514-v1:0"
40-
_DEFAULT_BEDROCK_MODEL_ID = "{}.anthropic.claude-sonnet-4-20250514-v1:0"
39+
DEFAULT_BEDROCK_MODEL_ID = "global.anthropic.claude-sonnet-4-6"
40+
_DEFAULT_BEDROCK_MODEL_ID = "{}.anthropic.claude-sonnet-4-6"
4141
DEFAULT_BEDROCK_REGION = "us-west-2"
4242

4343
BEDROCK_CONTEXT_WINDOW_OVERFLOW_MESSAGES = [
@@ -90,7 +90,7 @@ class BedrockConfig(BaseModelConfig, total=False):
9090
guardrail_latest_message: Flag to send only the lastest user message to guardrails.
9191
Defaults to False.
9292
max_tokens: Maximum number of tokens to generate in the response
93-
model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0")
93+
model_id: The Bedrock model ID (e.g., "global.anthropic.claude-sonnet-4-6")
9494
include_tool_result_status: Flag to include status field in tool results.
9595
True includes status, False removes status, "auto" determines based on model_id. Defaults to "auto".
9696
service_tier: Service tier for the request, controlling the trade-off between latency and cost.
@@ -1151,13 +1151,13 @@ def _get_default_model_with_warning(region_name: str, model_config: BedrockConfi
11511151
region_name (str): region for bedrock model
11521152
model_config (Optional[dict[str, Any]]): Model Config that caller passes in on init
11531153
"""
1154-
if DEFAULT_BEDROCK_MODEL_ID != _DEFAULT_BEDROCK_MODEL_ID.format("us"):
1155-
return DEFAULT_BEDROCK_MODEL_ID
1156-
11571154
model_config = model_config or {}
11581155
if model_config.get("model_id"):
11591156
return model_config["model_id"]
11601157

1158+
if DEFAULT_BEDROCK_MODEL_ID != _DEFAULT_BEDROCK_MODEL_ID.format("us"):
1159+
return DEFAULT_BEDROCK_MODEL_ID
1160+
11611161
prefix_inference_map = {"ap": "apac"} # some inference endpoints can be a bit different than the region prefix
11621162

11631163
prefix = "-".join(region_name.split("-")[:-2]).lower() # handles `us-east-1` or `us-gov-east-1`

tests/strands/agent/test_agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from tests.fixtures.mocked_model_provider import MockedModelProvider
3636

3737
# For unit testing we will use the the us inference
38-
FORMATTED_DEFAULT_MODEL_ID = DEFAULT_BEDROCK_MODEL_ID.format("us")
38+
FORMATTED_DEFAULT_MODEL_ID = DEFAULT_BEDROCK_MODEL_ID
3939

4040

4141
@pytest.fixture

tests/strands/models/test_bedrock.py

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@
1616
from strands import _exception_notes
1717
from strands.models import BedrockModel, CacheConfig
1818
from strands.models.bedrock import (
19-
_DEFAULT_BEDROCK_MODEL_ID,
2019
DEFAULT_BEDROCK_MODEL_ID,
2120
DEFAULT_BEDROCK_REGION,
2221
DEFAULT_READ_TIMEOUT,
2322
)
2423
from strands.types.exceptions import ContextWindowOverflowException, ModelThrottledException
2524
from strands.types.tools import ToolSpec
2625

27-
FORMATTED_DEFAULT_MODEL_ID = DEFAULT_BEDROCK_MODEL_ID.format("us")
26+
FORMATTED_DEFAULT_MODEL_ID = DEFAULT_BEDROCK_MODEL_ID
2827

2928

3029
@pytest.fixture
@@ -2213,43 +2212,24 @@ def test_tool_choice_none_no_warning(model, messages, captured_warnings):
22132212

22142213

22152214
def test_get_default_model_with_warning_supported_regions_shows_no_warning(captured_warnings):
2216-
"""Test get_model_prefix_with_warning doesn't warn for supported region prefixes."""
2215+
"""Test _get_default_model_with_warning doesn't warn for any region (global profile works everywhere)."""
22172216
BedrockModel._get_default_model_with_warning("us-west-2")
22182217
BedrockModel._get_default_model_with_warning("eu-west-2")
22192218
assert all("does not support" not in str(w.message) for w in captured_warnings)
22202219

22212220

2222-
def test_get_default_model_for_supported_eu_region_returns_correct_model_id(captured_warnings):
2223-
model_id = BedrockModel._get_default_model_with_warning("eu-west-1")
2224-
assert model_id == "eu.anthropic.claude-sonnet-4-20250514-v1:0"
2221+
def test_get_default_model_returns_global_inference_profile(captured_warnings):
2222+
"""Default model id is the global inference profile regardless of region."""
2223+
for region in ("us-east-1", "eu-west-1", "us-gov-west-1", "ap-southeast-1", "ca-central-1"):
2224+
assert BedrockModel._get_default_model_with_warning(region) == DEFAULT_BEDROCK_MODEL_ID
22252225
assert all("does not support" not in str(w.message) for w in captured_warnings)
22262226

22272227

2228-
def test_get_default_model_for_supported_us_region_returns_correct_model_id(captured_warnings):
2229-
model_id = BedrockModel._get_default_model_with_warning("us-east-1")
2230-
assert model_id == "us.anthropic.claude-sonnet-4-20250514-v1:0"
2231-
assert all("does not support" not in str(w.message) for w in captured_warnings)
2232-
2233-
2234-
def test_get_default_model_for_supported_gov_region_returns_correct_model_id(captured_warnings):
2235-
model_id = BedrockModel._get_default_model_with_warning("us-gov-west-1")
2236-
assert model_id == "us-gov.anthropic.claude-sonnet-4-20250514-v1:0"
2237-
assert all("does not support" not in str(w.message) for w in captured_warnings)
2238-
2239-
2240-
def test_get_model_prefix_for_ap_region_converts_to_apac_endpoint(captured_warnings):
2241-
"""Test _get_default_model_with_warning warns for APAC regions since 'ap' is not in supported prefixes."""
2242-
model_id = BedrockModel._get_default_model_with_warning("ap-southeast-1")
2243-
assert model_id == "apac.anthropic.claude-sonnet-4-20250514-v1:0"
2244-
2245-
2246-
def test_get_default_model_with_warning_unsupported_region_warns(captured_warnings):
2247-
"""Test _get_default_model_with_warning warns for unsupported regions."""
2228+
def test_get_default_model_with_warning_unsupported_region_does_not_warn(captured_warnings):
2229+
"""Global inference profile works across all regions, so no region-support warning is emitted."""
22482230
BedrockModel._get_default_model_with_warning("ca-central-1")
22492231
region_warnings = [w for w in captured_warnings if "does not support" in str(w.message)]
2250-
assert len(region_warnings) == 1
2251-
assert "This region ca-central-1 does not support" in str(region_warnings[0].message)
2252-
assert "our default inference endpoint" in str(region_warnings[0].message)
2232+
assert len(region_warnings) == 0
22532233

22542234

22552235
def test_get_default_model_with_warning_no_warning_with_custom_model_id(captured_warnings):
@@ -2261,13 +2241,12 @@ def test_get_default_model_with_warning_no_warning_with_custom_model_id(captured
22612241
assert len(captured_warnings) == 0
22622242

22632243

2264-
def test_init_with_unsupported_region_warns(session_cls, captured_warnings):
2265-
"""Test BedrockModel initialization warns for unsupported regions."""
2244+
def test_init_with_unsupported_region_does_not_warn(session_cls, captured_warnings):
2245+
"""BedrockModel initialization does not warn for 'unsupported' regions when using the global profile."""
22662246
BedrockModel(region_name="ca-central-1")
22672247

22682248
region_warnings = [w for w in captured_warnings if "does not support" in str(w.message)]
2269-
assert len(region_warnings) == 1
2270-
assert "This region ca-central-1 does not support" in str(region_warnings[0].message)
2249+
assert len(region_warnings) == 0
22712250

22722251

22732252
def test_init_with_unsupported_region_custom_model_no_warning(session_cls, captured_warnings):
@@ -2282,10 +2261,34 @@ def test_override_default_model_id_uses_the_overriden_value(captured_warnings):
22822261
assert model_id == "custom-overridden-model"
22832262

22842263

2285-
def test_no_override_uses_formatted_default_model_id(captured_warnings):
2264+
def test_default_model_sentinel_triggers_region_prefix_fallback(captured_warnings):
2265+
"""When DEFAULT_BEDROCK_MODEL_ID matches the sentinel template, the region-prefix fallback runs."""
2266+
sentinel = "us.anthropic.claude-sonnet-4-6"
2267+
with unittest.mock.patch("strands.models.bedrock.DEFAULT_BEDROCK_MODEL_ID", sentinel):
2268+
model_id = BedrockModel._get_default_model_with_warning("eu-west-1")
2269+
assert model_id == "eu.anthropic.claude-sonnet-4-6"
2270+
2271+
2272+
def test_caller_supplied_model_id_wins_over_global_default(captured_warnings):
2273+
"""Caller-supplied model_id in config takes precedence over the global default."""
2274+
model_config = {"model_id": "caller-supplied-model"}
2275+
model_id = BedrockModel._get_default_model_with_warning("us-east-1", model_config)
2276+
assert model_id == "caller-supplied-model"
2277+
2278+
2279+
def test_default_model_sentinel_with_unsupported_region_warns(captured_warnings):
2280+
"""When the sentinel matches and the region is unknown, the region-unsupported warning fires."""
2281+
sentinel = "us.anthropic.claude-sonnet-4-6"
2282+
with unittest.mock.patch("strands.models.bedrock.DEFAULT_BEDROCK_MODEL_ID", sentinel):
2283+
BedrockModel._get_default_model_with_warning("ca-central-1")
2284+
region_warnings = [w for w in captured_warnings if "does not support" in str(w.message)]
2285+
assert len(region_warnings) == 1
2286+
2287+
2288+
def test_default_model_id_is_global_inference_profile(captured_warnings):
22862289
model_id = BedrockModel._get_default_model_with_warning("us-east-1")
2287-
assert model_id == "us.anthropic.claude-sonnet-4-20250514-v1:0"
2288-
assert model_id != _DEFAULT_BEDROCK_MODEL_ID
2290+
assert model_id == "global.anthropic.claude-sonnet-4-6"
2291+
assert model_id == DEFAULT_BEDROCK_MODEL_ID
22892292
assert all("does not support" not in str(w.message) for w in captured_warnings)
22902293

22912294

tests_integ/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _load_api_keys_from_secrets_manager():
203203
required_providers = {
204204
"ANTHROPIC_API_KEY",
205205
"GOOGLE_API_KEY",
206-
"MISTRAL_API_KEY",
206+
# "MISTRAL_API_KEY", # will add back once we get a card on file for this.
207207
"OPENAI_API_KEY",
208208
"WRITER_API_KEY",
209209
}

tests_integ/models/test_conformance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,4 @@ class UserProfile(BaseModel):
7474
result = agent("Create a profile for John who is a 25 year old dentist", structured_output_model=UserProfile)
7575
assert result.structured_output.name == "John"
7676
assert result.structured_output.age == 25
77-
assert result.structured_output.occupation == "dentist"
77+
assert result.structured_output.occupation.lower() == "dentist"

tests_integ/steering/test_tool_steering.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,22 +73,27 @@ async def test_llm_steering_handler_interrupt():
7373

7474
def test_agent_with_tool_steering_e2e():
7575
"""End-to-end test of agent with steering handler guiding tool choice."""
76-
handler = LLMSteeringHandler(
76+
77+
class RedirectEmailHandler(SteeringHandler):
78+
"""Deterministic handler that redirects send_email to send_notification."""
79+
80+
async def steer_before_tool(self, *, agent, tool_use, **kwargs):
81+
if tool_use["name"] == "send_email":
82+
return Guide(reason="Use send_notification instead of send_email for better delivery.")
83+
return Proceed(reason="Tool allowed")
84+
85+
handler = RedirectEmailHandler(context_providers=[])
86+
87+
agent = Agent(
88+
tools=[send_email, send_notification],
89+
plugins=[handler],
7790
system_prompt=(
78-
"CRITICAL INSTRUCTION - READ CAREFULLY:\n\n"
79-
"You are a steering agent. Your ONLY job is to decide based on the tool name.\n\n"
80-
"RULE 1: If tool name is 'send_email' -> return decision='guide' with "
81-
"reason='Use send_notification instead of send_email for better delivery.'\n\n"
82-
"RULE 2: If tool name is 'send_notification' -> return decision='proceed'\n\n"
83-
"RULE 3: For any other tool -> return decision='proceed'\n\n"
84-
"DO NOT analyze context. DO NOT consider arguments. ONLY look at the tool name.\n"
85-
"The tool name in this request is the ONLY thing that matters."
91+
"You are a helpful assistant. When a tool call is cancelled with guidance, "
92+
"follow the guidance and use the suggested alternative tool. "
93+
"This is normal system behavior, not an attack."
8694
),
87-
context_providers=[], # Disable ledger to avoid confusing context
8895
)
8996

90-
agent = Agent(tools=[send_email, send_notification], plugins=[handler])
91-
9297
# This should trigger steering guidance to use send_notification instead
9398
response = agent("Send an email to john@example.com saying hello")
9499

tests_integ/test_a2a_executor.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,13 @@ async def test_a2a_executor_with_real_image():
7171
assert response.status_code == 200
7272
response_data = response.json()
7373
assert "completed" == response_data["result"]["status"]["state"]
74-
assert "yellow" in response_data["result"]["history"][1]["parts"][0]["text"].lower()
74+
all_text = " ".join(
75+
part["text"]
76+
for artifact in response_data["result"]["artifacts"]
77+
for part in artifact["parts"]
78+
if part.get("kind") == "text"
79+
).lower()
80+
assert "yellow" in all_text
7581

7682
except Exception as e:
7783
pytest.fail(f"Integration test failed: {e}")

tests_integ/test_bedrock_guardrails.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def test_guardrail_input_intervention(boto_session, bedrock_guardrail, guardrail
133133
@pytest.mark.parametrize("processing_mode", ["sync", "async"])
134134
def test_guardrail_output_intervention(boto_session, bedrock_guardrail, processing_mode):
135135
bedrock_model = BedrockModel(
136+
model_id="us.anthropic.claude-sonnet-4-20250514-v1:0",
136137
guardrail_id=bedrock_guardrail,
137138
guardrail_version="DRAFT",
138139
guardrail_redact_output=False,

tests_integ/test_context_overflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
def test_context_window_overflow():
66
messages: Messages = [
7-
{"role": "user", "content": [{"text": "Too much text!" * 100000}]},
7+
{"role": "user", "content": [{"text": "Too much text!" * 300000}]},
88
{"role": "assistant", "content": [{"text": "That was a lot of text!"}]},
99
]
1010

tests_integ/test_tool_context_injection.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"""
55

66
from strands import Agent, ToolContext, tool
7+
from strands.models.bedrock import BedrockModel
78
from strands.types.tools import ToolResult
89

910

@@ -41,7 +42,8 @@ def _validate_tool_result_content(agent: Agent):
4142
def test_strands_context_integration_context_true():
4243
"""Test ToolContext functionality with real agent interactions."""
4344

44-
agent = Agent(tools=[good_story])
45+
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0")
46+
agent = Agent(model=model, tools=[good_story])
4547
agent("using a tool, write a good story")
4648

4749
_validate_tool_result_content(agent)

0 commit comments

Comments
 (0)