From 208d081988b982b70ad781f9c8e1396a75c1f8d7 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Sun, 17 Aug 2025 16:51:40 -0700 Subject: [PATCH 01/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 17 ++++++-- src/strands/types/tools.py | 4 +- tests/strands/models/test_bedrock.py | 58 ++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index ace35640a..bc191e4f8 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -276,10 +276,19 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: # Create a new content block with only the cleaned toolResult tool_result: ToolResult = content_block["toolResult"] - # Keep only the required fields for Bedrock - cleaned_tool_result = ToolResult( - content=tool_result["content"], toolUseId=tool_result["toolUseId"], status=tool_result["status"] - ) + model_id = self.config.get("model_id") + if "claude-3" in model_id: + # Keep the status field for Claude models + cleaned_tool_result = ToolResult( + content=tool_result["content"], + toolUseId=tool_result["toolUseId"], + status=tool_result["status"], + ) + else: + # For non-Claude models, use ToolResult without status + cleaned_tool_result = ToolResult( + toolUseId=tool_result["toolUseId"], content=tool_result["content"] + ) cleaned_block: ContentBlock = {"toolResult": cleaned_tool_result} cleaned_content.append(cleaned_block) diff --git a/src/strands/types/tools.py b/src/strands/types/tools.py index bb7c874f6..918b9c0b8 100644 --- a/src/strands/types/tools.py +++ b/src/strands/types/tools.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Protocol, Union +from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Optional, Protocol, Union from typing_extensions import TypedDict @@ -91,7 +91,7 @@ class ToolResult(TypedDict): """ content: list[ToolResultContent] - status: ToolResultStatus + status: Optional[ToolResultStatus] toolUseId: str diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 09e508845..83449848d 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1237,3 +1237,61 @@ def test_format_request_cleans_tool_result_content_blocks(model, model_id): assert tool_result == expected assert "extraField" not in tool_result assert "mcpMetadata" not in tool_result + + +def test_format_request_removes_status_field_for_non_claude_models(model, model_id): + """Test that format_request removes status field for non-Claude models.""" + # Update model to use a non-Claude model + model.update_config(model_id="us.writer.palmyra-x4-v1:0") + + messages = [ + { + "role": "user", + "content": [ + { + "toolResult": { + "content": [{"text": "Tool output"}], + "toolUseId": "tool123", + "status": "success", + } + }, + ], + } + ] + + formatted_request = model.format_request(messages) + + # Verify toolResult does not contain status field for non-Claude models + tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] + expected = {"toolUseId": "tool123", "content": [{"text": "Tool output"}]} + assert tool_result == expected + assert "status" not in tool_result + + +def test_format_request_keeps_status_field_for_claude_models(model, model_id): + """Test that format_request keeps status field for Claude models.""" + # Update model to use a Claude model + model.update_config(model_id="anthropic.claude-3-sonnet-20240229-v1:0") + + messages = [ + { + "role": "user", + "content": [ + { + "toolResult": { + "content": [{"text": "Tool output"}], + "toolUseId": "tool123", + "status": "success", + } + }, + ], + } + ] + + formatted_request = model.format_request(messages) + + # Verify toolResult contains status field for Claude models + tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] + expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"} + assert tool_result == expected + assert "status" in tool_result From 552737623c3906c7bd18b6c26c646594cf8c3577 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Sun, 17 Aug 2025 17:02:24 -0700 Subject: [PATCH 02/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- tests/strands/models/test_bedrock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 83449848d..25a5557d9 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1233,7 +1233,7 @@ def test_format_request_cleans_tool_result_content_blocks(model, model_id): # Verify toolResult only contains allowed fields in the formatted request tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] - expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"} + expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123"} assert tool_result == expected assert "extraField" not in tool_result assert "mcpMetadata" not in tool_result From a19d4f8df5851d0270244303e79c1ae04a41b116 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Tue, 19 Aug 2025 09:05:56 -0400 Subject: [PATCH 03/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 2 +- src/strands/types/tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index bc191e4f8..e20daf26c 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -277,7 +277,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: tool_result: ToolResult = content_block["toolResult"] model_id = self.config.get("model_id") - if "claude-3" in model_id: + if model_id and "claude-3" in model_id: # Keep the status field for Claude models cleaned_tool_result = ToolResult( content=tool_result["content"], diff --git a/src/strands/types/tools.py b/src/strands/types/tools.py index 918b9c0b8..86b5160d5 100644 --- a/src/strands/types/tools.py +++ b/src/strands/types/tools.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Optional, Protocol, Union -from typing_extensions import TypedDict +from typing_extensions import NotRequired, TypedDict from .media import DocumentContent, ImageContent @@ -91,7 +91,7 @@ class ToolResult(TypedDict): """ content: list[ToolResultContent] - status: Optional[ToolResultStatus] + status: NotRequired[Optional[ToolResultStatus]] toolUseId: str From f2489f4177241f7a243b9a3f0952e7cd5d8f28b4 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Tue, 19 Aug 2025 09:14:27 -0400 Subject: [PATCH 04/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index e20daf26c..dae9ed1fc 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -277,7 +277,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: tool_result: ToolResult = content_block["toolResult"] model_id = self.config.get("model_id") - if model_id and "claude-3" in model_id: + if model_id and "claude" in model_id: # Keep the status field for Claude models cleaned_tool_result = ToolResult( content=tool_result["content"], From 88ec21474403eef3843a428dd8246af7efab2a33 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Wed, 20 Aug 2025 08:54:23 -0400 Subject: [PATCH 05/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 2 +- src/strands/types/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index dae9ed1fc..08da61af2 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -277,7 +277,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: tool_result: ToolResult = content_block["toolResult"] model_id = self.config.get("model_id") - if model_id and "claude" in model_id: + if model_id and "anthropic.claude" in model_id: # Keep the status field for Claude models cleaned_tool_result = ToolResult( content=tool_result["content"], diff --git a/src/strands/types/tools.py b/src/strands/types/tools.py index 86b5160d5..d6fef6bab 100644 --- a/src/strands/types/tools.py +++ b/src/strands/types/tools.py @@ -91,7 +91,7 @@ class ToolResult(TypedDict): """ content: list[ToolResultContent] - status: NotRequired[Optional[ToolResultStatus]] + status: NotRequired[ToolResultStatus] toolUseId: str From b0dda32a82c517a5ee529b0134adcd1bb7afcf32 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Wed, 20 Aug 2025 08:57:28 -0400 Subject: [PATCH 06/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/types/tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strands/types/tools.py b/src/strands/types/tools.py index d6fef6bab..459e9cea5 100644 --- a/src/strands/types/tools.py +++ b/src/strands/types/tools.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass -from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Optional, Protocol, Union +from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Protocol, Union from typing_extensions import NotRequired, TypedDict From a919bb6e5a036d66c8617ec23084d26f6830b4d8 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Fri, 22 Aug 2025 10:20:11 -0400 Subject: [PATCH 07/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 17 +++++++++-------- tests/strands/models/test_bedrock.py | 21 +++++++++------------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index 08da61af2..2160896d3 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -68,6 +68,7 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: If a Bedrock Output guardrail triggers, replace output with this message. max_tokens: Maximum number of tokens to generate in the response model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0") + remove_tool_result_status: Flag to remove status field from tool results. Defaults to auto-detect based on model. stop_sequences: List of sequences that will stop generation when encountered streaming: Flag to enable/disable streaming. Defaults to True. temperature: Controls randomness in generation (higher = more random) @@ -89,6 +90,7 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: Optional[str] max_tokens: Optional[int] model_id: str + remove_tool_result_status: Optional[bool] stop_sequences: Optional[list[str]] streaming: Optional[bool] temperature: Optional[float] @@ -276,18 +278,17 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: # Create a new content block with only the cleaned toolResult tool_result: ToolResult = content_block["toolResult"] - model_id = self.config.get("model_id") - if model_id and "anthropic.claude" in model_id: - # Keep the status field for Claude models + if self.config.get("remove_tool_result_status", False): + # Remove status field when explicitly configured cleaned_tool_result = ToolResult( - content=tool_result["content"], - toolUseId=tool_result["toolUseId"], - status=tool_result["status"], + toolUseId=tool_result["toolUseId"], content=tool_result["content"] ) else: - # For non-Claude models, use ToolResult without status + # Keep status field by default cleaned_tool_result = ToolResult( - toolUseId=tool_result["toolUseId"], content=tool_result["content"] + content=tool_result["content"], + toolUseId=tool_result["toolUseId"], + status=tool_result["status"], ) cleaned_block: ContentBlock = {"toolResult": cleaned_tool_result} diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 25a5557d9..1515cc317 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1233,16 +1233,16 @@ def test_format_request_cleans_tool_result_content_blocks(model, model_id): # Verify toolResult only contains allowed fields in the formatted request tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] - expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123"} + expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"} assert tool_result == expected assert "extraField" not in tool_result assert "mcpMetadata" not in tool_result -def test_format_request_removes_status_field_for_non_claude_models(model, model_id): - """Test that format_request removes status field for non-Claude models.""" - # Update model to use a non-Claude model - model.update_config(model_id="us.writer.palmyra-x4-v1:0") +def test_format_request_removes_status_field_when_configured(model, model_id): + """Test that format_request removes status field when remove_tool_result_status=True.""" + # Configure model to remove status field + model.update_config(remove_tool_result_status=True) messages = [ { @@ -1261,18 +1261,15 @@ def test_format_request_removes_status_field_for_non_claude_models(model, model_ formatted_request = model.format_request(messages) - # Verify toolResult does not contain status field for non-Claude models + # Verify toolResult does not contain status field when configured to remove tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] expected = {"toolUseId": "tool123", "content": [{"text": "Tool output"}]} assert tool_result == expected assert "status" not in tool_result -def test_format_request_keeps_status_field_for_claude_models(model, model_id): - """Test that format_request keeps status field for Claude models.""" - # Update model to use a Claude model - model.update_config(model_id="anthropic.claude-3-sonnet-20240229-v1:0") - +def test_format_request_keeps_status_field_by_default(model, model_id): + """Test that format_request keeps status field by default.""" messages = [ { "role": "user", @@ -1290,7 +1287,7 @@ def test_format_request_keeps_status_field_for_claude_models(model, model_id): formatted_request = model.format_request(messages) - # Verify toolResult contains status field for Claude models + # Verify toolResult contains status field by default tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"} assert tool_result == expected From c314ad853615bc3db74a958495a5ee56225c979d Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Fri, 22 Aug 2025 10:22:13 -0400 Subject: [PATCH 08/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index 2160896d3..82fe4aae0 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -68,7 +68,7 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: If a Bedrock Output guardrail triggers, replace output with this message. max_tokens: Maximum number of tokens to generate in the response model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0") - remove_tool_result_status: Flag to remove status field from tool results. Defaults to auto-detect based on model. + remove_tool_result_status: Flag to remove status field from tool results. Defaults to False. stop_sequences: List of sequences that will stop generation when encountered streaming: Flag to enable/disable streaming. Defaults to True. temperature: Controls randomness in generation (higher = more random) From 004eaa601ecbdee07ed113719b82a52162f02ceb Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Thu, 28 Aug 2025 10:40:18 -0400 Subject: [PATCH 09/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 6 +++--- src/strands/types/tools.py | 4 ++-- tests/strands/models/test_bedrock.py | 30 +++++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index 82fe4aae0..c8e6f4f8b 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -68,7 +68,7 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: If a Bedrock Output guardrail triggers, replace output with this message. max_tokens: Maximum number of tokens to generate in the response model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0") - remove_tool_result_status: Flag to remove status field from tool results. Defaults to False. + remove_tool_result_status: Flag to remove status field from tool results. True removes status, False keeps status, "auto" keeps status. Defaults to None. stop_sequences: List of sequences that will stop generation when encountered streaming: Flag to enable/disable streaming. Defaults to True. temperature: Controls randomness in generation (higher = more random) @@ -90,7 +90,7 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: Optional[str] max_tokens: Optional[int] model_id: str - remove_tool_result_status: Optional[bool] + remove_tool_result_status: Optional[Literal["auto"] | bool] stop_sequences: Optional[list[str]] streaming: Optional[bool] temperature: Optional[float] @@ -278,7 +278,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: # Create a new content block with only the cleaned toolResult tool_result: ToolResult = content_block["toolResult"] - if self.config.get("remove_tool_result_status", False): + if self.config.get("remove_tool_result_status") is True: # Remove status field when explicitly configured cleaned_tool_result = ToolResult( toolUseId=tool_result["toolUseId"], content=tool_result["content"] diff --git a/src/strands/types/tools.py b/src/strands/types/tools.py index 459e9cea5..bb7c874f6 100644 --- a/src/strands/types/tools.py +++ b/src/strands/types/tools.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, AsyncGenerator, Awaitable, Callable, Literal, Protocol, Union -from typing_extensions import NotRequired, TypedDict +from typing_extensions import TypedDict from .media import DocumentContent, ImageContent @@ -91,7 +91,7 @@ class ToolResult(TypedDict): """ content: list[ToolResultContent] - status: NotRequired[ToolResultStatus] + status: ToolResultStatus toolUseId: str diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 1515cc317..c9a463e93 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -1268,7 +1268,35 @@ def test_format_request_removes_status_field_when_configured(model, model_id): assert "status" not in tool_result -def test_format_request_keeps_status_field_by_default(model, model_id): +def test_format_request_keeps_status_field_with_auto(model, model_id): + """Test that format_request keeps status field when remove_tool_result_status="auto".""" + # Configure model with auto setting + model.update_config(remove_tool_result_status="auto") + + messages = [ + { + "role": "user", + "content": [ + { + "toolResult": { + "content": [{"text": "Tool output"}], + "toolUseId": "tool123", + "status": "success", + } + }, + ], + } + ] + + formatted_request = model.format_request(messages) + + # Verify toolResult contains status field with auto setting + tool_result = formatted_request["messages"][0]["content"][0]["toolResult"] + expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"} + assert tool_result == expected + assert "status" in tool_result + + """Test that format_request keeps status field by default.""" messages = [ { From d9d7c12241362cdea3ddff861c10feb31e194e10 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Thu, 28 Aug 2025 10:41:44 -0400 Subject: [PATCH 10/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index c8e6f4f8b..5cf2195c1 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -68,7 +68,8 @@ class BedrockConfig(TypedDict, total=False): guardrail_redact_output_message: If a Bedrock Output guardrail triggers, replace output with this message. max_tokens: Maximum number of tokens to generate in the response model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0") - remove_tool_result_status: Flag to remove status field from tool results. True removes status, False keeps status, "auto" keeps status. Defaults to None. + remove_tool_result_status: Flag to remove status field from tool results. + True removes status, False keeps status, "auto" keeps status. Defaults to None. stop_sequences: List of sequences that will stop generation when encountered streaming: Flag to enable/disable streaming. Defaults to True. temperature: Controls randomness in generation (higher = more random) From 9139015f03d6a43dd793f9fa37f38a5c71415d86 Mon Sep 17 00:00:00 2001 From: Rachit Mehta Date: Thu, 28 Aug 2025 10:46:14 -0400 Subject: [PATCH 11/11] fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider --- src/strands/models/bedrock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index 5cf2195c1..2a0ac903e 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -281,7 +281,7 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages: if self.config.get("remove_tool_result_status") is True: # Remove status field when explicitly configured - cleaned_tool_result = ToolResult( + cleaned_tool_result = ToolResult( # type: ignore[typeddict-item] toolUseId=tool_result["toolUseId"], content=tool_result["content"] ) else: