diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py index 5be84a29b77..e916de34402 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py @@ -149,3 +149,76 @@ async def test_bedrock_converse_budget_tokens_preserved(): assert "thinking" in additional_fields, "thinking parameter should be in additionalModelRequestFields" assert thinking_config.get("type") == "enabled", "thinking.type should be 'enabled'" assert thinking_config.get("budget_tokens") == 1024, f"thinking.budget_tokens should be 1024, but got {thinking_config.get('budget_tokens')}" + + +def test_openai_model_with_thinking_converts_to_reasoning_effort(): + """ + Test that when using a non-Anthropic model (like OpenAI gpt-5.2) with thinking parameter, + the thinking is converted to reasoning_effort and NOT passed as thinking. + + This ensures we don't regress on issue #16052 where non-Anthropic models would fail + with UnsupportedParamsError when thinking was passed directly. + """ + from litellm.llms.anthropic.experimental_pass_through.messages.handler import ( + anthropic_messages_handler, + ) + + with patch("litellm.completion", return_value="test-response") as mock_completion: + try: + anthropic_messages_handler( + max_tokens=1024, + messages=[{"role": "user", "content": "What is 2+2?"}], + model="openai/gpt-5.2", + api_key="test-api-key", + thinking={ + "type": "enabled", + "budget_tokens": 1024 + }, + ) + except Exception as e: + print(f"Error: {e}") + + mock_completion.assert_called_once() + + call_kwargs = mock_completion.call_args.kwargs + + # Verify reasoning_effort is set (converted from thinking) + assert "reasoning_effort" in call_kwargs, "reasoning_effort should be passed to completion" + assert call_kwargs["reasoning_effort"] == "minimal", f"reasoning_effort should be 'minimal' for budget_tokens=1024, got {call_kwargs.get('reasoning_effort')}" + + # Verify thinking is NOT passed (non-Claude model) + assert "thinking" not in call_kwargs, "thinking should NOT be passed for non-Claude models" + + +class TestThinkingParameterTransformation: + """Core tests for thinking parameter transformation logic.""" + + def test_claude_model_preserves_thinking_with_budget_tokens(self): + """Test that Claude models get thinking parameter passed through with exact budget_tokens.""" + from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import ( + LiteLLMAnthropicMessagesAdapter, + ) + + thinking = {"type": "enabled", "budget_tokens": 5000} + result = LiteLLMAnthropicMessagesAdapter.translate_thinking_for_model( + thinking=thinking, + model="bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0", + ) + + assert result == {"thinking": thinking} + assert result["thinking"]["budget_tokens"] == 5000 + + def test_non_claude_model_converts_thinking_to_reasoning_effort(self): + """Test that non-Claude models convert thinking to reasoning_effort.""" + from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import ( + LiteLLMAnthropicMessagesAdapter, + ) + + thinking = {"type": "enabled", "budget_tokens": 1024} + result = LiteLLMAnthropicMessagesAdapter.translate_thinking_for_model( + thinking=thinking, + model="openai/gpt-5.2", + ) + + assert result == {"reasoning_effort": "minimal"} + assert "thinking" not in result