diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 1ce80207552..28caa55ca40 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -80,6 +80,7 @@ from litellm.types.utils import ( from litellm.utils import ( ModelResponse, Usage, + _is_explicitly_disabled_factory, _supports_factory, add_dummy_tool, any_assistant_message_has_thinking_blocks, @@ -431,6 +432,34 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): llm_provider=llm_provider, ) + @staticmethod + def _param_explicitly_unsupported(model: str, param: str) -> bool: + """Whether the upstream API rejects ``param`` for ``model``. + + Primary signal is the model map: ``supports_{param}: false`` flips + this to True via the same ``_is_explicitly_disabled_factory`` + chain other capability gates use, so a missing flag is treated + as "supported". + + Falls back to a model-family check for the Opus 4.7 sampling + deprecation (``temperature`` / ``top_p`` / ``top_k`` return 400 + on the Anthropic Messages API per the Opus 4.7 migration guide). + This keeps unreleased dated 4.7 snapshots and any + provider-prefixed alias that isn't yet in the JSON covered. + """ + try: + if _is_explicitly_disabled_factory( + model=model, + custom_llm_provider=None, + key=f"supports_{param}", + ): + return True + except Exception: + pass + if param in ("temperature", "top_p", "top_k"): + return AnthropicConfig._is_claude_4_7_model(model) + return False + def get_supported_openai_params(self, model: str): params = [ "stream", @@ -463,6 +492,16 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): params.append("thinking") params.append("reasoning_effort") + # Strip sampling params the upstream API rejects for this model. + # Anthropic returns a 400 for ``temperature`` / ``top_p`` / ``top_k`` + # on Claude Opus 4.7; the model map encodes this via + # ``supports_temperature: false`` / ``supports_top_p: false``. + params = [ + p + for p in params + if not AnthropicConfig._param_explicitly_unsupported(model, p) + ] + return params @staticmethod @@ -1452,8 +1491,15 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if _value is not None: optional_params["stop_sequences"] = _value elif param == "temperature": + if AnthropicConfig._param_explicitly_unsupported(model, "temperature"): + # Anthropic Messages API rejects ``temperature`` for + # Opus 4.7; silently drop rather than forwarding the + # value to a guaranteed 400. + continue optional_params["temperature"] = value elif param == "top_p": + if AnthropicConfig._param_explicitly_unsupported(model, "top_p"): + continue optional_params["top_p"] = value elif param == "response_format" and isinstance(value, dict): if any( diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index efc890d9ee2..5731485cb8d 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -573,6 +573,18 @@ class AmazonConverseConfig(BaseConfig): ): supported_params.append("thinking") supported_params.append("reasoning_effort") + + # Strip sampling params the upstream API rejects for this model. + # Anthropic's Messages API (surfaced via Bedrock Converse for + # claude-opus-4-7) returns 400 for ``temperature`` / ``top_p``; + # the model map encodes this via ``supports_temperature: false`` + # / ``supports_top_p: false`` so ``drop_params=True`` strips + # them before the request leaves litellm. + supported_params = [ + p + for p in supported_params + if not AnthropicConfig._param_explicitly_unsupported(model, p) + ] return supported_params def map_tool_choice_values( diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index fb5bfa6cf4e..0d0ddb3e5fb 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1155,6 +1155,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1201,6 +1203,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1232,6 +1236,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1262,6 +1268,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1292,6 +1300,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1448,6 +1458,35 @@ "supports_native_structured_output": true, "supports_minimal_reasoning_effort": true }, + "jp.anthropic.claude-sonnet-4-6": { + "cache_creation_input_token_cost": 4.125e-06, + "cache_read_input_token_cost": 3.3e-07, + "input_cost_per_token": 3.3e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.65e-05, + "search_context_cost_per_query": { + "search_context_size_high": 0.01, + "search_context_size_low": 0.01, + "search_context_size_medium": 0.01 + }, + "supports_assistant_prefill": true, + "supports_computer_use": true, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_max_reasoning_effort": true, + "supports_tool_choice": true, + "supports_vision": true, + "tool_use_system_prompt_tokens": 346, + "supports_native_structured_output": true, + "supports_minimal_reasoning_effort": true + }, "anthropic.claude-sonnet-4-20250514-v1:0": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, @@ -1993,6 +2032,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -9602,6 +9643,7 @@ "search_context_size_low": 0.01, "search_context_size_medium": 0.01 }, + "supports_adaptive_thinking": true, "supports_assistant_prefill": true, "supports_computer_use": true, "supports_function_calling": true, @@ -9795,6 +9837,7 @@ "search_context_size_low": 0.01, "search_context_size_medium": 0.01 }, + "supports_adaptive_thinking": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -9828,6 +9871,7 @@ "search_context_size_low": 0.01, "search_context_size_medium": 0.01 }, + "supports_adaptive_thinking": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -9861,6 +9905,7 @@ "search_context_size_low": 0.01, "search_context_size_medium": 0.01 }, + "supports_adaptive_thinking": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -9868,6 +9913,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -9895,6 +9942,7 @@ "search_context_size_low": 0.01, "search_context_size_medium": 0.01 }, + "supports_adaptive_thinking": true, "supports_assistant_prefill": false, "supports_computer_use": true, "supports_function_calling": true, @@ -9902,6 +9950,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -32957,6 +33007,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -32986,6 +33038,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 94f0f1e78d3..0d0ddb3e5fb 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -1155,6 +1155,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1201,6 +1203,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1232,6 +1236,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1262,6 +1268,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -1292,6 +1300,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -2022,6 +2032,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -9901,6 +9913,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -9936,6 +9950,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -32991,6 +33007,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, @@ -33020,6 +33038,8 @@ "supports_prompt_caching": true, "supports_reasoning": true, "supports_response_schema": true, + "supports_temperature": false, + "supports_top_p": false, "supports_tool_choice": true, "supports_vision": true, "supports_xhigh_reasoning_effort": true, 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 a19752dc648..19684ddf304 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 @@ -2350,6 +2350,154 @@ def test_get_supported_params_includes_reasoning_for_sonnet_4_6_dotted_alias(): assert "reasoning_effort" in params +# --- Opus 4.7 ``temperature`` / ``top_p`` deprecation (issue #26444) --- +# +# Anthropic's Messages API returns 400 (``temperature is deprecated for this +# model``) when ``temperature`` or ``top_p`` is sent to Claude Opus 4.7 with a +# non-default value. The model registry encodes this as +# ``supports_temperature: false`` / ``supports_top_p: false`` on every Opus 4.7 +# entry (Anthropic, Bedrock Converse, Vertex, Azure AI), and +# ``AnthropicConfig`` reads those flags via ``_is_explicitly_disabled_factory`` +# to filter the supported-params list and drop the values inside +# ``map_openai_params``. A static fallback on ``_is_claude_4_7_model`` covers +# unreleased dated snapshots that aren't in the registry yet. + + +@pytest.mark.parametrize( + "model", + [ + "claude-opus-4-7", + "claude-opus-4-7-20260416", + "anthropic/claude-opus-4-7", + ], +) +def test_opus_4_7_drops_temperature_and_top_p_from_supported_params( + monkeypatch, model +): + """Opus 4.7 must not advertise ``temperature`` / ``top_p`` so ``drop_params=True`` strips them.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + # Force a clean reload so the local JSON is honoured for the assertion. + import importlib + + import litellm as _litellm + + importlib.reload(_litellm) + + config = AnthropicConfig() + params = config.get_supported_openai_params(model=model) + + assert "temperature" not in params, ( + f"temperature should be filtered from Opus 4.7 supported params; got {params!r}" + ) + assert "top_p" not in params, ( + f"top_p should be filtered from Opus 4.7 supported params; got {params!r}" + ) + + +def test_opus_4_7_unknown_dated_variant_falls_back_to_family_check(monkeypatch): + """Dated Opus 4.7 snapshots not yet in the model registry are still covered + by the ``_is_claude_4_7_model`` family fallback.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AnthropicConfig() + + # Plausible future snapshot id not present in model_prices_and_context_window.json. + params = config.get_supported_openai_params(model="claude-opus-4-7-20991231") + + assert "temperature" not in params + assert "top_p" not in params + + +@pytest.mark.parametrize( + "model", + [ + "claude-sonnet-4-6", + "claude-sonnet-4-6-20260219", + "claude-haiku-4-5", + "claude-3-5-sonnet-20241022", + "claude-3-7-sonnet-20250219", + "claude-opus-4-6", + ], +) +def test_non_opus_4_7_models_still_support_temperature_and_top_p( + monkeypatch, model +): + """Regression guard: only Opus 4.7 deprecated temperature; older models + (including same-generation 4.6 and reasoning-family 3.7-sonnet) must keep + advertising ``temperature`` and ``top_p`` so existing call sites keep working.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AnthropicConfig() + params = config.get_supported_openai_params(model=model) + + assert "temperature" in params, ( + f"temperature should remain supported for {model}; got {params!r}" + ) + assert "top_p" in params, ( + f"top_p should remain supported for {model}; got {params!r}" + ) + + +def test_opus_4_7_map_openai_params_drops_temperature_and_top_p(monkeypatch): + """``map_openai_params`` must not leak ``temperature`` / ``top_p`` into the + Anthropic request body for Opus 4.7, even when callers pass them + explicitly without ``drop_params=True`` (defense in depth).""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AnthropicConfig() + + optional_params = config.map_openai_params( + non_default_params={"temperature": 0.3, "top_p": 0.9}, + optional_params={}, + model="claude-opus-4-7", + drop_params=False, + ) + + assert "temperature" not in optional_params + assert "top_p" not in optional_params + + +def test_opus_4_6_map_openai_params_preserves_temperature_and_top_p(monkeypatch): + """Regression guard on the mapping site: Opus 4.6 still receives both + sampling params verbatim.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AnthropicConfig() + + optional_params = config.map_openai_params( + non_default_params={"temperature": 0.3, "top_p": 0.9}, + optional_params={}, + model="claude-opus-4-6", + drop_params=False, + ) + + assert optional_params["temperature"] == 0.3 + assert optional_params["top_p"] == 0.9 + + +def test_opus_4_7_drop_params_true_strips_temperature_before_transform(monkeypatch): + """End-to-end-shape check that mirrors issue #26444's reproduction: a + ``drop_params=True`` call with ``temperature`` set must produce a + transformed Anthropic request body that does not contain ``temperature``.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AnthropicConfig() + messages = [{"role": "user", "content": "hi"}] + + optional_params = config.map_openai_params( + non_default_params={"temperature": 0.1}, + optional_params={}, + model="claude-opus-4-7", + drop_params=True, + ) + body = config.transform_request( + model="claude-opus-4-7", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={}, + ) + + assert "temperature" not in body, ( + f"temperature leaked into the Anthropic request body: {body!r}" + ) + + def test_sonnet_4_6_reasoning_effort_to_transform_request_payload(): """ Sonnet 4.6 should convert reasoning_effort to adaptive thinking in final request payload. 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 5f2ed3dc00f..475b0252386 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -4578,3 +4578,69 @@ def test_transform_response_does_not_leak_body_on_parse_failure(): msg = str(exc_info.value) assert "secret content" not in msg assert "Error converting to valid response block" in msg + + +# --- Opus 4.7 ``temperature`` / ``top_p`` deprecation (issue #26444) --- +# +# Bedrock Converse surfaces the Anthropic Messages API for ``anthropic.claude-*`` +# models, so the same 400 (``temperature is deprecated for this model``) hits +# Opus 4.7 on Bedrock. ``AmazonConverseConfig.get_supported_openai_params`` +# reads the ``supports_temperature`` / ``supports_top_p`` flags off the model +# registry via ``AnthropicConfig._param_explicitly_unsupported`` so +# ``drop_params=True`` strips them before the call leaves litellm. + + +@pytest.mark.parametrize( + "model", + [ + "bedrock/converse/anthropic.claude-opus-4-7", + "bedrock/converse/us.anthropic.claude-opus-4-7", + "bedrock/converse/eu.anthropic.claude-opus-4-7", + "bedrock/converse/global.anthropic.claude-opus-4-7", + ], +) +def test_bedrock_converse_opus_4_7_drops_temperature_and_top_p(monkeypatch, model): + """Bedrock-routed Opus 4.7 must not advertise ``temperature`` / ``top_p``.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + import importlib + + import litellm as _litellm + + importlib.reload(_litellm) + + config = AmazonConverseConfig() + params = config.get_supported_openai_params(model=model) + + assert "temperature" not in params, ( + f"temperature should be filtered for {model}; got {params!r}" + ) + assert "top_p" not in params, ( + f"top_p should be filtered for {model}; got {params!r}" + ) + + +@pytest.mark.parametrize( + "model", + [ + "bedrock/converse/anthropic.claude-sonnet-4-6", + "bedrock/converse/us.anthropic.claude-opus-4-6", + "bedrock/converse/anthropic.claude-haiku-4-5-20251001-v1:0", + "bedrock/converse/anthropic.claude-3-5-sonnet-20241022-v2:0", + ], +) +def test_bedrock_converse_non_opus_4_7_models_keep_temperature_and_top_p( + monkeypatch, model +): + """Regression guard: same-generation Sonnet 4.6 / Haiku 4.5 / older 3.5 + models on Bedrock Converse must continue to advertise ``temperature`` + and ``top_p``.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = AmazonConverseConfig() + params = config.get_supported_openai_params(model=model) + + assert "temperature" in params, ( + f"temperature should remain supported for {model}; got {params!r}" + ) + assert "top_p" in params, ( + f"top_p should remain supported for {model}; got {params!r}" + )