fix(databricks): drop unsupported temperature param for Claude Opus 4.7

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) <noreply@anthropic.com>
This commit is contained in:
Harshith Uppula 2026-05-17 00:51:54 -07:00
parent a72414a061
commit b9a180cde9
4 changed files with 294 additions and 1 deletions

View file

@ -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
)

View file

@ -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,

View file

@ -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,

View file

@ -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