diff --git a/src/strands/models/bedrock.py b/src/strands/models/bedrock.py index 3fa907995..83141b856 100644 --- a/src/strands/models/bedrock.py +++ b/src/strands/models/bedrock.py @@ -448,6 +448,15 @@ def _format_bedrock_messages(self, messages: Messages) -> list[dict[str, Any]]: # Create new message with cleaned content (skip if empty) if cleaned_content: + # Bedrock requires reasoningContent blocks to come first in assistant messages + # when thinking is enabled. Session managers or message reconstruction may + # produce blocks in wrong order, so we defensively reorder here. + if message["role"] == "assistant": + reasoning = [b for b in cleaned_content if "reasoningContent" in b] + other = [b for b in cleaned_content if "reasoningContent" not in b] + if reasoning and other and cleaned_content[0] != reasoning[0]: + cleaned_content = reasoning + other + cleaned_messages.append({"content": cleaned_content, "role": message["role"]}) if filtered_unknown_members: diff --git a/tests/strands/models/test_bedrock.py b/tests/strands/models/test_bedrock.py index 66fe8ab00..99967cd39 100644 --- a/tests/strands/models/test_bedrock.py +++ b/tests/strands/models/test_bedrock.py @@ -2817,3 +2817,38 @@ def test_guardrail_latest_message_disabled_does_not_wrap(model): assert "text" in formatted assert "guardContent" not in formatted + + +def test_format_bedrock_messages_reorders_reasoning_blocks_first(): + """Test that reasoning blocks are reordered to come first in assistant messages. + + Bedrock requires reasoningContent blocks to precede all other blocks in assistant + messages when thinking is enabled. Session managers may restore messages with blocks + in wrong order, so _format_bedrock_messages must defensively reorder them. + + Regression test for https://github.com/strands-agents/sdk-python/issues/1698 + """ + model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0") + + messages = [ + {"role": "user", "content": [{"text": "What is the status?"}]}, + { + "role": "assistant", + "content": [ + {"text": "I'll check the status."}, + {"reasoningContent": {"reasoningText": {"text": "Let me think...", "signature": "sig"}}}, + {"toolUse": {"toolUseId": "t1", "name": "check", "input": {}}}, + ], + }, + {"role": "user", "content": [{"toolResult": {"toolUseId": "t1", "content": [{"text": "OK"}]}}]}, + ] + + result = model._format_bedrock_messages(messages) + + assistant_msg = result[1] + assert assistant_msg["role"] == "assistant" + assert "reasoningContent" in assistant_msg["content"][0], ( + "reasoningContent must be the first block in assistant messages" + ) + assert "text" in assistant_msg["content"][1] + assert "toolUse" in assistant_msg["content"][2]