From f0fbcf78671aeaa7680c5da7cd25d459aed81f92 Mon Sep 17 00:00:00 2001 From: Himanshu Joshi Date: Sun, 27 Sep 2026 00:39:57 +0530 Subject: [PATCH 1/2] fix(streaming): guard against empty choices in _simple_text_part When upstream providers emit trailing usage chunks with choices: [], _stream_choice_delta returns an empty tuple, causing an IndexError on deltas[0]. Added guards to return None when choices or deltas is empty. --- litellm/main.py | 4 ++++ tests/local_testing/test_stream_chunk_builder.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/litellm/main.py b/litellm/main.py index 12854db15d0..5b5e43258fd 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -8952,7 +8952,11 @@ def _delta_carries_more_than_text(delta: Mapping[str, object]) -> bool: def _simple_text_part(choices: Sequence[object]) -> str | None: + if not choices: + return None deltas: Final = tuple(_stream_choice_delta(choice) for choice in choices) + if not deltas: + return None if any(_delta_carries_more_than_text(delta) for delta in deltas): return None content: Final = deltas[0].get("content") diff --git a/tests/local_testing/test_stream_chunk_builder.py b/tests/local_testing/test_stream_chunk_builder.py index 6d62dd52b89..660586777fb 100644 --- a/tests/local_testing/test_stream_chunk_builder.py +++ b/tests/local_testing/test_stream_chunk_builder.py @@ -895,3 +895,19 @@ def test_grok_bug(load_env): litellm.set_verbose = True _, LLAMA3_3 = load_env execute_completion(LLAMA3_3) + + +def test_stream_chunk_builder_empty_choices_guard(): + """ + Test that stream_chunk_builder does not crash with IndexError when + streaming chunks contain empty choices lists (e.g. trailing usage chunks). + """ + chunks = [ + {"id": "chat-test-1", "model": "gpt-4o", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "Hello"}}]}, + {"id": "chat-test-1", "model": "gpt-4o", "choices": [{"index": 0, "delta": {"content": " world"}}]}, + {"id": "chat-test-1", "model": "gpt-4o", "choices": [], "usage": {"prompt_tokens": 10, "completion_tokens": 2, "total_tokens": 12}}, + ] + res = litellm.stream_chunk_builder(chunks) + assert res is not None + assert res["choices"][0]["message"]["content"] == "Hello world" + From 3d6f54e431fc4d2e10b116856fe59374690865b7 Mon Sep 17 00:00:00 2001 From: Himanshu Joshi Date: Sun, 27 Sep 2026 00:55:28 +0530 Subject: [PATCH 2/2] test(streaming): exercise _simple_text_part empty sequence guards --- tests/local_testing/test_stream_chunk_builder.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/local_testing/test_stream_chunk_builder.py b/tests/local_testing/test_stream_chunk_builder.py index 660586777fb..81c8ef2295a 100644 --- a/tests/local_testing/test_stream_chunk_builder.py +++ b/tests/local_testing/test_stream_chunk_builder.py @@ -902,6 +902,12 @@ def test_stream_chunk_builder_empty_choices_guard(): Test that stream_chunk_builder does not crash with IndexError when streaming chunks contain empty choices lists (e.g. trailing usage chunks). """ + from litellm.main import _simple_text_part + + # Direct unit test exercising empty-sequence guards in _simple_text_part + assert _simple_text_part([]) is None + assert _simple_text_part([{"delta": {}}]) == "" + chunks = [ {"id": "chat-test-1", "model": "gpt-4o", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "Hello"}}]}, {"id": "chat-test-1", "model": "gpt-4o", "choices": [{"index": 0, "delta": {"content": " world"}}]}, @@ -911,3 +917,4 @@ def test_stream_chunk_builder_empty_choices_guard(): assert res is not None assert res["choices"][0]["message"]["content"] == "Hello world" +