diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 1f5b76f3d0a..5bdc2f0e47f 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1241,6 +1241,35 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): return thinking return AnthropicThinkingParam(type=thinking.get("type", "enabled"), budget_tokens=max_tokens - 1) + @staticmethod + def _translate_legacy_thinking_for_adaptive_model( + model: str, optional_params: Dict, custom_llm_provider: str + ) -> None: + if not AnthropicConfig._is_adaptive_thinking_model(model, custom_llm_provider): + return + thinking = optional_params.get("thinking") + if not isinstance(thinking, dict) or thinking.get("type") != "enabled": + return + + budget = int(thinking.get("budget_tokens") or 0) + if budget >= DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET and AnthropicConfig._supports_effort_level( + model, "xhigh", custom_llm_provider + ): + effort = "xhigh" + elif budget >= DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET: + effort = "high" + elif budget >= DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET: + effort = "medium" + else: + effort = "low" + + optional_params["thinking"] = {"type": "adaptive"} + output_config = optional_params.get("output_config") + if not isinstance(output_config, dict): + output_config = {} + output_config.setdefault("effort", effort) + optional_params["output_config"] = output_config + def _extract_json_schema_from_response_format(self, value: Optional[dict]) -> Optional[dict]: if value is None: return None @@ -1484,6 +1513,12 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ): optional_params["metadata"] = {"user_id": value} elif param == "thinking": + optional_params["thinking"] = value + AnthropicConfig._translate_legacy_thinking_for_adaptive_model( + model=model, + optional_params=optional_params, + custom_llm_provider=self._resolved_provider, + ) if ( isinstance(value, dict) and value.get("type") == "adaptive" @@ -1514,8 +1549,6 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): model, ) optional_params.pop("thinking", None) - else: - optional_params["thinking"] = value elif param == "reasoning_effort": # Accept both string ("low") and dict ({"effort": "low", # "summary": "concise"}). The Responses->Chat parser keeps the diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py index cb220764965..baa3b86f0cd 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/transformation.py @@ -2,13 +2,9 @@ from typing import Any, AsyncIterator, Dict, List, Optional, Tuple import httpx -from litellm.constants import ( - DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, - DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, - DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET, -) from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from litellm.litellm_core_utils.litellm_logging import verbose_logger +from litellm.llms.anthropic.chat.transformation import AnthropicConfig from litellm.llms.base_llm.anthropic_messages.transformation import ( BaseAnthropicMessagesConfig, ) @@ -239,40 +235,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): existing_output_config.setdefault("effort", mapped_effort) optional_params["output_config"] = existing_output_config - @staticmethod - def _translate_legacy_thinking_for_adaptive_model( - model: str, optional_params: Dict, custom_llm_provider: str - ) -> None: - """Translate legacy ``thinking.type=enabled`` to adaptive for 4.6/4.7. - Caller-provided ``output_config.effort`` is never overridden. - """ - from litellm.llms.anthropic.chat.transformation import AnthropicConfig - - if not AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider): - return - thinking = optional_params.get("thinking") - if not isinstance(thinking, dict) or thinking.get("type") != "enabled": - return - - budget = int(thinking.get("budget_tokens") or 0) - if budget >= DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET and ( - AnthropicConfig._supports_effort_level(model, "xhigh", custom_llm_provider) - ): - effort = "xhigh" - elif budget >= DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET: - effort = "high" - elif budget >= DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET: - effort = "medium" - else: - effort = "low" - - optional_params["thinking"] = {"type": "adaptive"} - existing_output_config = optional_params.get("output_config") - if not isinstance(existing_output_config, dict): - existing_output_config = {} - existing_output_config.setdefault("effort", effort) - optional_params["output_config"] = existing_output_config - @staticmethod def _translate_adaptive_effort_for_non_adaptive_model( model: str, optional_params: Dict, max_tokens: Optional[int], custom_llm_provider: str @@ -316,7 +278,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): subclasses to handle. """ from litellm.exceptions import BadRequestError as _BadRequestError - from litellm.llms.anthropic.chat.transformation import AnthropicConfig if AnthropicConfig._is_adaptive_thinking_model(model, custom_llm_provider): return @@ -397,7 +358,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): custom_llm_provider=self._resolved_provider, ) - self._translate_legacy_thinking_for_adaptive_model( + AnthropicConfig._translate_legacy_thinking_for_adaptive_model( model=model, optional_params=anthropic_messages_optional_request_params, custom_llm_provider=self._resolved_provider, @@ -421,8 +382,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): # Transform context_management from OpenAI format to Anthropic format if needed context_management_param = anthropic_messages_optional_request_params.get("context_management") if context_management_param is not None: - from litellm.llms.anthropic.chat.transformation import AnthropicConfig - transformed_context_management = AnthropicConfig.map_openai_context_management_to_anthropic( context_management_param ) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index c38b3593465..76f37bb9e37 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -900,6 +900,12 @@ class AmazonConverseConfig(BaseConfig): "tool_choice": {"disable_parallel_tool_use": disable_parallel} } if param == "thinking": + optional_params["thinking"] = value + AnthropicConfig._translate_legacy_thinking_for_adaptive_model( + model=model, + optional_params=optional_params, + custom_llm_provider="bedrock", + ) if ( isinstance(value, dict) and value.get("type") == "adaptive" @@ -920,8 +926,7 @@ class AmazonConverseConfig(BaseConfig): optional_params["thinking"] = capped else: litellm.verbose_logger.warning(DROP_UNSUPPORTED_ADAPTIVE_THINKING_WARNING, model) - else: - optional_params["thinking"] = value + optional_params.pop("thinking", None) elif param == "reasoning_effort" and isinstance(value, str): self._handle_reasoning_effort_parameter( model=model, reasoning_effort=value, optional_params=optional_params 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 43ef7fcd971..62ce8939f4b 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 @@ -22,6 +22,12 @@ from litellm.llms.anthropic.chat.transformation import AnthropicConfig from litellm.llms.anthropic.experimental_pass_through.messages.transformation import ( AnthropicMessagesConfig, ) +from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, +) +from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation import ( + VertexAIAnthropicConfig, +) from litellm.types.llms.anthropic import ANTHROPIC_BETA_HEADER_VALUES from litellm.types.utils import ServerToolUse @@ -2516,6 +2522,35 @@ def test_raw_adaptive_thinking_untouched_for_46_plus_model(): assert result["thinking"] == {"type": "adaptive"} +@pytest.mark.parametrize( + "config,model", + [ + (AnthropicConfig(), "claude-opus-4-8"), + ( + AmazonAnthropicClaudeConfig(), + "global.anthropic.claude-opus-4-8", + ), + (VertexAIAnthropicConfig(), "claude-opus-4-8"), + ], + ids=["anthropic", "bedrock-invoke", "vertex"], +) +def test_legacy_thinking_maps_to_adaptive_thinking_for_chat_routes(config, model): + result = config.map_openai_params( + non_default_params={ + "thinking": { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + } + }, + optional_params={}, + model=model, + drop_params=False, + ) + + assert result["thinking"] == {"type": "adaptive"} + assert result["output_config"] == {"effort": "high"} + + @pytest.fixture def local_model_cost_map(monkeypatch): original_model_cost = litellm.model_cost diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index fc12ead36a1..45858ee96ec 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -341,6 +341,25 @@ def test_reasoning_effort_sets_output_config_for_adaptive_models_converse( assert optional_params["output_config"] == {"effort": expected_effort} +def test_legacy_thinking_maps_to_adaptive_thinking_for_converse(): + config = AmazonConverseConfig() + + optional_params = config.map_openai_params( + non_default_params={ + "thinking": { + "type": "enabled", + "budget_tokens": 4096, + } + }, + optional_params={"output_config": {"effort": "low"}}, + model="bedrock/converse/global.anthropic.claude-opus-4-8", + drop_params=False, + ) + + assert optional_params["thinking"] == {"type": "adaptive"} + assert optional_params["output_config"] == {"effort": "low"} + + @pytest.mark.parametrize( "model", [