test(responses): cover empty-output rebuild on stream completion

Cover _collect_response_from_stream and _collect_response_from_stream_async
for #41014: when the terminal response carries an empty output array but
output_item.done events streamed the full answer, the bridge rebuilds the
output from the streamed items.

Includes the rebuild path, the non-empty-output short-circuit and the
no-streamed-items fallback for the sync collector, plus a rebuild case for
the async collector.

Signed-off-by: rain <1504569896@qq.com>
This commit is contained in:
rain 2026-09-20 22:59:34 +00:00 • committed by Rainmemery
parent af1540ac2b
commit 6e723882cc

View file

@ -335,3 +335,131 @@ async def test_acompletion_keeps_provider_native_model_id_through_responses(
handed_model = responses_call.call_args.kwargs["model"]
assert _upstream_model_for(handed_model, custom_llm_provider) == expected_upstream_model
# ------------------------------------------------------------------------
# #41009/#41014: rebuild empty streamed output from output_item.done events
# ------------------------------------------------------------------------
class _FakeCompletedResponse:
response = {
"id": "resp_41014",
"object": "response",
"created_at": 0,
"status": "completed",
"model": "gpt-5.4",
"output": [],
}
class _FakeStreamIter:
def __init__(self, streamed_items):
self.completed_response = _FakeCompletedResponse()
self._hidden_params = None
self._items = streamed_items
def __iter__(self):
return iter([])
def get_streamed_output_items(self):
return self._items
class _FakeAsyncStreamIter(_FakeStreamIter):
def __aiter__(self):
return self
async def __anext__(self):
raise StopAsyncIteration
def test_collect_response_from_stream_rebuilds_empty_output_from_streamed_items():
from litellm.completion_extras.litellm_responses_transformation.handler import (
ResponsesToCompletionBridgeHandler,
)
bridge = ResponsesToCompletionBridgeHandler()
streamed_items = [
{
"type": "message",
"id": "msg_1",
"role": "assistant",
"content": [{"type": "output_text", "text": "Hello", "annotations": []}],
}
]
response = bridge._collect_response_from_stream(_FakeStreamIter(streamed_items))
assert len(response.output) == 1
assert response.output[0]["id"] == "msg_1"
def test_collect_response_from_stream_keeps_nonempty_output_untouched():
from litellm.completion_extras.litellm_responses_transformation.handler import (
ResponsesToCompletionBridgeHandler,
)
bridge = ResponsesToCompletionBridgeHandler()
class _NonEmptyCompleted:
response = {
"id": "resp_nonempty",
"object": "response",
"created_at": 0,
"status": "completed",
"model": "gpt-5.4",
"output": [{"type": "message", "id": "msg_0"}],
}
class _StreamNoItems:
completed_response = _NonEmptyCompleted()
_hidden_params = None
def __iter__(self):
return iter([])
response = bridge._collect_response_from_stream(_StreamNoItems())
assert len(response.output) == 1
assert response.output[0]["id"] == "msg_0"
def test_collect_response_from_stream_without_streamed_items_keeps_empty_output():
from litellm.completion_extras.litellm_responses_transformation.handler import (
ResponsesToCompletionBridgeHandler,
)
bridge = ResponsesToCompletionBridgeHandler()
class _NoStreamedItems:
completed_response = _FakeCompletedResponse()
_hidden_params = None
def __iter__(self):
return iter([])
response = bridge._collect_response_from_stream(_NoStreamedItems())
assert len(response.output) == 0
def test_collect_response_from_stream_async_rebuilds_empty_output():
from litellm.completion_extras.litellm_responses_transformation.handler import (
ResponsesToCompletionBridgeHandler,
)
from litellm.types.utils import ResponsesAPIResponse
import asyncio
async def run():
bridge = ResponsesToCompletionBridgeHandler()
streamed_items = [
{
"type": "message",
"id": "msg_async_1",
"role": "assistant",
"content": [{"type": "output_text", "text": "Hi", "annotations": []}],
}
]
response = await bridge._collect_response_from_stream_async(_FakeAsyncStreamIter(streamed_items))
assert isinstance(response, ResponsesAPIResponse)
assert len(response.output) == 1
assert response.output[0]["id"] == "msg_async_1"
asyncio.run(run())