fix(databricks): drop assistant turns left empty after stripping thinking_blocks

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-09-02 21:43:22 +00:00
parent e543ae3980
commit 518506834a
2 changed files with 53 additions and 1 deletions

View file

@ -56,6 +56,14 @@ from ...openai_like.chat.transformation import OpenAILikeChatConfig
from ..common_utils import DatabricksBase, DatabricksException
def _is_bare_assistant_message(message_dict: dict[str, Any]) -> bool:
"""Databricks rejects assistant messages with neither content nor tool calls, e.g. a replayed
thinking-only turn once its `thinking_blocks` are stripped."""
return message_dict.get("role") == "assistant" and not any(
message_dict.get(key) for key in ("content", "tool_calls", "function_call")
)
def _sanitize_empty_content(message_dict: dict[str, Any]) -> None:
"""
Remove or filter content so empty text blocks are not sent.
@ -434,6 +442,8 @@ class DatabricksConfig(DatabricksBase, OpenAILikeChatConfig, AnthropicConfig):
if "cache_control" in _message and isinstance(_message.get("content"), str):
_message = self._move_cache_control_into_string_content_block(_message)
_sanitize_empty_content(cast(dict[str, Any], _message))
if _is_bare_assistant_message(cast(dict[str, Any], _message)):
continue
new_messages.append(_message)
if "claude" not in model:

View file

@ -284,11 +284,53 @@ def test_transform_request_strips_thinking_blocks_and_reasoning_content():
assert result[1] == {"role": "assistant", "content": "Hello! How can I help?"}
assert not any(
key in message for message in result for key in ("thinking_blocks", "reasoning_content", "provider_specific_fields")
key in message
for message in result
for key in ("thinking_blocks", "reasoning_content", "provider_specific_fields")
)
assert "thinking_blocks" in messages[1]
def test_transform_request_drops_thinking_only_assistant_turn_but_keeps_tool_call_turn():
"""A replayed thinking-only assistant turn has nothing left once `thinking_blocks` are stripped, so it must be
dropped instead of being sent as a bare {"role": "assistant"}. A thinking + tool_use turn keeps its tool_calls."""
config = DatabricksConfig()
tool_call = {"id": "call_1", "type": "function", "function": {"name": "f", "arguments": "{}"}}
messages = [
{"role": "user", "content": "hi"},
{
"role": "assistant",
"content": None,
"thinking_blocks": [{"type": "thinking", "thinking": "hmm", "signature": "sig_1"}],
"reasoning_content": "hmm",
},
{"role": "user", "content": "again"},
{
"role": "assistant",
"content": None,
"thinking_blocks": [{"type": "thinking", "thinking": "call f", "signature": "sig_2"}],
"reasoning_content": "call f",
"tool_calls": [tool_call],
},
{"role": "tool", "tool_call_id": "call_1", "content": "ok"},
]
result = config.transform_request(
model="databricks-claude-opus-5",
messages=messages,
optional_params={},
litellm_params={},
headers={},
)["messages"]
assert result == [
{"role": "user", "content": "hi"},
{"role": "user", "content": "again"},
{"role": "assistant", "tool_calls": [tool_call]},
{"role": "tool", "tool_call_id": "call_1", "content": "ok"},
]
def _parallel_tool_calls():
return [
{