Skip to content

fix(responses): guard empty choices in streaming delta extraction - #32519

Open
ansulev wants to merge 2 commits into
BerriAI:litellm_oss_stagingfrom
ansulev:litellm_fix_responses_empty_choices
Open

fix(responses): guard empty choices in streaming delta extraction#32519
ansulev wants to merge 2 commits into
BerriAI:litellm_oss_stagingfrom
ansulev:litellm_fix_responses_empty_choices

Conversation

@ansulev

@ansulev ansulev commented Jul 8, 2026

Copy link
Copy Markdown

What

_get_delta_string_from_streaming_choices in
litellm/responses/litellm_completion_transformation/streaming_iterator.py indexes choices[0]
without checking for an empty list.

Repro

Proxy config bridging /v1/responses to an OpenAI-compatible chat-completions backend
(use_chat_completions_api: true). DeepSeek (tested: deepseek-v4-flash behind a custom
api_base) emits a terminal streaming chunk with "choices": []. Result:

File ".../streaming_iterator.py", in _get_delta_string_from_streaming_choices
    choice = choices[0]
IndexError: list index out of range

The stream dies with data: {"error": {"message": "list index out of range", ... "code": "500"}}
after the content has already streamed.

Fix

Return "" when choices is empty — the same chunk.choices and ... guard every other access in
this file already uses (annotation, reasoning and tool-call paths). Unit test covers both branches.

Tested locally on 1.91.0: with the guard, the same request terminates cleanly with
response.completed + data: [DONE] and usage intact.

Note

Supersedes #32468 (same fix, was wrongly targeted at main from a fork; this one targets
litellm_oss_staging per the source-branch policy, plus the missing test coverage).

ansulev added 2 commits July 8, 2026 19:58
_get_delta_string_from_streaming_choices indexes choices[0] without
checking for an empty list. Providers such as DeepSeek emit a terminal
streaming chunk with "choices": [] (finish/usage chunk), which raises
IndexError and kills the /v1/responses stream mid-response when
bridging to a chat-completions backend.

Every other access in this file already guards with `chunk.choices and ...`;
this applies the same guard here.
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Guards _get_delta_string_from_streaming_choices against terminal streaming chunks where choices is an empty list (e.g. DeepSeek usage/finish chunks), preventing the IndexError that previously killed the /v1/responses stream after content had already been sent.

  • Adds a if not choices: return \"\" early-return at the top of _get_delta_string_from_streaming_choices, matching the chunk.choices and … pattern already used by the annotation, reasoning, and tool-call paths in the same file.
  • Adds a new mock-only unit test (test_streaming_empty_choices.py) that covers both the empty-choices branch and the existing happy path.

Confidence Score: 5/5

The change is a minimal, isolated two-line guard that closes an unchecked list index and is accompanied by a focused unit test; no existing behaviour is altered.

Both files touch only the streaming iterator's delta-extraction helper. The fix is a single early-return consistent with every other guard already present in the same method's callers, and the new test exercises both branches without making any real network calls.

No files require special attention.

Important Files Changed

Filename Overview
litellm/responses/litellm_completion_transformation/streaming_iterator.py Adds a two-line early-return guard for empty choices in _get_delta_string_from_streaming_choices, consistent with how all other access sites in the file already guard against empty choices.
tests/test_litellm/responses/test_streaming_empty_choices.py New unit test covering both the empty-choices path (returns "") and the normal path (returns delta content); no real network calls, uses inline mock objects only.

Reviews (1): Last reviewed commit: "test(responses): cover empty-choices gua..." | Re-trigger Greptile

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants