From b9a180cde9a64d07ab013cac8ef7457ed25abcf1 Mon Sep 17 00:00:00 2001 From: Harshith Uppula Date: Sun, 17 May 2026 00:51:54 -0700 Subject: [PATCH] fix(databricks): drop unsupported temperature param for Claude Opus 4.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #28113. Same root cause — Anthropic's Messages API rejects `temperature` for Claude Opus 4.7, accepting only `top_p` — applied to the Databricks adapter. Reported by @Kontinuation in the original issue thread (#26444), with a detailed reproduction by @dgomez04 in the same thread. Mirrors the #28113 pattern: - `DatabricksConfig.get_supported_openai_params` filters out `temperature` / `top_p` when the model id matches the `claude-opus-4-7` family - `map_openai_params` adds the same defense-in-depth guard so a raw kwargs leak is caught at the second layer too - `supports_temperature: false` / `supports_top_p: false` set on the new `databricks/databricks-claude-opus-4-7` entry in both `model_prices_and_context_window.json` and the backup file to satisfy `ci_cd/check_files_match.py` Helper is kept self-contained on `DatabricksConfig` (rather than reaching into the helper added by #28113 on `AnthropicConfig`) so this PR can land independently of #28113. Happy to deduplicate in a follow-up once both land. Tests: - new unit: temperature filtered out for databricks-claude-opus-4-7, the dated variant, and the vendor-prefixed form - new unit: unreleased dated 4.7 snapshots fall back to the `_is_claude_4_7_model` family check - regression: Sonnet 4.5 / Haiku 4.5 / Opus 4.5 / Opus 4.1 / 3.7-sonnet on Databricks still expose temperature - defense-in-depth: `map_openai_params(drop_params=False)` strips temperature for Opus 4.7 and preserves it for Opus 4.5 - end-to-end shape: top-level `litellm.get_supported_openai_params` for `databricks-claude-opus-4-7` no longer reports temperature (the exact contract `drop_params=True` keys off) Closes the Databricks gap from #26444. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../llms/databricks/chat/transformation.py | 67 +++++++- ...odel_prices_and_context_window_backup.json | 56 +++++++ model_prices_and_context_window.json | 22 +++ .../test_databricks_chat_transformation.py | 150 ++++++++++++++++++ 4 files changed, 294 insertions(+), 1 deletion(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 09c782a4755..bbf2c03a97a 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -197,8 +197,40 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): complete_url = f"{api_base}/chat/completions" return complete_url + @staticmethod + def _param_explicitly_unsupported(model: str, param: str) -> bool: + """Whether the upstream API rejects ``param`` for ``model``. + + Databricks model-serving routes ``databricks-claude-*`` to the + Anthropic Messages API, so the same parameter-support rules apply. + 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. + """ + from litellm.utils import _is_explicitly_disabled_factory + + try: + if _is_explicitly_disabled_factory( + model=model, + custom_llm_provider="databricks", + 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: Optional[str] = None) -> list: - return [ + params = [ "stream", "stop", "temperature", @@ -213,6 +245,25 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "thinking", ] + # Strip sampling params the upstream API rejects for this model. + # Databricks model-serving routes ``databricks-claude-*`` to the + # Anthropic Messages API, which returns 400 for ``temperature`` / + # ``top_p`` on Claude Opus 4.7. The model map encodes this via + # ``supports_temperature: false`` / ``supports_top_p: false``; + # ``_param_explicitly_unsupported`` reads those flags and falls + # back to a model-family check so unreleased dated 4.7 snapshots + # are covered too. (Mirrors the helper added in #28113 for the + # Anthropic / Bedrock paths — kept self-contained here so this PR + # can land independently.) + model_str = model or "" + params = [ + p + for p in params + if not DatabricksConfig._param_explicitly_unsupported(model_str, p) + ] + + return params + def convert_anthropic_tool_to_databricks_tool( self, tool: Optional[AllAnthropicToolsValues] ) -> Optional[DatabricksTool]: @@ -291,6 +342,20 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): replace_max_completion_tokens_with_max_tokens: bool = True, ) -> dict: is_thinking_enabled = self.is_thinking_enabled(non_default_params) + + # Defense in depth: strip sampling params the upstream API rejects + # for this model before delegating to the OpenAI-like mapper. The + # supported-params filter in ``get_supported_openai_params`` already + # makes ``drop_params=True`` work via the standard contract; this + # second guard catches raw kwargs that bypass that path (e.g. a + # caller passing ``temperature`` directly with ``drop_params=False`` + # on Opus 4.7, which would otherwise propagate to a guaranteed 400). + non_default_params = { + k: v + for k, v in non_default_params.items() + if not DatabricksConfig._param_explicitly_unsupported(model, k) + } + mapped_params = super().map_openai_params( non_default_params, optional_params, model, drop_params ) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index fb5bfa6cf4e..6395c17ab8a 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -1448,6 +1448,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, @@ -9602,6 +9631,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 +9825,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 +9859,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 +9893,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, @@ -9895,6 +9928,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, @@ -11191,6 +11225,28 @@ "supports_minimal_reasoning_effort": true, "supports_tool_choice": true }, + "databricks/databricks-claude-opus-4-7": { + "input_cost_per_token": 5.00003e-06, + "input_dbu_cost_per_token": 7.1429e-05, + "litellm_provider": "databricks", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "metadata": { + "notes": "Input/output cost per token is dbu cost * $0.070. Number provided for reference, '*_dbu_cost_per_token' used in actual calculation. Pricing mirrors databricks-claude-opus-4-5 as a placeholder pending the Databricks pay-per-token listing for Opus 4.7." + }, + "mode": "chat", + "output_cost_per_token": 2.5000010000000002e-05, + "output_dbu_cost_per_token": 0.000357143, + "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_minimal_reasoning_effort": true, + "supports_temperature": false, + "supports_top_p": false, + "supports_tool_choice": true + }, "databricks/databricks-claude-sonnet-4": { "input_cost_per_token": 2.9999900000000002e-06, "input_dbu_cost_per_token": 4.2857e-05, diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 94f0f1e78d3..6395c17ab8a 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -11225,6 +11225,28 @@ "supports_minimal_reasoning_effort": true, "supports_tool_choice": true }, + "databricks/databricks-claude-opus-4-7": { + "input_cost_per_token": 5.00003e-06, + "input_dbu_cost_per_token": 7.1429e-05, + "litellm_provider": "databricks", + "max_input_tokens": 200000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "metadata": { + "notes": "Input/output cost per token is dbu cost * $0.070. Number provided for reference, '*_dbu_cost_per_token' used in actual calculation. Pricing mirrors databricks-claude-opus-4-5 as a placeholder pending the Databricks pay-per-token listing for Opus 4.7." + }, + "mode": "chat", + "output_cost_per_token": 2.5000010000000002e-05, + "output_dbu_cost_per_token": 0.000357143, + "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_reasoning": true, + "supports_minimal_reasoning_effort": true, + "supports_temperature": false, + "supports_top_p": false, + "supports_tool_choice": true + }, "databricks/databricks-claude-sonnet-4": { "input_cost_per_token": 2.9999900000000002e-06, "input_dbu_cost_per_token": 4.2857e-05, diff --git a/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py b/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py index 79e354d8621..518462842e4 100644 --- a/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py +++ b/tests/test_litellm/llms/databricks/chat/test_databricks_chat_transformation.py @@ -260,3 +260,153 @@ def test_transform_messages_sanitizes_empty_content(): ) assert "content" not in result[0] assert result[1]["content"] == "Hi" + + +# --- Opus 4.7 ``temperature`` / ``top_p`` deprecation (issue #26444) --- +# +# Databricks model-serving routes ``databricks-claude-*`` requests to the +# Anthropic Messages API, which returns 400 +# (``Model us.anthropic.claude-opus-4-7 does not support the temperature +# parameter``) when ``temperature`` or ``top_p`` is sent to Claude Opus 4.7. +# ``DatabricksConfig.get_supported_openai_params`` filters those params via +# the locally-defined ``_param_explicitly_unsupported`` helper, which reads +# ``supports_temperature: false`` / ``supports_top_p: false`` off the model +# registry and falls back to ``AnthropicConfig._is_claude_4_7_model`` for +# unreleased dated snapshots. Companion to the Anthropic / Bedrock fix in +# PR #28113. + + +@pytest.mark.parametrize( + "model", + [ + "databricks-claude-opus-4-7", + "databricks-claude-opus-4-7-20260416", + "databricks/databricks-claude-opus-4-7", + ], +) +def test_opus_4_7_drops_temperature_and_top_p_from_supported_params( + monkeypatch, model +): + """Opus 4.7 on Databricks must not advertise ``temperature`` / ``top_p`` so + ``drop_params=True`` strips them before the request leaves litellm.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + import importlib + + import litellm as _litellm + + importlib.reload(_litellm) + + config = DatabricksConfig() + params = config.get_supported_openai_params(model=model) + + assert "temperature" not in params, ( + f"temperature should be filtered from Databricks Opus 4.7 supported params; got {params!r}" + ) + assert "top_p" not in params, ( + f"top_p should be filtered from Databricks Opus 4.7 supported params; got {params!r}" + ) + + +def test_opus_4_7_unknown_dated_variant_falls_back_to_family_check(monkeypatch): + """Dated Databricks 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 = DatabricksConfig() + + params = config.get_supported_openai_params( + model="databricks-claude-opus-4-7-20991231" + ) + + assert "temperature" not in params + assert "top_p" not in params + + +@pytest.mark.parametrize( + "model", + [ + "databricks-claude-sonnet-4-5", + "databricks-claude-haiku-4-5", + "databricks-claude-opus-4-5", + "databricks-claude-opus-4-1", + "databricks-claude-3-7-sonnet", + ], +) +def test_non_opus_4_7_databricks_models_still_support_temperature_and_top_p( + monkeypatch, model +): + """Regression guard: only Opus 4.7 deprecated temperature on Anthropic's + Messages API. Every other Claude served via Databricks (Sonnet 4.5, + Haiku 4.5, Opus 4.5/4.1, 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 = DatabricksConfig() + 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 + Databricks 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 = DatabricksConfig() + + mapped = config.map_openai_params( + non_default_params={"temperature": 0.3, "top_p": 0.9, "max_tokens": 16}, + optional_params={}, + model="databricks-claude-opus-4-7", + drop_params=False, + ) + + assert "temperature" not in mapped, ( + f"temperature leaked through Databricks Opus 4.7 mapping: {mapped!r}" + ) + assert "top_p" not in mapped, ( + f"top_p leaked through Databricks Opus 4.7 mapping: {mapped!r}" + ) + # ``max_tokens`` is unrelated to the deprecation and must still be forwarded. + assert mapped.get("max_tokens") == 16 + + +def test_opus_4_5_map_openai_params_preserves_temperature_and_top_p(monkeypatch): + """Regression guard on the mapping site: Databricks Opus 4.5 still receives + both sampling params verbatim.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + config = DatabricksConfig() + + mapped = config.map_openai_params( + non_default_params={"temperature": 0.3, "top_p": 0.9}, + optional_params={}, + model="databricks-claude-opus-4-5", + drop_params=False, + ) + + assert mapped["temperature"] == 0.3 + assert mapped["top_p"] == 0.9 + + +def test_opus_4_7_drop_params_true_strips_temperature_end_to_end(monkeypatch): + """End-to-end-shape check that mirrors @dgomez04's reproduction in issue + #26444: ``get_supported_openai_params`` says ``temperature`` is NOT + supported for ``databricks-claude-opus-4-7``, which is the exact contract + ``litellm.utils.drop_params=True`` keys off to strip the param before the + Databricks call. Without this PR the same lookup returned True for + ``temperature``, so ``drop_params`` was a no-op and the request hit a 400.""" + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + import litellm + + params = litellm.get_supported_openai_params( + model="databricks-claude-opus-4-7", custom_llm_provider="databricks" + ) + assert "temperature" not in params, ( + "regression: top-level litellm.get_supported_openai_params still " + "claims temperature is supported for databricks-claude-opus-4-7; " + "drop_params=True will be a no-op and Anthropic will return 400." + ) + assert "top_p" not in params