mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
fix(streaming): exempt baseten/vllm EOF and emit mock finish_reason
Providers that never record stream finish_reason keep successful EOF. Mock streams now send a terminal stop chunk so openai-compatible exhaustion still works. Drop helper/test docstrings per CLAUDE.md.
This commit is contained in:
parent
a97ea2c5d0
commit
8c8abcd94d
3 changed files with 221 additions and 437 deletions
|
|
@ -1859,18 +1859,15 @@ class CustomStreamWrapper:
|
|||
if self.logging_obj._is_sync_litellm_request(litellm_params):
|
||||
self.logging_obj.success_handler(processed_chunk, None, None, cache_hit)
|
||||
|
||||
_PROVIDERS_WITHOUT_STREAM_FINISH_REASON = frozenset({"baseten", "vllm"})
|
||||
|
||||
def _has_provider_finish_reason(self) -> bool:
|
||||
"""True when the provider (or an intermittent mid-stream reason) supplied a terminal finish_reason."""
|
||||
return self.received_finish_reason is not None or self.intermittent_finish_reason is not None
|
||||
|
||||
def _should_require_provider_finish_reason(self) -> bool:
|
||||
return self.custom_llm_provider not in self._PROVIDERS_WITHOUT_STREAM_FINISH_REASON
|
||||
|
||||
def _raise_incomplete_stream_without_finish_reason(self) -> "NoReturn":
|
||||
"""
|
||||
OpenAI/Azure-compatible chat streams that end without any provider finish_reason
|
||||
must not be labeled as a successful completion (finish_reason="stop"). Raise a
|
||||
MidStreamFallbackError so callers can retry/fallback, while retaining partial
|
||||
content via generated_content / failure usage recovery.
|
||||
"""
|
||||
message = (
|
||||
"Stream ended without a finish_reason from the provider. "
|
||||
"Partial content was received but the response was not successfully completed."
|
||||
|
|
@ -2092,7 +2089,7 @@ class CustomStreamWrapper:
|
|||
self._restore_consumer_correlation_context()
|
||||
raise # Re-raise StopIteration
|
||||
else:
|
||||
if not self._has_provider_finish_reason():
|
||||
if self._should_require_provider_finish_reason() and not self._has_provider_finish_reason():
|
||||
self._raise_incomplete_stream_without_finish_reason()
|
||||
self.sent_last_chunk = True
|
||||
processed_chunk: Final = self.finish_reason_handler()
|
||||
|
|
@ -2356,7 +2353,7 @@ class CustomStreamWrapper:
|
|||
self._restore_consumer_correlation_context()
|
||||
raise StopAsyncIteration # Re-raise StopIteration
|
||||
else:
|
||||
if not self._has_provider_finish_reason():
|
||||
if self._should_require_provider_finish_reason() and not self._has_provider_finish_reason():
|
||||
self._raise_incomplete_stream_without_finish_reason()
|
||||
self.sent_last_chunk = True
|
||||
processed_chunk: Final = self.finish_reason_handler()
|
||||
|
|
|
|||
|
|
@ -6975,6 +6975,7 @@ def mock_completion_streaming_obj(model_response, mock_response, model, n: int |
|
|||
completion_obj = Delta(role="assistant", content=mock_response[i : i + 3])
|
||||
if n is None:
|
||||
model_response.choices[0].delta = completion_obj
|
||||
model_response.choices[0].finish_reason = None
|
||||
else:
|
||||
_all_choices = []
|
||||
for j in range(n):
|
||||
|
|
@ -6985,6 +6986,19 @@ def mock_completion_streaming_obj(model_response, mock_response, model, n: int |
|
|||
_all_choices.append(_streaming_choice)
|
||||
model_response.choices = _all_choices
|
||||
yield model_response
|
||||
if n is None:
|
||||
model_response.choices[0].delta = Delta(role="assistant", content=None)
|
||||
model_response.choices[0].finish_reason = "stop"
|
||||
else:
|
||||
model_response.choices = [
|
||||
litellm.utils.StreamingChoices(
|
||||
index=j,
|
||||
delta=litellm.utils.Delta(role="assistant", content=None),
|
||||
finish_reason="stop",
|
||||
)
|
||||
for j in range(n)
|
||||
]
|
||||
yield model_response
|
||||
|
||||
|
||||
async def async_mock_completion_streaming_obj(
|
||||
|
|
@ -7002,6 +7016,7 @@ async def async_mock_completion_streaming_obj(
|
|||
completion_obj = Delta(role="assistant", content=mock_response[i : i + 3])
|
||||
if n is None:
|
||||
model_response.choices[0].delta = completion_obj
|
||||
model_response.choices[0].finish_reason = None
|
||||
else:
|
||||
_all_choices = []
|
||||
for j in range(n):
|
||||
|
|
@ -7012,6 +7027,19 @@ async def async_mock_completion_streaming_obj(
|
|||
_all_choices.append(_streaming_choice)
|
||||
model_response.choices = _all_choices
|
||||
yield model_response
|
||||
if n is None:
|
||||
model_response.choices[0].delta = Delta(role="assistant", content=None)
|
||||
model_response.choices[0].finish_reason = "stop"
|
||||
else:
|
||||
model_response.choices = [
|
||||
litellm.utils.StreamingChoices(
|
||||
index=j,
|
||||
delta=litellm.utils.Delta(role="assistant", content=None),
|
||||
finish_reason="stop",
|
||||
)
|
||||
for j in range(n)
|
||||
]
|
||||
yield model_response
|
||||
|
||||
|
||||
########## Reading Config File ############################
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue