From 924a245eddc7d5b475283154d562d32a0f6e497c Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Mon, 29 Jun 2026 15:13:52 +0530 Subject: [PATCH] =?UTF-8?q?revert:=20remove=20empty-choices=20chunk=20filt?= =?UTF-8?q?er=20=E2=80=94=20drops=20usage-bearing=20final=20SSE=20chunk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The _has_empty_choices filter stripped the OpenAI usage-bearing trailing chunk {"choices":[]"usage":{...}} which caused stream_chunk_builder to fall back to estimated token counts instead of real provider usage. The try/except around stream_chunk_builder already handles any IndexError on empty choices safely. --- litellm/proxy/common_request_processing.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index fe75ad79872..ab16c0e9acc 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -2465,17 +2465,6 @@ class ProxyBaseLLMRequestProcessing: return elif not isinstance(first_chunk, str) and not hasattr(first_chunk, "choices"): return - # Strip trailing usage-only / empty-choices chunks (e.g. OpenAI's final SSE - # `data: {"choices":[],"usage":{...}}`) so stream_chunk_builder can safely - # inspect the last chunk's finish_reason without an IndexError. - def _has_empty_choices(c: Any) -> bool: - if isinstance(c, dict): - return c.get("choices") == [] - return bool(hasattr(c, "choices") and getattr(c, "choices") == []) - - chunks = [c for c in chunks if not _has_empty_choices(c)] - if not chunks: - return # Optimization, not a correctness guard: dispatch_success_handlers is the # authoritative de-dup via has_dispatched_final_stream_success. This just # skips the stream_chunk_builder assembly when completion already logged.