From f2d1c0eb22ed8f7e0f4ddf40bdb878ef192bad98 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 6 Feb 2026 09:24:43 +0530 Subject: [PATCH] Add adaptive thinking support for anthropic opus 4.6 --- litellm/llms/anthropic/chat/transformation.py | 19 +++-- .../test_anthropic_chat_transformation.py | 69 +++++++++++++++++++ 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 82eccee596d..a8254833d36 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -170,9 +170,9 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): tool_call["caller"] = cast(Dict[str, Any], anthropic_tool_content["caller"]) # type: ignore[typeddict-item] return tool_call - def _is_claude_opus_4_5(self, model: str) -> bool: + def _is_claude_opus_4_6(self, model: str) -> bool: """Check if the model is Claude Opus 4.5.""" - return "opus-4-5" in model.lower() or "opus_4_5" in model.lower() + return "opus-4-6" in model.lower() or "opus_4_6" in model.lower() def get_supported_openai_params(self, model: str): params = [ @@ -851,14 +851,13 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if param == "thinking": optional_params["thinking"] = value elif param == "reasoning_effort" and isinstance(value, str): - # For Claude Opus 4.5, map reasoning_effort to output_config - if self._is_claude_opus_4_5(model): - optional_params["output_config"] = {"effort": value} - - # For other models, map to thinking parameter - optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( - value - ) + # For Claude Opus 4.6, map reasoning_effort to new adaptive thinking type + if self._is_claude_opus_4_6(model): + optional_params["thinking"] = {"type": "adaptive"} + else: + optional_params["thinking"] = AnthropicConfig._map_reasoning_effort( + value + ) elif param == "web_search_options" and isinstance(value, dict): hosted_web_search_tool = self.map_web_search_tool( cast(OpenAIWebSearchOptions, value) diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 7e96c4634fd..afe4fb6273b 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -1881,6 +1881,75 @@ def test_calculate_usage_completion_tokens_details_with_reasoning(): assert usage.completion_tokens == 500 +# ============ Reasoning Effort Tests ============ + + +def test_reasoning_effort_maps_to_adaptive_thinking_for_opus_4_6(): + """ + Test that reasoning_effort maps to adaptive thinking type for Claude Opus 4.6. + + For Claude Opus 4.6, reasoning_effort should map to {"type": "adaptive"} + regardless of the effort level specified. + """ + config = AnthropicConfig() + + # Test with different reasoning_effort values - all should map to adaptive + for effort in ["low", "medium", "high", "minimal"]: + non_default_params = {"reasoning_effort": effort} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="claude-opus-4-6-20250514", + drop_params=False + ) + + # Should map to adaptive thinking type + assert "thinking" in result + assert result["thinking"]["type"] == "adaptive" + # Should not have budget_tokens for adaptive type + assert "budget_tokens" not in result["thinking"] + # reasoning_effort should not be in the result (it's transformed to thinking) + assert "reasoning_effort" not in result + + +def test_reasoning_effort_maps_to_budget_thinking_for_non_opus_4_6(): + """ + Test that reasoning_effort maps to budget-based thinking config for non-Opus 4.6 models. + + For models other than Claude Opus 4.6, reasoning_effort should map to + thinking config with budget_tokens based on the effort level. + """ + config = AnthropicConfig() + + # Test with Claude Sonnet 4.5 (non-Opus 4.6 model) + test_cases = [ + ("low", 1024), # DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET + ("medium", 2048), # DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET + ("high", 4096), # DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET + ("minimal", 128), # DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET + ] + + for effort, expected_budget in test_cases: + non_default_params = {"reasoning_effort": effort} + optional_params = {} + + result = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="claude-sonnet-4-5-20250929", + drop_params=False + ) + + # Should map to enabled thinking type with budget_tokens + assert "thinking" in result + assert result["thinking"]["type"] == "enabled" + assert result["thinking"]["budget_tokens"] == expected_budget + # reasoning_effort should not be in the result (it's transformed to thinking) + assert "reasoning_effort" not in result + + def test_code_execution_tool_results_extraction(): """ Test that code execution tool results (bash_code_execution_tool_result,