fix: lost tool_calls when streaming has both text and tool_calls

This commit is contained in:
Harshit Jain 2025-12-21 22:40:27 +05:30
parent acadaca7e7
commit b2588dc399
No known key found for this signature in database
GPG key ID: 36C392CD4415B4CF
2 changed files with 57 additions and 4 deletions

View file

@ -740,9 +740,7 @@ class LiteLLMAnthropicMessagesAdapter:
from litellm.types.llms.anthropic import TextBlock, ToolUseBlock
for choice in choices:
if choice.delta.content is not None and len(choice.delta.content) > 0:
return "text", TextBlock(type="text", text="")
elif (
if (
choice.delta.tool_calls is not None
and len(choice.delta.tool_calls) > 0
and choice.delta.tool_calls[0].function is not None
@ -753,6 +751,8 @@ class LiteLLMAnthropicMessagesAdapter:
name=choice.delta.tool_calls[0].function.name or "",
input={}, # type: ignore[typeddict-item]
)
elif choice.delta.content is not None and len(choice.delta.content) > 0:
return "text", TextBlock(type="text", text="")
elif isinstance(choice, StreamingChoices) and hasattr(
choice.delta, "thinking_blocks"
):
@ -796,7 +796,7 @@ class LiteLLMAnthropicMessagesAdapter:
for choice in choices:
if choice.delta.content is not None and len(choice.delta.content) > 0:
text += choice.delta.content
elif choice.delta.tool_calls is not None:
if choice.delta.tool_calls is not None:
partial_json = ""
for tool in choice.delta.tool_calls:
if (

View file

@ -1055,3 +1055,56 @@ def test_translate_anthropic_messages_to_openai_tool_result_single_item_backward
f"got {type(tool_message['content'])}"
)
assert tool_message["content"] == "72°F and sunny"
def test_streaming_chunk_with_both_text_and_tool_calls_issue_18238():
"""
When a streaming choice contains both text content and tool_calls,
both should be processed (tool_calls should not be ignored).
"""
# streaming choice with both text and tool_calls
choices = [
StreamingChoices(
finish_reason=None,
index=0,
delta=Delta(
provider_specific_fields=None,
content="Here is some text for litellm",
role=None,
function_call=None,
tool_calls=[
ChatCompletionDeltaToolCall(
id="toolu_bdrk_013xRVejhv3ybmLEGCoZib2b",
function=Function(arguments='{"cmd": "init"}', name="Bash"),
type="function",
index=0,
)
],
audio=None,
),
logprobs=None,
)
]
adapter = LiteLLMAnthropicMessagesAdapter()
# When both text and tool_calls exist, tool_calls (input_json_delta) takes priority
(
type_of_content,
content_block_delta,
) = adapter._translate_streaming_openai_chunk_to_anthropic(choices=choices)
assert type_of_content == "input_json_delta"
assert content_block_delta["partial_json"] == '{"cmd": "init"}'
# When both text and tool_calls exist, tool_use should be detected and tool name captured
(
block_type,
content_block_start,
) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block(
choices=choices
)
assert block_type == "tool_use"
assert content_block_start["name"] == "Bash"
assert content_block_start["id"] == "toolu_bdrk_013xRVejhv3ybmLEGCoZib2b"