mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-25 01:02:15 +00:00
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:
parent
a72414a061
commit
b9a180cde9
4 changed files with 294 additions and 1 deletions
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue