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
15 changes: 14 additions & 1 deletion src/strands/models/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def get_config(self) -> BedrockConfig:
def _should_include_tool_result_status(self) -> bool:
"""Determine whether to include tool result status based on current config."""
include_status = self.config.get("include_tool_result_status", "auto")

if include_status is True:
return True
elif include_status is False:
Expand Down Expand Up @@ -275,6 +275,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages:
"""Format messages for Bedrock API compatibility.

This function ensures messages conform to Bedrock's expected format by:
- Filtering out SDK_UNKNOWN_MEMBER content blocks
- Cleaning tool result content blocks by removing additional fields that may be
useful for retaining information in hooks but would cause Bedrock validation
exceptions when presented with unexpected fields
Expand All @@ -292,11 +293,17 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages:
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ToolResultBlock.html
"""
cleaned_messages = []
filtered_unknown_members = False

for message in messages:
cleaned_content: list[ContentBlock] = []

for content_block in message["content"]:
# Filter out SDK_UNKNOWN_MEMBER content blocks
if "SDK_UNKNOWN_MEMBER" in content_block:
filtered_unknown_members = True
continue

if "toolResult" in content_block:
# Create a new content block with only the cleaned toolResult
tool_result: ToolResult = content_block["toolResult"]
Expand All @@ -323,6 +330,12 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages:
# Create new message with cleaned content
cleaned_message: Message = Message(content=cleaned_content, role=message["role"])
cleaned_messages.append(cleaned_message)

if filtered_unknown_members:
logger.warning(
"Filtered out SDK_UNKNOWN_MEMBER content blocks from messages, consider upgrading boto3 version"
)

return cleaned_messages

def _has_blocked_guardrail(self, guardrail_data: dict[str, Any]) -> bool:
Expand Down
28 changes: 26 additions & 2 deletions tests/strands/models/test_bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -1331,15 +1331,15 @@ def test_format_request_removes_status_field_when_configured(model, model_id):
def test_auto_behavior_anthropic_vs_non_anthropic(bedrock_client):
model_anthropic = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0")
assert model_anthropic.get_config()["include_tool_result_status"] == "auto"

model_non_anthropic = BedrockModel(model_id="amazon.titan-text-v1")
assert model_non_anthropic.get_config()["include_tool_result_status"] == "auto"


def test_explicit_boolean_values_preserved(bedrock_client):
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0", include_tool_result_status=True)
assert model.get_config()["include_tool_result_status"] is True

model2 = BedrockModel(model_id="amazon.titan-text-v1", include_tool_result_status=False)
assert model2.get_config()["include_tool_result_status"] is False
"""Test that format_request keeps status field by default for anthropic.claude models."""
Expand Down Expand Up @@ -1368,3 +1368,27 @@ def test_explicit_boolean_values_preserved(bedrock_client):
expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"}
assert tool_result == expected
assert "status" in tool_result


def test_format_request_filters_sdk_unknown_member_content_blocks(model, model_id, caplog):
"""Test that format_request filters out SDK_UNKNOWN_MEMBER content blocks."""
messages = [
{
"role": "assistant",
"content": [
{"text": "Hello"},
{"SDK_UNKNOWN_MEMBER": {"name": "reasoningContent"}},
{"text": "World"},
],
}
]

formatted_request = model.format_request(messages)

content = formatted_request["messages"][0]["content"]
assert len(content) == 2
assert content[0] == {"text": "Hello"}
assert content[1] == {"text": "World"}

for block in content:
assert "SDK_UNKNOWN_MEMBER" not in block
Loading