mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
Merge remote-tracking branch 'origin/main' into litellm_streaming_buffer_release_on_scan
This commit is contained in:
commit
6faeb19c0c
3 changed files with 51 additions and 2 deletions
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
|
|
@ -4,7 +4,7 @@
|
|||
/ui/nginx.conf
|
||||
/ui/litellm-dashboard/src/lib/http/schema.d.ts
|
||||
/ui/litellm-dashboard/tsconfig.tsbuildinfo
|
||||
/model_prices_and_context_window.json @mateo-berri
|
||||
/litellm/model_prices_and_context_window_backup.json @mateo-berri
|
||||
/model_prices_and_context_window.json @mateo-berri @ryan-crabbe-berri @kerry-berri
|
||||
/litellm/model_prices_and_context_window_backup.json @mateo-berri @ryan-crabbe-berri @kerry-berri
|
||||
/litellm-proxy-extras/litellm_proxy_extras/migrations/ @yuneng-berri @ryan-crabbe-berri
|
||||
/.github/CODEOWNERS @yuneng-berri
|
||||
|
|
|
|||
|
|
@ -176,6 +176,8 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
|||
return {**item_kwargs, "name": tool_name, **namespace_kwargs}
|
||||
|
||||
def _is_reasoning_end(self, chunk):
|
||||
if not chunk.choices:
|
||||
return False
|
||||
delta: Final = chunk.choices[0].delta
|
||||
|
||||
# if this indicates reasoning content, don't consider reasoning ended
|
||||
|
|
@ -897,6 +899,8 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
|||
# Change: Never return a value, just enqueue output item events
|
||||
if self.sent_output_item_added_event:
|
||||
return
|
||||
if not chunk.choices:
|
||||
return
|
||||
delta: Final = chunk.choices[0].delta
|
||||
|
||||
self._sequence_number += 1
|
||||
|
|
@ -1224,6 +1228,8 @@ class LiteLLMCompletionStreamingIterator(ResponsesAPIStreamingIterator):
|
|||
|
||||
It's unclear how users expect litellm to translate multiple-choices-per-chunk to the responses API output.
|
||||
"""
|
||||
if not choices:
|
||||
return ""
|
||||
choice: Final = choices[0]
|
||||
chat_completion_delta: Final[ChatCompletionDelta] = choice.delta
|
||||
return chat_completion_delta.content or ""
|
||||
|
|
|
|||
|
|
@ -752,6 +752,49 @@ def test_completed_event_restores_usage_hidden_by_stream_options_none():
|
|||
assert completed.response.usage.output_tokens == 5
|
||||
|
||||
|
||||
def _empty_choices_chunk(usage: Usage | None = None) -> ModelResponseStream:
|
||||
return ModelResponseStream(id=CHAT_COMPLETION_ID, model="claude-haiku-4-5", choices=[], usage=usage)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_leading_empty_choices_chunk_does_not_kill_the_stream():
|
||||
"""
|
||||
Azure leads some streams with a `prompt_filter_results` chunk whose `choices` is empty.
|
||||
The bridge used to index `choices[0]` on it and die before the first token.
|
||||
"""
|
||||
iterator = _build_iterator([_empty_choices_chunk(), _chunk("Hello"), _chunk("!", finish_reason="stop")])
|
||||
|
||||
events = [event async for event in iterator]
|
||||
|
||||
event_types = [getattr(event, "type", None) for event in events]
|
||||
assert event_types.count(ResponsesAPIStreamEvents.OUTPUT_ITEM_ADDED) == 1
|
||||
assert "".join(event.delta for event in events if event.type == ResponsesAPIStreamEvents.OUTPUT_TEXT_DELTA) == "Hello!"
|
||||
assert event_types[-1] == ResponsesAPIStreamEvents.RESPONSE_COMPLETED
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_trailing_empty_choices_usage_chunk_reaches_response_completed():
|
||||
"""
|
||||
With `stream_options.include_usage` (which the bridge always sets) the last upstream chunk
|
||||
carries only usage and an empty `choices`. It must not crash the stream, and its usage must
|
||||
still land on `response.completed`.
|
||||
"""
|
||||
usage: Final = Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15)
|
||||
iterator = _build_iterator([_chunk("Hello"), _chunk("", finish_reason="stop"), _empty_choices_chunk(usage)])
|
||||
|
||||
events = [event async for event in iterator]
|
||||
|
||||
completed = next(
|
||||
event for event in events if getattr(event, "type", None) == ResponsesAPIStreamEvents.RESPONSE_COMPLETED
|
||||
)
|
||||
assert completed.response.usage.input_tokens == 10
|
||||
assert completed.response.usage.output_tokens == 5
|
||||
|
||||
|
||||
def test_is_reasoning_end_ignores_empty_choices_chunk():
|
||||
assert _build_iterator([])._is_reasoning_end(_empty_choices_chunk()) is False
|
||||
|
||||
|
||||
def test_object_tool_call_arguments_stream_as_valid_json():
|
||||
"""A provider that sends decoded object arguments must still stream valid JSON.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue