Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions src/strands/models/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,32 @@ def stream(self, request: dict[str, Any]) -> Iterable[StreamEvent]:
logger.warning("bedrock threw context window overflow error")
raise ContextWindowOverflowException(e) from e

region = self.client.meta.region_name

# add_note added in Python 3.11
if hasattr(e, "add_note"):
# Aid in debugging by adding more information
e.add_note(f"└ Bedrock region: {region}")
e.add_note(f"└ Model id: {self.config.get('model_id')}")

if (
e.response["Error"]["Code"] == "AccessDeniedException"
and "You don't have access to the model" in error_message
):
e.add_note(
"└ For more information see "
"https://strandsagents.com/user-guide/concepts/model-providers/amazon-bedrock/#model-access-issue"
)

if (
e.response["Error"]["Code"] == "ValidationException"
and "with on-demand throughput isn’t supported" in error_message
):
e.add_note(
"└ For more information see "
"https://strandsagents.com/latest/user-guide/concepts/model-providers/amazon-bedrock/#on-demand-throughput-isnt-supported"
)

# Otherwise raise the error
raise e

Expand Down
83 changes: 82 additions & 1 deletion tests/strands/models/test_bedrock.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys
import unittest.mock

import boto3
Expand All @@ -15,7 +16,10 @@
@pytest.fixture
def bedrock_client():
with unittest.mock.patch.object(strands.models.bedrock.boto3, "Session") as mock_session_cls:
yield mock_session_cls.return_value.client.return_value
mock_client = mock_session_cls.return_value.client.return_value
mock_client.meta = unittest.mock.MagicMock()
mock_client.meta.region_name = "us-west-2"
yield mock_client


@pytest.fixture
Expand Down Expand Up @@ -1029,3 +1033,80 @@ def test_converse_output_guardrails_redacts_output(bedrock_client):

bedrock_client.converse.assert_called_once()
bedrock_client.converse_stream.assert_not_called()


@pytest.mark.skipif(sys.version_info < (3, 11), reason="This test requires Python 3.11 or higher (need add_note)")
def test_add_note_on_client_error(bedrock_client, model):
"""Test that add_note is called on ClientError with region and model ID information."""
# Mock the client error response
error_response = {"Error": {"Code": "ValidationException", "Message": "Some error message"}}
bedrock_client.converse_stream.side_effect = ClientError(error_response, "ConversationStream")

# Call the stream method which should catch and add notes to the exception
with pytest.raises(ClientError) as err:
list(model.stream({"modelId": "test-model"}))

assert err.value.__notes__ == ["└ Bedrock region: us-west-2", "└ Model id: m1"]


def test_no_add_note_when_not_available(bedrock_client, model):
"""Verify that on any python version (even < 3.11 where add_note is not available, we get the right exception)."""
# Mock the client error response
error_response = {"Error": {"Code": "ValidationException", "Message": "Some error message"}}
bedrock_client.converse_stream.side_effect = ClientError(error_response, "ConversationStream")

# Call the stream method which should catch and add notes to the exception
with pytest.raises(ClientError):
list(model.stream({"modelId": "test-model"}))


@pytest.mark.skipif(sys.version_info < (3, 11), reason="This test requires Python 3.11 or higher (need add_note)")
def test_add_note_on_access_denied_exception(bedrock_client, model):
"""Test that add_note adds documentation link for AccessDeniedException."""
# Mock the client error response for access denied
error_response = {
"Error": {
"Code": "AccessDeniedException",
"Message": "An error occurred (AccessDeniedException) when calling the ConverseStream operation: "
"You don't have access to the model with the specified model ID.",
}
}
bedrock_client.converse_stream.side_effect = ClientError(error_response, "ConversationStream")

# Call the stream method which should catch and add notes to the exception
with pytest.raises(ClientError) as err:
list(model.stream({"modelId": "test-model"}))

assert err.value.__notes__ == [
"└ Bedrock region: us-west-2",
"└ Model id: m1",
"└ For more information see "
"https://strandsagents.com/user-guide/concepts/model-providers/amazon-bedrock/#model-access-issue",
]


@pytest.mark.skipif(sys.version_info < (3, 11), reason="This test requires Python 3.11 or higher (need add_note)")
def test_add_note_on_validation_exception_throughput(bedrock_client, model):
"""Test that add_note adds documentation link for ValidationException about on-demand throughput."""
# Mock the client error response for validation exception
error_response = {
"Error": {
"Code": "ValidationException",
"Message": "An error occurred (ValidationException) when calling the ConverseStream operation: "
"Invocation of model ID anthropic.claude-3-7-sonnet-20250219-v1:0 with on-demand throughput "
"isn’t supported. Retry your request with the ID or ARN of an inference profile that contains "
"this model.",
}
}
bedrock_client.converse_stream.side_effect = ClientError(error_response, "ConversationStream")

# Call the stream method which should catch and add notes to the exception
with pytest.raises(ClientError) as err:
list(model.stream({"modelId": "test-model"}))

assert err.value.__notes__ == [
"└ Bedrock region: us-west-2",
"└ Model id: m1",
"└ For more information see "
"https://strandsagents.com/latest/user-guide/concepts/model-providers/amazon-bedrock/#on-demand-throughput-isnt-supported",
]