Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,8 @@ def _get_delta_string_from_streaming_choices(self, choices: List[StreamingChoice

It's unclear how users expect litellm to translate multiple-choices-per-chunk to the responses API output.
"""
if not choices:
return ""
choice = choices[0]
chat_completion_delta: ChatCompletionDelta = choice.delta
return chat_completion_delta.content or ""
Expand Down
33 changes: 33 additions & 0 deletions tests/test_litellm/responses/test_streaming_empty_choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Test for the empty-choices guard in the Responses API streaming iterator.

Providers such as DeepSeek emit a terminal streaming chunk with "choices": []
(finish/usage chunk). _get_delta_string_from_streaming_choices must return ""
for it instead of raising IndexError and killing the /v1/responses stream.
"""

from litellm.responses.litellm_completion_transformation.streaming_iterator import (
LiteLLMCompletionStreamingIterator,
)
from litellm.types.utils import Delta, StreamingChoices


class TestEmptyChoicesGuard:
def test_empty_choices_returns_empty_string(self):
"""A terminal chunk with choices: [] must not raise IndexError."""
result = LiteLLMCompletionStreamingIterator._get_delta_string_from_streaming_choices(
None, []
)
assert result == ""

def test_non_empty_choices_returns_delta_content(self):
"""The normal path is unchanged: first choice's delta content is returned."""
choice = StreamingChoices(
index=0,
delta=Delta(content="hello", role="assistant"),
finish_reason=None,
)
result = LiteLLMCompletionStreamingIterator._get_delta_string_from_streaming_choices(
None, [choice]
)
assert result == "hello"
Loading