mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(databricks): only inject a reasoning block when content follows it
Databricks rejects a reasoning block in the final position of an assistant message. That is exactly a thinking-plus-tool-call turn: the call moves to message-level tool_calls, leaving the reasoning block alone and therefore final, which returns "The final block in an assistant message cannot be thinking". Verified against a live endpoint. The keys are still stripped when the block cannot be placed, so neither the original nor this failure can occur.
This commit is contained in:
parent
ffb6294b7b
commit
40c09ee27d
2 changed files with 40 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue