Skip to content

Commit 1e6d12d

Browse files
authored
fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider (#686)
* fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider * fix: Remove status field from toolResult for non-claude 3 models in Bedrock model provider
1 parent 2db5226 commit 1e6d12d

File tree

2 files changed

+102
-9
lines changed

2 files changed

+102
-9
lines changed

src/strands/models/bedrock.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
"too many total text bytes",
3838
]
3939

40+
# Models that should include tool result status (include_tool_result_status = True)
41+
_MODELS_INCLUDE_STATUS = [
42+
"anthropic.claude",
43+
]
44+
4045
T = TypeVar("T", bound=BaseModel)
4146

4247

@@ -71,6 +76,8 @@ class BedrockConfig(TypedDict, total=False):
7176
guardrail_redact_output_message: If a Bedrock Output guardrail triggers, replace output with this message.
7277
max_tokens: Maximum number of tokens to generate in the response
7378
model_id: The Bedrock model ID (e.g., "us.anthropic.claude-sonnet-4-20250514-v1:0")
79+
include_tool_result_status: Flag to include status field in tool results.
80+
True includes status, False removes status, "auto" determines based on model_id. Defaults to "auto".
7481
stop_sequences: List of sequences that will stop generation when encountered
7582
streaming: Flag to enable/disable streaming. Defaults to True.
7683
temperature: Controls randomness in generation (higher = more random)
@@ -92,6 +99,7 @@ class BedrockConfig(TypedDict, total=False):
9299
guardrail_redact_output_message: Optional[str]
93100
max_tokens: Optional[int]
94101
model_id: str
102+
include_tool_result_status: Optional[Literal["auto"] | bool]
95103
stop_sequences: Optional[list[str]]
96104
streaming: Optional[bool]
97105
temperature: Optional[float]
@@ -119,7 +127,7 @@ def __init__(
119127
if region_name and boto_session:
120128
raise ValueError("Cannot specify both `region_name` and `boto_session`.")
121129

122-
self.config = BedrockModel.BedrockConfig(model_id=DEFAULT_BEDROCK_MODEL_ID)
130+
self.config = BedrockModel.BedrockConfig(model_id=DEFAULT_BEDROCK_MODEL_ID, include_tool_result_status="auto")
123131
self.update_config(**model_config)
124132

125133
logger.debug("config=<%s> | initializing", self.config)
@@ -169,6 +177,17 @@ def get_config(self) -> BedrockConfig:
169177
"""
170178
return self.config
171179

180+
def _should_include_tool_result_status(self) -> bool:
181+
"""Determine whether to include tool result status based on current config."""
182+
include_status = self.config.get("include_tool_result_status", "auto")
183+
184+
if include_status is True:
185+
return True
186+
elif include_status is False:
187+
return False
188+
else: # "auto"
189+
return any(model in self.config["model_id"] for model in _MODELS_INCLUDE_STATUS)
190+
172191
def format_request(
173192
self,
174193
messages: Messages,
@@ -282,10 +301,18 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages:
282301
# Create a new content block with only the cleaned toolResult
283302
tool_result: ToolResult = content_block["toolResult"]
284303

285-
# Keep only the required fields for Bedrock
286-
cleaned_tool_result = ToolResult(
287-
content=tool_result["content"], toolUseId=tool_result["toolUseId"], status=tool_result["status"]
288-
)
304+
if self._should_include_tool_result_status():
305+
# Include status field
306+
cleaned_tool_result = ToolResult(
307+
content=tool_result["content"],
308+
toolUseId=tool_result["toolUseId"],
309+
status=tool_result["status"],
310+
)
311+
else:
312+
# Remove status field
313+
cleaned_tool_result = ToolResult( # type: ignore[typeddict-item]
314+
toolUseId=tool_result["toolUseId"], content=tool_result["content"]
315+
)
289316

290317
cleaned_block: ContentBlock = {"toolResult": cleaned_tool_result}
291318
cleaned_content.append(cleaned_block)
@@ -296,7 +323,6 @@ def _format_bedrock_messages(self, messages: Messages) -> Messages:
296323
# Create new message with cleaned content
297324
cleaned_message: Message = Message(content=cleaned_content, role=message["role"])
298325
cleaned_messages.append(cleaned_message)
299-
300326
return cleaned_messages
301327

302328
def _has_blocked_guardrail(self, guardrail_data: dict[str, Any]) -> bool:

tests/strands/models/test_bedrock.py

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,6 @@ async def test_stream_stop_reason_override_non_streaming(bedrock_client, alist,
12751275

12761276

12771277
def test_format_request_cleans_tool_result_content_blocks(model, model_id):
1278-
"""Test that format_request cleans toolResult blocks by removing extra fields."""
12791278
messages = [
12801279
{
12811280
"role": "user",
@@ -1295,9 +1294,77 @@ def test_format_request_cleans_tool_result_content_blocks(model, model_id):
12951294

12961295
formatted_request = model.format_request(messages)
12971296

1298-
# Verify toolResult only contains allowed fields in the formatted request
12991297
tool_result = formatted_request["messages"][0]["content"][0]["toolResult"]
1300-
expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"}
1298+
expected = {"toolUseId": "tool123", "content": [{"text": "Tool output"}]}
13011299
assert tool_result == expected
13021300
assert "extraField" not in tool_result
13031301
assert "mcpMetadata" not in tool_result
1302+
assert "status" not in tool_result
1303+
1304+
1305+
def test_format_request_removes_status_field_when_configured(model, model_id):
1306+
model.update_config(include_tool_result_status=False)
1307+
1308+
messages = [
1309+
{
1310+
"role": "user",
1311+
"content": [
1312+
{
1313+
"toolResult": {
1314+
"content": [{"text": "Tool output"}],
1315+
"toolUseId": "tool123",
1316+
"status": "success",
1317+
}
1318+
},
1319+
],
1320+
}
1321+
]
1322+
1323+
formatted_request = model.format_request(messages)
1324+
1325+
tool_result = formatted_request["messages"][0]["content"][0]["toolResult"]
1326+
expected = {"toolUseId": "tool123", "content": [{"text": "Tool output"}]}
1327+
assert tool_result == expected
1328+
assert "status" not in tool_result
1329+
1330+
1331+
def test_auto_behavior_anthropic_vs_non_anthropic(bedrock_client):
1332+
model_anthropic = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0")
1333+
assert model_anthropic.get_config()["include_tool_result_status"] == "auto"
1334+
1335+
model_non_anthropic = BedrockModel(model_id="amazon.titan-text-v1")
1336+
assert model_non_anthropic.get_config()["include_tool_result_status"] == "auto"
1337+
1338+
1339+
def test_explicit_boolean_values_preserved(bedrock_client):
1340+
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0", include_tool_result_status=True)
1341+
assert model.get_config()["include_tool_result_status"] is True
1342+
1343+
model2 = BedrockModel(model_id="amazon.titan-text-v1", include_tool_result_status=False)
1344+
assert model2.get_config()["include_tool_result_status"] is False
1345+
"""Test that format_request keeps status field by default for anthropic.claude models."""
1346+
# Default model is anthropic.claude, so should keep status
1347+
model = BedrockModel()
1348+
1349+
messages = [
1350+
{
1351+
"role": "user",
1352+
"content": [
1353+
{
1354+
"toolResult": {
1355+
"content": [{"text": "Tool output"}],
1356+
"toolUseId": "tool123",
1357+
"status": "success",
1358+
}
1359+
},
1360+
],
1361+
}
1362+
]
1363+
1364+
formatted_request = model.format_request(messages)
1365+
1366+
# Verify toolResult contains status field by default
1367+
tool_result = formatted_request["messages"][0]["content"][0]["toolResult"]
1368+
expected = {"content": [{"text": "Tool output"}], "toolUseId": "tool123", "status": "success"}
1369+
assert tool_result == expected
1370+
assert "status" in tool_result

0 commit comments

Comments
 (0)