From 443bee300294796081a201bc1da57833ed77a64b Mon Sep 17 00:00:00 2001 From: cohml <62400541+cohml@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:04:30 -0400 Subject: [PATCH 01/11] fix(databricks): translate reasoning_effort to thinking for Gemini 2.5 models. --- .../llms/databricks/chat/transformation.py | 25 +++- .../test_databricks_chat_transformation.py | 116 ++++++++++++++++++ 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index ba8c312ea51..a79dfe8a59b 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -264,6 +264,22 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "thinking", ] + @staticmethod + def _databricks_model_uses_anthropic_thinking_param(model: str) -> bool: + """ + Per Databricks docs, Claude and Gemini 2.5 endpoints accept the + Anthropic-style `thinking={"type":"enabled","budget_tokens":N}` payload + and do NOT accept OpenAI's top-level `reasoning_effort`. Gemini 3+ and + GPT-5/GPT-OSS accept `reasoning_effort` natively and need no + translation. + """ + model_lower = model.lower() + if "claude" in model_lower: + return True + if "gemini-2" in model_lower: + return True + return False + def convert_anthropic_tool_to_databricks_tool( self, tool: Optional[AllAnthropicToolsValues] ) -> Optional[DatabricksTool]: @@ -367,19 +383,24 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "response_format", None ) # unsupported for claude models - if json_schema -> convert to tool call - if "reasoning_effort" in non_default_params and "claude" in model: + if ( + "reasoning_effort" in non_default_params + and self._databricks_model_uses_anthropic_thinking_param(model) + ): reasoning_effort_value = non_default_params.get("reasoning_effort") mapped_thinking = AnthropicConfig._map_reasoning_effort( reasoning_effort=reasoning_effort_value, model=model, llm_provider="databricks", ) + is_claude = "claude" in model.lower() if mapped_thinking is None: optional_params.pop("thinking", None) optional_params.pop("output_config", None) else: optional_params["thinking"] = mapped_thinking - if AnthropicConfig._is_adaptive_thinking_model(model): + # output_config + adaptive thinking is an Anthropic-only feature. + if is_claude and AnthropicConfig._is_adaptive_thinking_model(model): mapped_effort: Optional[str] = None if isinstance(reasoning_effort_value, str): mapped_effort = REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get(reasoning_effort_value) 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 cfdb76a97f4..e1a126298d7 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 @@ -8,6 +8,11 @@ from fastapi.testclient import TestClient sys.path.insert(0, os.path.abspath("../../../../..")) # Adds the parent directory to the system path from unittest.mock import MagicMock, patch +from litellm.constants import ( + DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, +) from litellm.llms.databricks.chat.transformation import ( DatabricksChatResponseIterator, DatabricksConfig, @@ -416,3 +421,114 @@ def test_transform_request_keeps_parallel_tool_calls_for_claude(): )["messages"] assert len([m for m in result if m.get("role") == "assistant"]) == 1 + + +# --------------------------------------------------------------------------- +# reasoning_effort translation +# +# Databricks foundation-model endpoints take reasoning controls via different +# payload shapes depending on the underlying model family: +# +# Claude: Anthropic-style `thinking={"type":"enabled","budget_tokens":N}` +# Gemini 2.5: Same Anthropic-style `thinking` payload as Claude +# (per docs.databricks.com/.../query-reason-models) +# Gemini 3+: Native OpenAI-style top-level `reasoning_effort` +# GPT-5/GPT-OSS: Native OpenAI-style top-level `reasoning_effort` +# +# LiteLLM should translate `reasoning_effort` into the right shape for the +# first two families and pass it through unchanged for the latter two. +# --------------------------------------------------------------------------- + + +def _map_reasoning_effort(model: str, reasoning_effort, **extra_non_default): + """Run map_openai_params with reasoning_effort + optional extras. + + `max_tokens` is included by default to mirror real client behavior. Without + it the base-class `update_optional_params_with_thinking_tokens` helper + KeyErrors on pure pass-through models (a pre-existing issue orthogonal to + this fix — `is_thinking_enabled` returns True whenever `reasoning_effort` is + set, but the helper then assumes `optional_params["thinking"]` exists). + """ + non_default = {"reasoning_effort": reasoning_effort, "max_tokens": 1024} + non_default.update(extra_non_default) + return DatabricksConfig().map_openai_params( + non_default_params=non_default, + optional_params={}, + model=model, + drop_params=False, + ) + + +def test_claude_translates_reasoning_effort_to_thinking(): + """Regression: Claude path must still translate to Anthropic-style thinking.""" + params = _map_reasoning_effort("databricks-claude-3-7-sonnet", "low") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_5_low_translates_to_thinking_budget(): + params = _map_reasoning_effort("databricks-gemini-2-5-flash", "low") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_5_medium_translates_to_thinking_budget(): + params = _map_reasoning_effort("databricks-gemini-2-5-flash", "medium") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_5_high_translates_to_thinking_budget(): + params = _map_reasoning_effort("databricks-gemini-2-5-flash", "high") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_5_pro_translates_to_thinking_budget(): + """Cover the gemini-2-5-pro endpoint too, not just flash.""" + params = _map_reasoning_effort("databricks-gemini-2-5-pro", "high") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_5_none_drops_thinking_and_reasoning_effort(): + """`reasoning_effort='none'` mirrors the Claude behavior: no thinking emitted.""" + params = _map_reasoning_effort("databricks-gemini-2-5-flash", "none") + assert "thinking" not in params + assert "reasoning_effort" not in params + + +def test_gemini_3_passes_reasoning_effort_through(): + """Databricks-Gemini-3+ accepts reasoning_effort natively — do not translate.""" + params = _map_reasoning_effort("databricks-gemini-3-1-pro", "low") + assert params.get("reasoning_effort") == "low" + assert "thinking" not in params + + +def test_gpt_5_passes_reasoning_effort_through(): + """Databricks-GPT-5 family accepts reasoning_effort natively.""" + params = _map_reasoning_effort("databricks-gpt-5-1", "low") + assert params.get("reasoning_effort") == "low" + assert "thinking" not in params + + +def test_gpt_oss_passes_reasoning_effort_through(): + """Databricks-GPT-OSS accepts reasoning_effort natively.""" + params = _map_reasoning_effort("databricks-gpt-oss-120b", "high") + assert params.get("reasoning_effort") == "high" + assert "thinking" not in params From d7b793c054a184ee9d7784559d959ba124ffe0d5 Mon Sep 17 00:00:00 2001 From: cohml <62400541+cohml@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:02:20 -0400 Subject: [PATCH 02/11] fix(databricks,base_llm): narrow gemini-2.5 match and guard thinking-tokens helper. --- litellm/llms/base_llm/chat/transformation.py | 11 +++++-- .../llms/databricks/chat/transformation.py | 5 +++- .../test_databricks_chat_transformation.py | 30 +++++++++++++------ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index ab901a467e8..693ef38bd29 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -14,7 +14,6 @@ from typing import ( Tuple, Type, Union, - cast, ) import httpx @@ -124,7 +123,15 @@ class BaseConfig(ABC): if is_thinking_enabled and ( "max_tokens" not in non_default_params and "max_completion_tokens" not in non_default_params ): - thinking_token_budget = cast(dict, optional_params["thinking"]).get("budget_tokens", None) + # `is_thinking_enabled` is True when `reasoning_effort` is set OR + # when `thinking` is set. Providers that pass `reasoning_effort` + # through natively (e.g. Databricks-Gemini-3+, Databricks-GPT-5) + # never populate an Anthropic-style `thinking` block, so guard + # against that case here. + thinking = optional_params.get("thinking") + if not isinstance(thinking, dict): + return + thinking_token_budget = thinking.get("budget_tokens", None) if thinking_token_budget is not None: optional_params["max_tokens"] = thinking_token_budget + DEFAULT_MAX_TOKENS diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index a79dfe8a59b..e6e63392e9c 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -276,7 +276,10 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): model_lower = model.lower() if "claude" in model_lower: return True - if "gemini-2" in model_lower: + # Match gemini-2-5 / gemini-2.5 only — not the broader 2.x range, which + # could catch hypothetical future 2.0/2.6/etc variants that may use a + # different reasoning contract. + if "gemini-2-5" in model_lower or "gemini-2.5" in model_lower: return True return False 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 e1a126298d7..02936e740f7 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 @@ -441,15 +441,8 @@ def test_transform_request_keeps_parallel_tool_calls_for_claude(): def _map_reasoning_effort(model: str, reasoning_effort, **extra_non_default): - """Run map_openai_params with reasoning_effort + optional extras. - - `max_tokens` is included by default to mirror real client behavior. Without - it the base-class `update_optional_params_with_thinking_tokens` helper - KeyErrors on pure pass-through models (a pre-existing issue orthogonal to - this fix — `is_thinking_enabled` returns True whenever `reasoning_effort` is - set, but the helper then assumes `optional_params["thinking"]` exists). - """ - non_default = {"reasoning_effort": reasoning_effort, "max_tokens": 1024} + """Run map_openai_params with reasoning_effort + optional extras.""" + non_default = {"reasoning_effort": reasoning_effort} non_default.update(extra_non_default) return DatabricksConfig().map_openai_params( non_default_params=non_default, @@ -506,6 +499,25 @@ def test_gemini_2_5_pro_translates_to_thinking_budget(): assert "reasoning_effort" not in params +def test_gemini_2_5_with_dot_notation_translates(): + """A user passing the upstream Google-style `gemini-2.5-...` form should + still trigger the Anthropic-thinking translation, not pass through.""" + params = _map_reasoning_effort("databricks-gemini-2.5-flash", "low") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + +def test_gemini_2_0_does_not_match(): + """Guard against over-matching: `gemini-2-0` (hypothetical or future) is + NOT a Gemini 2.5 endpoint and must not get the thinking translation.""" + params = _map_reasoning_effort("databricks-gemini-2-0-flash", "low") + assert "thinking" not in params + assert params.get("reasoning_effort") == "low" + + def test_gemini_2_5_none_drops_thinking_and_reasoning_effort(): """`reasoning_effort='none'` mirrors the Claude behavior: no thinking emitted.""" params = _map_reasoning_effort("databricks-gemini-2-5-flash", "none") From 2bff743f438c2abec4809ab4cfdcb742f2a9173f Mon Sep 17 00:00:00 2001 From: cohml <62400541+cohml@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:37:59 -0400 Subject: [PATCH 03/11] refactor(databricks): drive anthropic-thinking routing via JSON flag. --- litellm/llms/databricks/chat/transformation.py | 17 ++++++++--------- .../model_prices_and_context_window_backup.json | 10 ++++++++++ litellm/types/utils.py | 1 + litellm/utils.py | 1 + model_prices_and_context_window.json | 10 ++++++++++ .../chat/test_databricks_chat_transformation.py | 7 +++++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index e6e63392e9c..8902f4cb061 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -273,15 +273,14 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): GPT-5/GPT-OSS accept `reasoning_effort` natively and need no translation. """ - model_lower = model.lower() - if "claude" in model_lower: - return True - # Match gemini-2-5 / gemini-2.5 only — not the broader 2.x range, which - # could catch hypothetical future 2.0/2.6/etc variants that may use a - # different reasoning contract. - if "gemini-2-5" in model_lower or "gemini-2.5" in model_lower: - return True - return False + from litellm.utils import _supports_factory + + normalized = model.lower().replace(".", "-") + return _supports_factory( + model=normalized, + custom_llm_provider="databricks", + key="supports_anthropic_thinking_payload", + ) def convert_anthropic_tool_to_databricks_tool( self, tool: Optional[AllAnthropicToolsValues] diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 70b6b05e6ec..1cd41a40ed4 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -12690,6 +12690,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-haiku-4-5": { @@ -12709,6 +12710,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4": { @@ -12728,6 +12730,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4-1": { @@ -12747,6 +12750,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4-5": { @@ -12766,6 +12770,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true, "supports_output_config": true }, @@ -12786,6 +12791,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4-1": { @@ -12805,6 +12811,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4-5": { @@ -12824,6 +12831,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-flash": { @@ -12841,6 +12849,7 @@ "output_dbu_cost_per_token": 3.5714e-05, "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", "supports_function_calling": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-pro": { @@ -12858,6 +12867,7 @@ "output_dbu_cost_per_token": 0.000142857, "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", "supports_function_calling": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemma-3-12b": { diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 908f5b76424..10af3fc8866 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -151,6 +151,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False): supports_max_reasoning_effort: Optional[bool] supports_output_config: Optional[bool] supports_image_size: Optional[bool] + supports_anthropic_thinking_payload: Optional[bool] bedrock_output_config_effort_ceiling: Optional[Literal["low", "medium", "high", "max", "xhigh"]] bedrock_converse_supports_strict_tools: Optional[bool] diff --git a/litellm/utils.py b/litellm/utils.py index 19c2fe16085..5f7985b84fd 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -5472,6 +5472,7 @@ def _get_model_info_helper( supports_url_context=_model_info.get("supports_url_context", None), supports_reasoning=_model_info.get("supports_reasoning", None), supports_adaptive_thinking=_model_info.get("supports_adaptive_thinking", None), + supports_anthropic_thinking_payload=_model_info.get("supports_anthropic_thinking_payload", None), supports_none_reasoning_effort=_model_info.get("supports_none_reasoning_effort", None), supports_minimal_reasoning_effort=_model_info.get("supports_minimal_reasoning_effort", None), supports_low_reasoning_effort=_model_info.get("supports_low_reasoning_effort", None), diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b961c326625..fb9b192bac5 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -12690,6 +12690,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-haiku-4-5": { @@ -12709,6 +12710,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4": { @@ -12728,6 +12730,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4-1": { @@ -12747,6 +12750,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-opus-4-5": { @@ -12766,6 +12770,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true, "supports_output_config": true }, @@ -12786,6 +12791,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4-1": { @@ -12805,6 +12811,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4-5": { @@ -12824,6 +12831,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-flash": { @@ -12841,6 +12849,7 @@ "output_dbu_cost_per_token": 3.5714e-05, "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", "supports_function_calling": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-pro": { @@ -12858,6 +12867,7 @@ "output_dbu_cost_per_token": 0.000142857, "source": "https://www.databricks.com/product/pricing/proprietary-foundation-model-serving", "supports_function_calling": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemma-3-12b": { 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 02936e740f7..59872e9962f 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 @@ -8,6 +8,7 @@ from fastapi.testclient import TestClient sys.path.insert(0, os.path.abspath("../../../../..")) # Adds the parent directory to the system path from unittest.mock import MagicMock, patch +import litellm from litellm.constants import ( DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET, DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, @@ -20,6 +21,12 @@ from litellm.llms.databricks.chat.transformation import ( ) +@pytest.fixture(autouse=True) +def _use_local_model_cost_map(monkeypatch): + monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") + monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) + + def test_transform_choices(): config = DatabricksConfig() databricks_choices = [ From 7a7fea2a6198a67d2d71ebbedaadfff38ce27b4e Mon Sep 17 00:00:00 2001 From: milan Date: Mon, 20 Jul 2026 17:36:25 +0000 Subject: [PATCH 04/11] style: apply ruff format to databricks transformation Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/llms/databricks/chat/transformation.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 0b595d15484..34d6daa876f 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -389,10 +389,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "response_format", None ) # unsupported for claude models - if json_schema -> convert to tool call - if ( - "reasoning_effort" in non_default_params - and self._databricks_model_uses_anthropic_thinking_param(model) - ): + if "reasoning_effort" in non_default_params and self._databricks_model_uses_anthropic_thinking_param(model): reasoning_effort_value = non_default_params.get("reasoning_effort") mapped_thinking = AnthropicConfig._map_reasoning_effort( reasoning_effort=reasoning_effort_value, From e969eac8ea26818620807878384c5e3d4c392bb1 Mon Sep 17 00:00:00 2001 From: milan Date: Mon, 20 Jul 2026 17:46:24 +0000 Subject: [PATCH 05/11] test: register supports_anthropic_thinking_payload in model prices json schema Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- tests/test_litellm/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index a1a9448cc58..9b36a84bec8 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -862,6 +862,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "supports_xhigh_reasoning_effort": {"type": "boolean"}, "supports_max_reasoning_effort": {"type": "boolean"}, "supports_adaptive_thinking": {"type": "boolean"}, + "supports_anthropic_thinking_payload": {"type": "boolean"}, "supports_mid_conversation_system": {"type": "boolean"}, "supports_sampling_params": {"type": "boolean"}, "supports_output_config": {"type": "boolean"}, From d9c9dcf67af168036797e76bee8123df257ebe91 Mon Sep 17 00:00:00 2001 From: milan Date: Mon, 20 Jul 2026 18:44:25 +0000 Subject: [PATCH 06/11] fix(databricks): guard adaptive thinking to Claude and scope test fixture Addresses Greptile P2 feedback: reject an adaptive thinking payload for non-Claude Databricks models instead of silently emitting a shape the Gemini endpoint can't parse, and scope the local-cost-map fixture to the tests that need it rather than autouse. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../llms/databricks/chat/transformation.py | 10 ++++- .../test_databricks_chat_transformation.py | 42 +++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 34d6daa876f..76e2a5169d2 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -22,6 +22,7 @@ import httpx from pydantic import BaseModel from litellm.constants import RESPONSE_FORMAT_TOOL_NAME +from litellm.exceptions import BadRequestError from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( _handle_invalid_parallel_tool_calls, _should_convert_tool_call_to_json_mode, @@ -402,9 +403,16 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): optional_params.pop("thinking", None) optional_params.pop("output_config", None) else: + is_adaptive = mapped_thinking.get("type") == "adaptive" + if is_adaptive and not is_claude: + raise BadRequestError( + message=(f"Adaptive thinking is only supported on Databricks Claude models, not {model!r}."), + model=model, + llm_provider="databricks", + ) optional_params["thinking"] = mapped_thinking # output_config + adaptive thinking is an Anthropic-only feature. - if is_claude and AnthropicConfig._is_adaptive_thinking_model(model, "databricks"): + if is_claude and is_adaptive: mapped_effort: Optional[str] = None if isinstance(reasoning_effort_value, str): mapped_effort = REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get(reasoning_effort_value) 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 91a6f0683e8..c00f5a5d1a8 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 @@ -21,7 +21,7 @@ from litellm.llms.databricks.chat.transformation import ( ) -@pytest.fixture(autouse=True) +@pytest.fixture() def _use_local_model_cost_map(monkeypatch): monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True") monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url="")) @@ -466,7 +466,7 @@ def _map_reasoning_effort(model: str, reasoning_effort, **extra_non_default): ) -def test_claude_translates_reasoning_effort_to_thinking(): +def test_claude_translates_reasoning_effort_to_thinking(_use_local_model_cost_map): """Regression: Claude path must still translate to Anthropic-style thinking.""" params = _map_reasoning_effort("databricks-claude-3-7-sonnet", "low") assert params.get("thinking") == { @@ -476,7 +476,7 @@ def test_claude_translates_reasoning_effort_to_thinking(): assert "reasoning_effort" not in params -def test_gemini_2_5_low_translates_to_thinking_budget(): +def test_gemini_2_5_low_translates_to_thinking_budget(_use_local_model_cost_map): params = _map_reasoning_effort("databricks-gemini-2-5-flash", "low") assert params.get("thinking") == { "type": "enabled", @@ -485,7 +485,7 @@ def test_gemini_2_5_low_translates_to_thinking_budget(): assert "reasoning_effort" not in params -def test_gemini_2_5_medium_translates_to_thinking_budget(): +def test_gemini_2_5_medium_translates_to_thinking_budget(_use_local_model_cost_map): params = _map_reasoning_effort("databricks-gemini-2-5-flash", "medium") assert params.get("thinking") == { "type": "enabled", @@ -494,7 +494,7 @@ def test_gemini_2_5_medium_translates_to_thinking_budget(): assert "reasoning_effort" not in params -def test_gemini_2_5_high_translates_to_thinking_budget(): +def test_gemini_2_5_high_translates_to_thinking_budget(_use_local_model_cost_map): params = _map_reasoning_effort("databricks-gemini-2-5-flash", "high") assert params.get("thinking") == { "type": "enabled", @@ -503,7 +503,7 @@ def test_gemini_2_5_high_translates_to_thinking_budget(): assert "reasoning_effort" not in params -def test_gemini_2_5_pro_translates_to_thinking_budget(): +def test_gemini_2_5_pro_translates_to_thinking_budget(_use_local_model_cost_map): """Cover the gemini-2-5-pro endpoint too, not just flash.""" params = _map_reasoning_effort("databricks-gemini-2-5-pro", "high") assert params.get("thinking") == { @@ -513,7 +513,7 @@ def test_gemini_2_5_pro_translates_to_thinking_budget(): assert "reasoning_effort" not in params -def test_gemini_2_5_with_dot_notation_translates(): +def test_gemini_2_5_with_dot_notation_translates(_use_local_model_cost_map): """A user passing the upstream Google-style `gemini-2.5-...` form should still trigger the Anthropic-thinking translation, not pass through.""" params = _map_reasoning_effort("databricks-gemini-2.5-flash", "low") @@ -524,7 +524,7 @@ def test_gemini_2_5_with_dot_notation_translates(): assert "reasoning_effort" not in params -def test_gemini_2_0_does_not_match(): +def test_gemini_2_0_does_not_match(_use_local_model_cost_map): """Guard against over-matching: `gemini-2-0` (hypothetical or future) is NOT a Gemini 2.5 endpoint and must not get the thinking translation.""" params = _map_reasoning_effort("databricks-gemini-2-0-flash", "low") @@ -532,29 +532,47 @@ def test_gemini_2_0_does_not_match(): assert params.get("reasoning_effort") == "low" -def test_gemini_2_5_none_drops_thinking_and_reasoning_effort(): +def test_gemini_2_5_none_drops_thinking_and_reasoning_effort(_use_local_model_cost_map): """`reasoning_effort='none'` mirrors the Claude behavior: no thinking emitted.""" params = _map_reasoning_effort("databricks-gemini-2-5-flash", "none") assert "thinking" not in params assert "reasoning_effort" not in params -def test_gemini_3_passes_reasoning_effort_through(): +def test_gemini_3_passes_reasoning_effort_through(_use_local_model_cost_map): """Databricks-Gemini-3+ accepts reasoning_effort natively — do not translate.""" params = _map_reasoning_effort("databricks-gemini-3-1-pro", "low") assert params.get("reasoning_effort") == "low" assert "thinking" not in params -def test_gpt_5_passes_reasoning_effort_through(): +def test_gpt_5_passes_reasoning_effort_through(_use_local_model_cost_map): """Databricks-GPT-5 family accepts reasoning_effort natively.""" params = _map_reasoning_effort("databricks-gpt-5-1", "low") assert params.get("reasoning_effort") == "low" assert "thinking" not in params -def test_gpt_oss_passes_reasoning_effort_through(): +def test_gpt_oss_passes_reasoning_effort_through(_use_local_model_cost_map): """Databricks-GPT-OSS accepts reasoning_effort natively.""" params = _map_reasoning_effort("databricks-gpt-oss-120b", "high") assert params.get("reasoning_effort") == "high" assert "thinking" not in params + + +def test_non_claude_adaptive_thinking_flag_is_rejected(monkeypatch, _use_local_model_cost_map): + """Adaptive thinking + output_config is Claude-only; a non-Claude model that + resolves to an adaptive payload would send Databricks' Gemini endpoint a shape + it can't parse, so the translation must fail loudly instead of passing it through.""" + fake_model = "databricks-gemini-2-5-adaptive-probe" + monkeypatch.setitem( + litellm.model_cost, + fake_model, + { + "litellm_provider": "databricks", + "supports_anthropic_thinking_payload": True, + "supports_adaptive_thinking": True, + }, + ) + with pytest.raises(litellm.exceptions.BadRequestError): + _map_reasoning_effort(fake_model, "high") From 38ffa6736f1c04d39e33c96bb4df7d13a721d5f5 Mon Sep 17 00:00:00 2001 From: milan Date: Sun, 23 Aug 2026 20:47:56 +0000 Subject: [PATCH 07/11] fix(types): qualify supports_anthropic_thinking_payload with ReadOnly Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/types/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 1abafb15ebd..e07edde68c5 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -165,7 +165,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False): supports_max_reasoning_effort: bool | None supports_output_config: bool | None supports_image_size: bool | None - supports_anthropic_thinking_payload: bool | None + supports_anthropic_thinking_payload: ReadOnly[bool | None] bedrock_output_config_effort_ceiling: Literal["low", "medium", "high", "max", "xhigh"] | None bedrock_converse_supports_strict_tools: bool | None From 896b6dc95678e57c63d0f7d396c6a817394c8b81 Mon Sep 17 00:00:00 2001 From: milan Date: Sun, 23 Aug 2026 21:16:52 +0000 Subject: [PATCH 08/11] ci: regenerate model prices schema and raise unit shard job timeouts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .github/workflows/test-unit.yml | 6 +++--- model_prices_and_context_window.schema.json | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index a7c67f2b35d..2dfca3d308f 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -211,7 +211,7 @@ jobs: workers: 2 reruns: 2 timeout-minutes: 20 - job-timeout-minutes: 55 + job-timeout-minutes: 60 - shard: proxy-extras artifact-name: proxy-extras @@ -219,7 +219,7 @@ jobs: workers: 2 reruns: 2 timeout-minutes: 20 - job-timeout-minutes: 55 + job-timeout-minutes: 60 - shard: enterprise-package artifact-name: enterprise-package @@ -227,7 +227,7 @@ jobs: workers: 4 reruns: 2 timeout-minutes: 20 - job-timeout-minutes: 55 + job-timeout-minutes: 60 - shard: responses-caching-types artifact-name: responses-caching-types diff --git a/model_prices_and_context_window.schema.json b/model_prices_and_context_window.schema.json index f5560a20ab2..06881c90e3d 100644 --- a/model_prices_and_context_window.schema.json +++ b/model_prices_and_context_window.schema.json @@ -601,6 +601,9 @@ "supports_adaptive_thinking": { "type": "boolean" }, + "supports_anthropic_thinking_payload": { + "type": "boolean" + }, "supports_assistant_prefill": { "type": "boolean" }, From 8a5aeed2961a1c960ac10dbe1b46a73d72665956 Mon Sep 17 00:00:00 2001 From: milan Date: Sun, 23 Aug 2026 22:08:26 +0000 Subject: [PATCH 09/11] fix(databricks): add supports_anthropic_thinking_payload to claude opus/sonnet 4-6 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/model_prices_and_context_window_backup.json | 2 ++ model_prices_and_context_window.json | 2 ++ 2 files changed, 4 insertions(+) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 96e98eec8e6..40b3ddae11f 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -14683,6 +14683,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4": { @@ -14762,6 +14763,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-flash": { diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 96e98eec8e6..40b3ddae11f 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -14683,6 +14683,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-claude-sonnet-4": { @@ -14762,6 +14763,7 @@ "supports_assistant_prefill": true, "supports_function_calling": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_tool_choice": true }, "databricks/databricks-gemini-2-5-flash": { From c7b607c46e4efa7cc04726e49c96030ea374bb08 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 12 Sep 2026 13:13:38 -0700 Subject: [PATCH 10/11] fix(databricks): keep the Claude fallback when gating the anthropic thinking payload Gate the reasoning_effort translation on the cost-map flag or the model name containing claude, so unmapped Claude serving endpoints keep translating. Flag the newer Claude entries that were missing it. Expose supports_anthropic_thinking_payload as a public helper next to the other supports_* wrappers instead of importing the private factory. Drop the adaptive-only guard, since the adaptive flags only ever match Claude ids, and add regression tests for an unmapped Claude endpoint and an adaptive Claude model --- .../llms/databricks/chat/transformation.py | 30 +---- ...odel_prices_and_context_window_backup.json | 6 + litellm/utils.py | 6 + model_prices_and_context_window.json | 6 + .../test_databricks_chat_transformation.py | 106 ++++++------------ 5 files changed, 61 insertions(+), 93 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 517c79c2e17..82c3b5d91d3 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -10,7 +10,6 @@ import httpx from pydantic import BaseModel from litellm.constants import RESPONSE_FORMAT_TOOL_NAME -from litellm.exceptions import BadRequestError from litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response import ( _handle_invalid_parallel_tool_calls, _should_convert_tool_call_to_json_mode, @@ -274,21 +273,12 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): ] @staticmethod - def _databricks_model_uses_anthropic_thinking_param(model: str) -> bool: - """ - Per Databricks docs, Claude and Gemini 2.5 endpoints accept the - Anthropic-style `thinking={"type":"enabled","budget_tokens":N}` payload - and do NOT accept OpenAI's top-level `reasoning_effort`. Gemini 3+ and - GPT-5/GPT-OSS accept `reasoning_effort` natively and need no - translation. - """ - from litellm.utils import _supports_factory + def _uses_anthropic_thinking_param(model: str) -> bool: + from litellm.utils import supports_anthropic_thinking_payload normalized: Final = model.lower().replace(".", "-") - return _supports_factory( - model=normalized, - custom_llm_provider="databricks", - key="supports_anthropic_thinking_payload", + return "claude" in normalized or supports_anthropic_thinking_payload( + model=normalized, custom_llm_provider="databricks" ) def convert_anthropic_tool_to_databricks_tool(self, tool: AllAnthropicToolsValues | None) -> DatabricksTool | None: @@ -396,7 +386,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): "response_format", None ) # unsupported for claude models - if json_schema -> convert to tool call - if "reasoning_effort" in non_default_params and self._databricks_model_uses_anthropic_thinking_param(model): + if "reasoning_effort" in non_default_params and self._uses_anthropic_thinking_param(model): reasoning_effort_value: Final = non_default_params.get("reasoning_effort") mapped_thinking: Final = AnthropicConfig._map_reasoning_effort( reasoning_effort=reasoning_effort_value, @@ -404,20 +394,12 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): custom_llm_provider="databricks", llm_provider="databricks", ) - is_claude: Final = "claude" in model.lower() if mapped_thinking is None: optional_params.pop("thinking", None) optional_params.pop("output_config", None) else: - is_adaptive: Final = mapped_thinking.get("type") == "adaptive" - if is_adaptive and not is_claude: - raise BadRequestError( - message=(f"Adaptive thinking is only supported on Databricks Claude models, not {model!r}."), - model=model, - llm_provider="databricks", - ) optional_params["thinking"] = mapped_thinking - if is_claude and is_adaptive: + if AnthropicConfig._is_adaptive_thinking_model(model, "databricks"): mapped_effort: str | None = None if isinstance(reasoning_effort_value, str): mapped_effort = REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT.get(reasoning_effort_value) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 8da437d85fd..39856b0d933 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -17623,6 +17623,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": false, @@ -17652,6 +17653,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true, @@ -17801,6 +17803,7 @@ "supports_function_calling": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17828,6 +17831,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17855,6 +17859,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17979,6 +17984,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true diff --git a/litellm/utils.py b/litellm/utils.py index 784d02ccc00..c52a8fbc42b 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -2850,6 +2850,12 @@ def supports_reasoning(model: str, custom_llm_provider: str | None = None) -> bo return _supports_factory(model=model, custom_llm_provider=custom_llm_provider, key="supports_reasoning") +def supports_anthropic_thinking_payload(model: str, custom_llm_provider: str | None = None) -> bool: + return _supports_factory( + model=model, custom_llm_provider=custom_llm_provider, key="supports_anthropic_thinking_payload" + ) + + def supports_none_reasoning_effort(model: str, custom_llm_provider: str | None = None) -> bool: """ Check if the given model accepts reasoning effort "none" and return a boolean value. diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 8da437d85fd..39856b0d933 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -17623,6 +17623,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": false, @@ -17652,6 +17653,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true, @@ -17801,6 +17803,7 @@ "supports_function_calling": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17828,6 +17831,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17855,6 +17859,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true @@ -17979,6 +17984,7 @@ "supports_mid_conversation_system": true, "supports_prompt_caching": true, "supports_reasoning": true, + "supports_anthropic_thinking_payload": true, "supports_sampling_params": false, "supports_tool_choice": true, "supports_vision": true 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 40d7f834052..52bb89fed5a 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 @@ -523,29 +523,30 @@ def test_databricks_config_probes_capabilities_under_databricks_namespace(): assert DatabricksConfig().custom_llm_provider == "databricks" -# --------------------------------------------------------------------------- -# reasoning_effort translation -# -# Databricks foundation-model endpoints take reasoning controls via different -# payload shapes depending on the underlying model family: -# -# Claude: Anthropic-style `thinking={"type":"enabled","budget_tokens":N}` -# Gemini 2.5: Same Anthropic-style `thinking` payload as Claude -# (per docs.databricks.com/.../query-reason-models) -# Gemini 3+: Native OpenAI-style top-level `reasoning_effort` -# GPT-5/GPT-OSS: Native OpenAI-style top-level `reasoning_effort` -# -# LiteLLM should translate `reasoning_effort` into the right shape for the -# first two families and pass it through unchanged for the latter two. -# --------------------------------------------------------------------------- +@pytest.mark.parametrize( + "model, expected_thinking, expected_output_config", + [ + ("databricks-claude-opus-4-8", {"type": "adaptive"}, {"effort": "high"}), + ("databricks-claude-opus-4-6", {"type": "enabled", "budget_tokens": 4096}, None), + ], + ids=["adaptive_only_upgrades_to_adaptive", "legacy_capable_forwards_verbatim"], +) +def test_map_openai_params_upgrades_legacy_thinking_on_adaptive_only_claude( + model, expected_thinking, expected_output_config +): + mapped = DatabricksConfig().map_openai_params( + non_default_params={"thinking": {"type": "enabled", "budget_tokens": 4096}}, + optional_params={}, + model=model, + drop_params=False, + ) + assert mapped["thinking"] == expected_thinking + assert mapped.get("output_config") == expected_output_config -def _map_reasoning_effort(model: str, reasoning_effort, **extra_non_default): - """Run map_openai_params with reasoning_effort + optional extras.""" - non_default = {"reasoning_effort": reasoning_effort} - non_default.update(extra_non_default) +def _map_reasoning_effort(model: str, reasoning_effort: str): return DatabricksConfig().map_openai_params( - non_default_params=non_default, + non_default_params={"reasoning_effort": reasoning_effort}, optional_params={}, model=model, drop_params=False, @@ -553,7 +554,6 @@ def _map_reasoning_effort(model: str, reasoning_effort, **extra_non_default): def test_claude_translates_reasoning_effort_to_thinking(_use_local_model_cost_map): - """Regression: Claude path must still translate to Anthropic-style thinking.""" params = _map_reasoning_effort("databricks-claude-3-7-sonnet", "low") assert params.get("thinking") == { "type": "enabled", @@ -562,6 +562,22 @@ def test_claude_translates_reasoning_effort_to_thinking(_use_local_model_cost_ma assert "reasoning_effort" not in params +def test_adaptive_claude_translates_reasoning_effort_to_output_config(_use_local_model_cost_map): + params = _map_reasoning_effort("databricks-claude-opus-4-7", "high") + assert params.get("thinking") == {"type": "adaptive", "display": "summarized"} + assert params.get("output_config") == {"effort": "high"} + assert "reasoning_effort" not in params + + +def test_unmapped_claude_endpoint_still_translates(_use_local_model_cost_map): + params = _map_reasoning_effort("my-claude-serving-endpoint", "low") + assert params.get("thinking") == { + "type": "enabled", + "budget_tokens": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET, + } + assert "reasoning_effort" not in params + + def test_gemini_2_5_low_translates_to_thinking_budget(_use_local_model_cost_map): params = _map_reasoning_effort("databricks-gemini-2-5-flash", "low") assert params.get("thinking") == { @@ -590,7 +606,6 @@ def test_gemini_2_5_high_translates_to_thinking_budget(_use_local_model_cost_map def test_gemini_2_5_pro_translates_to_thinking_budget(_use_local_model_cost_map): - """Cover the gemini-2-5-pro endpoint too, not just flash.""" params = _map_reasoning_effort("databricks-gemini-2-5-pro", "high") assert params.get("thinking") == { "type": "enabled", @@ -600,8 +615,6 @@ def test_gemini_2_5_pro_translates_to_thinking_budget(_use_local_model_cost_map) def test_gemini_2_5_with_dot_notation_translates(_use_local_model_cost_map): - """A user passing the upstream Google-style `gemini-2.5-...` form should - still trigger the Anthropic-thinking translation, not pass through.""" params = _map_reasoning_effort("databricks-gemini-2.5-flash", "low") assert params.get("thinking") == { "type": "enabled", @@ -611,80 +624,35 @@ def test_gemini_2_5_with_dot_notation_translates(_use_local_model_cost_map): def test_gemini_2_0_does_not_match(_use_local_model_cost_map): - """Guard against over-matching: `gemini-2-0` (hypothetical or future) is - NOT a Gemini 2.5 endpoint and must not get the thinking translation.""" params = _map_reasoning_effort("databricks-gemini-2-0-flash", "low") assert "thinking" not in params assert params.get("reasoning_effort") == "low" def test_gemini_2_5_none_drops_thinking_and_reasoning_effort(_use_local_model_cost_map): - """`reasoning_effort='none'` mirrors the Claude behavior: no thinking emitted.""" params = _map_reasoning_effort("databricks-gemini-2-5-flash", "none") assert "thinking" not in params assert "reasoning_effort" not in params def test_gemini_3_passes_reasoning_effort_through(_use_local_model_cost_map): - """Databricks-Gemini-3+ accepts reasoning_effort natively — do not translate.""" params = _map_reasoning_effort("databricks-gemini-3-1-pro", "low") assert params.get("reasoning_effort") == "low" assert "thinking" not in params def test_gpt_5_passes_reasoning_effort_through(_use_local_model_cost_map): - """Databricks-GPT-5 family accepts reasoning_effort natively.""" params = _map_reasoning_effort("databricks-gpt-5-1", "low") assert params.get("reasoning_effort") == "low" assert "thinking" not in params def test_gpt_oss_passes_reasoning_effort_through(_use_local_model_cost_map): - """Databricks-GPT-OSS accepts reasoning_effort natively.""" params = _map_reasoning_effort("databricks-gpt-oss-120b", "high") assert params.get("reasoning_effort") == "high" assert "thinking" not in params -def test_non_claude_adaptive_thinking_flag_is_rejected(monkeypatch, _use_local_model_cost_map): - """Adaptive thinking + output_config is Claude-only; a non-Claude model that - resolves to an adaptive payload would send Databricks' Gemini endpoint a shape - it can't parse, so the translation must fail loudly instead of passing it through.""" - fake_model = "databricks-gemini-2-5-adaptive-probe" - monkeypatch.setitem( - litellm.model_cost, - fake_model, - { - "litellm_provider": "databricks", - "supports_anthropic_thinking_payload": True, - "supports_adaptive_thinking": True, - }, - ) - with pytest.raises(litellm.exceptions.BadRequestError): - _map_reasoning_effort(fake_model, "high") - - -@pytest.mark.parametrize( - "model, expected_thinking, expected_output_config", - [ - ("databricks-claude-opus-4-8", {"type": "adaptive"}, {"effort": "high"}), - ("databricks-claude-opus-4-6", {"type": "enabled", "budget_tokens": 4096}, None), - ], - ids=["adaptive_only_upgrades_to_adaptive", "legacy_capable_forwards_verbatim"], -) -def test_map_openai_params_upgrades_legacy_thinking_on_adaptive_only_claude( - model, expected_thinking, expected_output_config -): - mapped = DatabricksConfig().map_openai_params( - non_default_params={"thinking": {"type": "enabled", "budget_tokens": 4096}}, - optional_params={}, - model=model, - drop_params=False, - ) - assert mapped["thinking"] == expected_thinking - assert mapped.get("output_config") == expected_output_config - - def _streaming_chunk(usage=None, choices=None): base = { "id": "chatcmpl-test", From a7ebc12673b2f0f03bbaa10608d6e76187851905 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Sat, 12 Sep 2026 15:53:03 -0700 Subject: [PATCH 11/11] ci: drop the removed rust bridge test path from the ocr job --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 32d2cf0390c..84d8f48b4be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1084,9 +1084,7 @@ jobs: name: Run tests command: | mkdir -p test-results - TEST_FILES=$(printf "%s\n%s\n" \ - "$(circleci tests glob "tests/ocr_tests/**/test_*.py")" \ - "tests/test_litellm/ocr/test_rust_bridge.py") + TEST_FILES=$(circleci tests glob "tests/ocr_tests/**/test_*.py") echo "$TEST_FILES" | circleci tests run \ --verbose \ --command="tr ' ' '\\n' | awk '/\\.py/ {print; next} {sub(/\\.[A-Z][^.]*$/, \"\"); gsub(/\\./, \"/\"); print \$0 \".py\"}' | xargs uv run --no-sync python -m pytest \