From 17b8c226a0bac90e3dfc453c7b4ca64fee2efc08 Mon Sep 17 00:00:00 2001 From: Mackenzie Zastrow Date: Wed, 25 Jun 2025 13:35:43 -0400 Subject: [PATCH] feat: Add additional exception information for bedrock We've received customer confusion regarding some of the errors when attempting to run on bedrock. To help customers understand what's going wrong, add region and model information to exceptions coming out of the bedrock provider & point them to our docs when possible. Semi-related to #238 --- src/strands/models/bedrock.py | 26 +++++++++ tests/strands/models/test_bedrock.py | 83 +++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index a5ffb539d..5448f96e0 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -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 diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 137b57c8c..3ed72973b 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1,4 +1,5 @@ import os +import sys import unittest.mock import boto3 @@ -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 @@ -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", + ]