diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 538904b34e6..063462287f8 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -387,7 +387,15 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): ) # unsupported for claude models - if json_schema -> convert to tool call if "reasoning_effort" in non_default_params and self._uses_anthropic_thinking_param(model): - reasoning_effort_value: Final = non_default_params.get("reasoning_effort") + reasoning_effort_value = non_default_params.get("reasoning_effort") + # Accept both the string form ("low") and the dict form + # ({"effort": "low", "summary": "concise"}). The Responses->Chat + # parser keeps the full dict whenever `summary` is set (#28196), + # so a dict is the standard shape Responses-bridge callers send. + # Same coercion the direct Anthropic and Bedrock Converse + # adapters already do. + if isinstance(reasoning_effort_value, dict): + reasoning_effort_value = reasoning_effort_value.get("effort") mapped_thinking: Final = AnthropicConfig._map_reasoning_effort( reasoning_effort=reasoning_effort_value, model=model, diff --git a/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py b/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py index 52bb89fed5a..888c03a46b4 100644 --- a/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py +++ b/tests/unit/llms/databricks/chat/test_databricks_chat_transformation.py @@ -544,7 +544,7 @@ def test_map_openai_params_upgrades_legacy_thinking_on_adaptive_only_claude( assert mapped.get("output_config") == expected_output_config -def _map_reasoning_effort(model: str, reasoning_effort: str): +def _map_reasoning_effort(model: str, reasoning_effort: str | dict): return DatabricksConfig().map_openai_params( non_default_params={"reasoning_effort": reasoning_effort}, optional_params={}, @@ -578,6 +578,22 @@ def test_unmapped_claude_endpoint_still_translates(_use_local_model_cost_map): assert "reasoning_effort" not in params +@pytest.mark.parametrize( + "model", + ["databricks-claude-3-7-sonnet", "my-claude-serving-endpoint"], +) +def test_reasoning_effort_dict_is_coerced(_use_local_model_cost_map, model): + """The Responses->Chat parser keeps `reasoning_effort` as a dict whenever + `summary` is set (#28196). Without coercion the dict reaches + `_map_reasoning_effort` and no thinking payload is produced.""" + params = _map_reasoning_effort(model, {"effort": "low", "summary": "concise"}) + 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") == {