From 0716a4cf15b06059ccef9f9d44b6293706939d34 Mon Sep 17 00:00:00 2001 From: Felipe Rodrigues Gare Carnielli Date: Fri, 22 May 2026 00:55:38 -0300 Subject: [PATCH] fix(anthropic): handle empty streaming tool calls --- .../adapters/transformation.py | 2 +- ...al_pass_through_adapters_transformation.py | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 0e198daf089e..51a1e739a0f3 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -1476,7 +1476,7 @@ def _translate_streaming_openai_chunk_to_anthropic( for choice in choices: if choice.delta.content is not None and len(choice.delta.content) > 0: text += choice.delta.content - if choice.delta.tool_calls is not None: + if choice.delta.tool_calls: partial_json = "" for tool in choice.delta.tool_calls: if ( diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 11465e6f7180..44530fecebdf 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -1203,6 +1203,51 @@ def test_streaming_chunk_with_both_text_and_tool_calls_issue_18238(): assert content_block_start["id"] == "toolu_bdrk_013xRVejhv3ybmLEGCoZib2b" +def test_streaming_chunk_with_text_and_empty_tool_calls_returns_text_delta(): + """ + Some OpenAI-compatible providers emit `tool_calls: []` on regular text chunks. + + Empty tool_calls should be treated as no tool call so the Anthropic adapter + does not shadow text with an empty input_json_delta. + """ + choices = [ + StreamingChoices( + finish_reason=None, + index=0, + delta=Delta( + provider_specific_fields=None, + content="Hello from vLLM", + role="assistant", + function_call=None, + tool_calls=[], + audio=None, + ), + logprobs=None, + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + + ( + type_of_content, + content_block_delta, + ) = adapter._translate_streaming_openai_chunk_to_anthropic(choices=choices) + + assert type_of_content == "text_delta" + assert content_block_delta["type"] == "text_delta" + assert content_block_delta["text"] == "Hello from vLLM" + + ( + block_type, + content_block_start, + ) = adapter._translate_streaming_openai_chunk_to_anthropic_content_block( + choices=choices + ) + + assert block_type == "text" + assert content_block_start == {"type": "text", "text": ""} + + # ============================================================================ # Cache Control Transformation Tests # ============================================================================