mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
Fix AnthropicStreamWrapper: correct content_block_start type + suppress empty input_json_delta
- Peek at first chunk before emitting content_block_start so the block type (thinking, tool_use, text) is correct instead of always hardcoding text - Suppress spurious empty input_json_delta that was produced when translating the OpenAI-style tool_use start chunk (arguments="") and being held as holding_chunk - Update test_parallel_tool_calls.py to reflect the new correct behaviour (no initial empty text block before tool_use blocks); the TODO comments in those tests explicitly anticipated this fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
df7148036a
commit
f92c73dbc7
2 changed files with 26 additions and 23 deletions
|
|
@ -115,10 +115,23 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper):
|
|||
"AnthropicStreamWrapper: stream yielded None as first chunk"
|
||||
)
|
||||
self._should_start_new_content_block(first_chunk)
|
||||
self.holding_chunk = LiteLLMAnthropicMessagesAdapter().translate_streaming_openai_response_to_anthropic(
|
||||
_translated = LiteLLMAnthropicMessagesAdapter().translate_streaming_openai_response_to_anthropic(
|
||||
response=first_chunk,
|
||||
current_content_block_index=self.current_content_block_index,
|
||||
)
|
||||
# Don't hold an empty input_json_delta: OpenAI-style tool_use
|
||||
# start chunks carry arguments="" which translates to a
|
||||
# partial_json="" delta that should not be emitted.
|
||||
if (
|
||||
isinstance(_translated, dict)
|
||||
and _translated.get("type") == "content_block_delta"
|
||||
and isinstance(_translated.get("delta"), dict)
|
||||
and _translated["delta"].get("type") == "input_json_delta"
|
||||
and not _translated["delta"].get("partial_json")
|
||||
):
|
||||
self.holding_chunk = None
|
||||
else:
|
||||
self.holding_chunk = _translated
|
||||
self.chunk_queue.append(
|
||||
{
|
||||
"type": "content_block_start",
|
||||
|
|
@ -273,10 +286,21 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper):
|
|||
"AnthropicStreamWrapper: stream yielded None as first chunk"
|
||||
)
|
||||
self._should_start_new_content_block(first_chunk)
|
||||
self.holding_chunk = LiteLLMAnthropicMessagesAdapter().translate_streaming_openai_response_to_anthropic(
|
||||
_translated = LiteLLMAnthropicMessagesAdapter().translate_streaming_openai_response_to_anthropic(
|
||||
response=first_chunk,
|
||||
current_content_block_index=self.current_content_block_index,
|
||||
)
|
||||
# Don't hold an empty input_json_delta (see __next__ for explanation)
|
||||
if (
|
||||
isinstance(_translated, dict)
|
||||
and _translated.get("type") == "content_block_delta"
|
||||
and isinstance(_translated.get("delta"), dict)
|
||||
and _translated["delta"].get("type") == "input_json_delta"
|
||||
and not _translated["delta"].get("partial_json")
|
||||
):
|
||||
self.holding_chunk = None
|
||||
else:
|
||||
self.holding_chunk = _translated
|
||||
self.chunk_queue.append(
|
||||
{
|
||||
"type": "content_block_start",
|
||||
|
|
|
|||
|
|
@ -134,13 +134,6 @@ def test_anthropic_stream_wrapper_single_tool_call():
|
|||
# Verify the expected sequence of chunk types
|
||||
expected_types = [
|
||||
"message_start", # Initial message start
|
||||
# TODO: for future contributors: if the initial content_block_start
|
||||
# respects the upstream's starting chunk, the initial empty text block
|
||||
# should be removed (and this test should be updated accordingly)
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Initial empty text block start
|
||||
"content_block_stop", # End of empty text block
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Start of first tool_use content block
|
||||
"content_block_delta", # {"city":
|
||||
"content_block_delta", # "NY"}
|
||||
|
|
@ -196,13 +189,6 @@ def test_anthropic_stream_wrapper_back_to_back_tool_calls():
|
|||
# Verify the expected sequence of chunk types
|
||||
expected_types = [
|
||||
"message_start", # Initial message start
|
||||
# TODO: for future contributors: if the initial content_block_start
|
||||
# respects the upstream's starting chunk, the initial empty text block
|
||||
# should be removed (and this test should be updated accordingly)
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Initial empty text block start
|
||||
"content_block_stop", # End of empty text block
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Start of first tool_use content block
|
||||
"content_block_delta", # {"city":
|
||||
"content_block_delta", # "NY"}
|
||||
|
|
@ -267,13 +253,6 @@ def test_anthropic_stream_wrapper_interleaved_tool_calls_and_text():
|
|||
# Verify the expected sequence of chunk types
|
||||
expected_types = [
|
||||
"message_start", # Initial message start
|
||||
# TODO: for future contributors: if the initial content_block_start
|
||||
# respects the upstream's starting chunk, the initial empty text block
|
||||
# should be removed (and this test should be updated accordingly)
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Initial empty text block start
|
||||
"content_block_stop", # End of empty text block
|
||||
# ---------------------------------------------------------------------
|
||||
"content_block_start", # Start of first tool_use content block
|
||||
"content_block_delta", # {"city":
|
||||
"content_block_delta", # "NY"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue