@@ -2856,3 +2856,176 @@ def test_guardrail_latest_message_disabled_does_not_wrap(model):
28562856
28572857 assert "text" in formatted
28582858 assert "guardContent" not in formatted
2859+
2860+
2861+ # --- Tests for thinking-enabled multi-turn reasoningContent handling ---
2862+
2863+
2864+ def test_format_request_injects_reasoning_when_thinking_enabled_and_missing (bedrock_client , model_id ):
2865+ """When thinking is enabled, assistant messages without reasoningContent get a redactedContent placeholder."""
2866+ model = BedrockModel (
2867+ model_id = model_id ,
2868+ additional_request_fields = {"thinking" : {"type" : "enabled" , "budget_tokens" : 4096 }},
2869+ )
2870+
2871+ messages = [
2872+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
2873+ {
2874+ "role" : "assistant" ,
2875+ "content" : [
2876+ {"text" : "I'll check." },
2877+ {"toolUse" : {"toolUseId" : "t1" , "name" : "lookup" , "input" : {}}},
2878+ ],
2879+ },
2880+ {"role" : "user" , "content" : [{"toolResult" : {"toolUseId" : "t1" , "content" : [{"text" : "result" }]}}]},
2881+ ]
2882+
2883+ request = model ._format_request (messages )
2884+ assistant_msg = request ["messages" ][1 ]
2885+ assert assistant_msg ["role" ] == "assistant"
2886+
2887+ # The first content block must be a reasoningContent placeholder
2888+ first_block = assistant_msg ["content" ][0 ]
2889+ assert "reasoningContent" in first_block
2890+ assert "redactedContent" in first_block ["reasoningContent" ]
2891+
2892+ # Original content blocks should follow
2893+ assert "text" in assistant_msg ["content" ][1 ]
2894+ assert "toolUse" in assistant_msg ["content" ][2 ]
2895+
2896+
2897+ def test_format_request_no_injection_when_thinking_disabled (bedrock_client , model_id ):
2898+ """When thinking is not enabled, assistant messages are not modified."""
2899+ model = BedrockModel (model_id = model_id )
2900+
2901+ messages = [
2902+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
2903+ {
2904+ "role" : "assistant" ,
2905+ "content" : [
2906+ {"text" : "Response." },
2907+ {"toolUse" : {"toolUseId" : "t1" , "name" : "lookup" , "input" : {}}},
2908+ ],
2909+ },
2910+ ]
2911+
2912+ request = model ._format_request (messages )
2913+ assistant_msg = request ["messages" ][1 ]
2914+
2915+ # No reasoningContent should be injected
2916+ assert not any ("reasoningContent" in cb for cb in assistant_msg ["content" ])
2917+ assert "text" in assistant_msg ["content" ][0 ]
2918+ assert "toolUse" in assistant_msg ["content" ][1 ]
2919+
2920+
2921+ def test_format_request_preserves_existing_reasoning_when_thinking_enabled (bedrock_client , model_id ):
2922+ """When thinking is enabled and reasoningContent already exists, it should be preserved (not duplicated)."""
2923+ model = BedrockModel (
2924+ model_id = model_id ,
2925+ additional_request_fields = {"thinking" : {"type" : "enabled" , "budget_tokens" : 4096 }},
2926+ )
2927+
2928+ messages = [
2929+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
2930+ {
2931+ "role" : "assistant" ,
2932+ "content" : [
2933+ {"reasoningContent" : {"reasoningText" : {"text" : "Let me think..." , "signature" : "sig123" }}},
2934+ {"text" : "Here's my response." },
2935+ ],
2936+ },
2937+ ]
2938+
2939+ request = model ._format_request (messages )
2940+ assistant_msg = request ["messages" ][1 ]
2941+
2942+ # reasoningContent should be first and preserved (not duplicated)
2943+ assert len (assistant_msg ["content" ]) == 2
2944+ assert "reasoningContent" in assistant_msg ["content" ][0 ]
2945+ assert assistant_msg ["content" ][0 ]["reasoningContent" ]["reasoningText" ]["text" ] == "Let me think..."
2946+ assert "text" in assistant_msg ["content" ][1 ]
2947+
2948+
2949+ def test_format_request_reorders_reasoning_to_first_when_thinking_enabled (bedrock_client , model_id ):
2950+ """When thinking is enabled and reasoningContent is not the first block, it should be moved to front."""
2951+ model = BedrockModel (
2952+ model_id = model_id ,
2953+ additional_request_fields = {"thinking" : {"type" : "enabled" , "budget_tokens" : 4096 }},
2954+ )
2955+
2956+ messages = [
2957+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
2958+ {
2959+ "role" : "assistant" ,
2960+ "content" : [
2961+ {"text" : "Response first." },
2962+ {"reasoningContent" : {"reasoningText" : {"text" : "Thinking after text" , "signature" : "s1" }}},
2963+ {"toolUse" : {"toolUseId" : "t1" , "name" : "test" , "input" : {}}},
2964+ ],
2965+ },
2966+ ]
2967+
2968+ request = model ._format_request (messages )
2969+ assistant_msg = request ["messages" ][1 ]
2970+
2971+ # reasoningContent must be first
2972+ assert "reasoningContent" in assistant_msg ["content" ][0 ]
2973+ # Followed by non-reasoning blocks
2974+ assert "text" in assistant_msg ["content" ][1 ]
2975+ assert "toolUse" in assistant_msg ["content" ][2 ]
2976+
2977+
2978+ def test_format_request_multi_turn_thinking_session_reload (bedrock_client , model_id ):
2979+ """Simulate a session reload where assistant messages lost reasoningContent blocks."""
2980+ model = BedrockModel (
2981+ model_id = model_id ,
2982+ additional_request_fields = {"thinking" : {"type" : "enabled" , "budget_tokens" : 4096 }},
2983+ )
2984+
2985+ # Simulate messages loaded from a session manager that stripped reasoningContent
2986+ messages = [
2987+ {"role" : "user" , "content" : [{"text" : "What is the status of zone X?" }]},
2988+ {
2989+ "role" : "assistant" ,
2990+ "content" : [
2991+ {"text" : "I'll check the status." },
2992+ {"toolUse" : {"toolUseId" : "tool_123" , "name" : "zoneStatus" , "input" : {"zone" : "X" }}},
2993+ ],
2994+ },
2995+ {
2996+ "role" : "user" ,
2997+ "content" : [{"toolResult" : {"toolUseId" : "tool_123" , "content" : [{"text" : "Zone X is active" }]}}],
2998+ },
2999+ {
3000+ "role" : "assistant" ,
3001+ "content" : [{"text" : "Zone X is currently active." }],
3002+ },
3003+ {"role" : "user" , "content" : [{"text" : "Now tell me about zone Y" }]},
3004+ ]
3005+
3006+ request = model ._format_request (messages )
3007+
3008+ # Both assistant messages should now have reasoningContent as first block
3009+ for msg in request ["messages" ]:
3010+ if msg ["role" ] == "assistant" :
3011+ first_block = msg ["content" ][0 ]
3012+ assert "reasoningContent" in first_block , (
3013+ f"Assistant message missing reasoningContent: { msg ['content' ]} "
3014+ )
3015+
3016+
3017+ def test_format_request_does_not_inject_reasoning_for_user_messages (bedrock_client , model_id ):
3018+ """User messages should never get reasoningContent injected."""
3019+ model = BedrockModel (
3020+ model_id = model_id ,
3021+ additional_request_fields = {"thinking" : {"type" : "enabled" , "budget_tokens" : 4096 }},
3022+ )
3023+
3024+ messages = [
3025+ {"role" : "user" , "content" : [{"text" : "Hello" }]},
3026+ ]
3027+
3028+ request = model ._format_request (messages )
3029+ user_msg = request ["messages" ][0 ]
3030+
3031+ assert not any ("reasoningContent" in cb for cb in user_msg ["content" ])
0 commit comments