diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index d236fdd0d23..9848bb1da31 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -481,10 +481,13 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): Databricks rejects both message-level keys outright, and constrains the replacement to exactly one reasoning block holding exactly one summary entry, whose signature is - required. Blocks without a signature cannot be replayed, and a message already carrying - a reasoning block must not gain a second one, so in both cases the keys are dropped - rather than sent in a form the API refuses. reasoning_content is a plain-text mirror of - the same thinking, so it carries nothing the signed summary does not. + required. It also rejects a reasoning block as the final block of an assistant message, + which happens when the turn was thinking plus a tool call: the call moves to + message-level tool_calls and leaves nothing behind it. So the block is only injected + when other content follows it, and a message already carrying a reasoning block never + gains a second one. Otherwise the keys are dropped rather than sent in a form the API + refuses. reasoning_content is a plain-text mirror of the same thinking, so it carries + nothing the signed summary does not. """ dropped: Final = ("thinking_blocks", "reasoning_content") stripped: Final = {k: v for k, v in message.items() if k not in dropped} # mutable-ok: outbound provider JSON @@ -497,7 +500,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): None, ) holds_reasoning: Final = any(isinstance(b, dict) and b.get("type") == "reasoning" for b in existing) - replace: Final = signed is not None and not holds_reasoning + replace: Final = signed is not None and not holds_reasoning and bool(existing) text: Final = (signed.get("thinking") or "") if signed is not None else "" signature: Final = signed["signature"] if signed is not None else "" entry: Final = {"type": "summary_text", "text": text, "signature": signature} # mutable-ok: provider JSON 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 6800c61d4e1..5a5a8acc748 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 @@ -626,3 +626,35 @@ def test_existing_reasoning_block_is_not_duplicated(): assert "thinking_blocks" not in result assert [b for b in result["content"] if b.get("type") == "reasoning"] == [already_converted] + + +def test_reasoning_block_is_not_emitted_as_the_only_content_block(): + """Databricks rejects a reasoning block as the final block of an assistant message. That + is exactly a thinking-plus-tool-call turn, where the call moves to message-level + tool_calls and leaves nothing after the reasoning block, so the block is dropped.""" + result = DatabricksConfig()._move_reasoning_into_content_block( + { + "role": "assistant", + "content": None, + "tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "bash", "arguments": "{}"}}], + "thinking_blocks": [{"type": "thinking", "thinking": "", "signature": "sig-abc"}], + } + ) + + assert "thinking_blocks" not in result + assert not any(isinstance(b, dict) and b.get("type") == "reasoning" for b in result.get("content") or []) + assert result["tool_calls"][0]["id"] == "call_1" + + +def test_reasoning_block_still_emitted_when_text_follows(): + """The normal case must keep working: text after the reasoning block is valid.""" + result = DatabricksConfig()._move_reasoning_into_content_block( + { + "role": "assistant", + "content": "391", + "thinking_blocks": [{"type": "thinking", "thinking": "t", "signature": "sig-abc"}], + } + ) + + assert result["content"][0]["type"] == "reasoning" + assert result["content"][1] == {"type": "text", "text": "391"}