From ffef39710aaa5e1e75459c939cb48ecd9e4fce85 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 24 Sep 2026 15:19:37 +0800 Subject: [PATCH] fix(databricks): handle tool-only replies without content --- .../llms/databricks/chat/transformation.py | 2 +- litellm/types/llms/databricks.py | 2 +- .../test_databricks_chat_transformation.py | 66 ++++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/litellm/llms/databricks/chat/transformation.py b/litellm/llms/databricks/chat/transformation.py index 538904b34e6..aa3fadfa920 100644 --- a/litellm/llms/databricks/chat/transformation.py +++ b/litellm/llms/databricks/chat/transformation.py @@ -558,7 +558,7 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig): def resolve_reasoning_and_content( message: DatabricksMessage, block_reasoning_content: str | None ) -> tuple[str | None, str | None]: - content_str: Final = DatabricksConfig.extract_content_str(message["content"]) + content_str: Final = DatabricksConfig.extract_content_str(message.get("content")) if block_reasoning_content is not None: return block_reasoning_content, content_str return _extract_reasoning_content({**message, "content": content_str}) diff --git a/litellm/types/llms/databricks.py b/litellm/types/llms/databricks.py index a9c027bd2de..7dde9147040 100644 --- a/litellm/types/llms/databricks.py +++ b/litellm/types/llms/databricks.py @@ -56,7 +56,7 @@ class DatabricksTool(TypedDict): class DatabricksMessage(TypedDict, total=False): role: Required[str] - content: Required[AllDatabricksContentValues] + content: ReadOnly[AllDatabricksContentValues | None] tool_calls: list[DatabricksTool] | None reasoning_content: ReadOnly[str | None] reasoning: ReadOnly[str | None] 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 a3391a2c585..60cfcb0ed8c 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 @@ -7,6 +7,64 @@ import respx import litellm +def test_completion_accepts_tool_call_without_content(respx_mock: respx.MockRouter): + upstream: Final = respx_mock.post("https://example.databricks.test/serving-endpoints/chat/completions").mock( + return_value=httpx.Response( + status_code=200, + json={ + "id": "chatcmpl-tool-call", + "object": "chat.completion", + "created": 1677652288, + "model": "my-custom-model", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "tool_calls": [ + { + "id": "call_weather", + "type": "function", + "function": {"name": "get_weather", "arguments": '{"city":"Paris"}'}, + } + ], + }, + "finish_reason": "tool_calls", + } + ], + "usage": {"prompt_tokens": 9, "completion_tokens": 5, "total_tokens": 14}, + }, + ) + ) + + response: Final = litellm.completion( + model="databricks/my-custom-model", + messages=[{"role": "user", "content": "What is the weather in Paris?"}], + tools=[ + { + "type": "function", + "function": { + "name": "get_weather", + "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}, + }, + } + ], + api_base="https://example.databricks.test/serving-endpoints", + api_key="fake-databricks-api-key", + num_retries=0, + ) + + assert upstream.call_count == 1 + assert response.choices[0].finish_reason == "tool_calls" + message: Final = response.choices[0].message + assert message.content is None + assert message.tool_calls is not None + assert len(message.tool_calls) == 1 + assert message.tool_calls[0].id == "call_weather" + assert message.tool_calls[0].function.name == "get_weather" + assert message.tool_calls[0].function.arguments == '{"city":"Paris"}' + + def test_completion_merges_leading_system_and_developer_messages_for_chat_template_models( respx_mock: respx.MockRouter, ): @@ -18,7 +76,9 @@ def test_completion_merges_leading_system_and_developer_messages_for_chat_templa "object": "chat.completion", "created": 1677652288, "model": "my-custom-model", - "choices": [{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}], + "choices": [ + {"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"} + ], "usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10}, }, ) @@ -54,7 +114,9 @@ def test_completion_merges_system_messages_when_one_has_empty_content(respx_mock "object": "chat.completion", "created": 1677652288, "model": "my-custom-model", - "choices": [{"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"}], + "choices": [ + {"index": 0, "message": {"role": "assistant", "content": "Answer"}, "finish_reason": "stop"} + ], "usage": {"prompt_tokens": 9, "completion_tokens": 1, "total_tokens": 10}, }, )