diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 098a074892f..d890b78624f 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -1133,9 +1133,28 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if AnthropicConfig._is_claude_4_6_model( model ) or AnthropicConfig._is_claude_4_7_model(model): + # ``_map_reasoning_effort`` returns ``type=adaptive`` + # for any string on adaptive models without checking + # the value, so reject unmapped efforts here (matching + # the /v1/messages path) instead of relying on the + # downstream ``_apply_output_config`` check. Co-locating + # validation with the mapping prevents garbage from + # leaking into ``optional_params`` if ``map_openai_params`` + # is ever called without a subsequent ``transform_request``. mapped_effort = AnthropicConfig.REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get( - value, value + value ) + if mapped_effort is None: + raise litellm.exceptions.BadRequestError( + message=( + f"Invalid reasoning_effort: {value!r}. " + f"Must be one of: 'minimal', 'low', " + f"'medium', 'high', 'xhigh', 'max', 'none'" + ), + model=model, + llm_provider=self.custom_llm_provider + or "anthropic", + ) optional_params["output_config"] = {"effort": mapped_effort} elif param == "web_search_options" and isinstance(value, dict): hosted_web_search_tool = self.map_web_search_tool( diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 7f42743cfc4..6f9e8de62e5 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -485,11 +485,28 @@ class AmazonConverseConfig(BaseConfig): if AnthropicConfig._is_claude_4_6_model( model ) or AnthropicConfig._is_claude_4_7_model(model): + # Use ``.get()`` without a fallback so unmapped efforts + # (e.g. ``"disabled"``) surface as a clean 400 here + # rather than leaking the raw garbage string through to + # ``_validate_anthropic_adaptive_effort`` (which does + # catch it, but only because validation happens to run). + # Matches the /v1/messages pattern where validation is + # co-located with the mapping. mapped_effort = ( AnthropicConfig.REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get( - reasoning_effort, reasoning_effort + reasoning_effort ) ) + if mapped_effort is None: + raise litellm.exceptions.BadRequestError( + message=( + f"Invalid reasoning_effort: {reasoning_effort!r}. " + f"Must be one of: 'minimal', 'low', 'medium', " + f"'high', 'xhigh', 'max', 'none'" + ), + model=model, + llm_provider="bedrock_converse", + ) self._validate_anthropic_adaptive_effort( model=model, effort=mapped_effort )