From 97ba69e921a5cbd8d696994322db5fe0feaeea56 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Sat, 13 Jun 2026 08:29:13 +0800 Subject: [PATCH] fix: ignore empty stream output items --- .../lib/streaming/responses/_responses.py | 14 ++++--- tests/lib/responses/test_responses.py | 41 +++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/openai/lib/streaming/responses/_responses.py b/src/openai/lib/streaming/responses/_responses.py index 6975a9260d..74618aa6d3 100644 --- a/src/openai/lib/streaming/responses/_responses.py +++ b/src/openai/lib/streaming/responses/_responses.py @@ -328,18 +328,22 @@ def accumulate_event(self, event: RawResponseStreamEvent) -> ParsedResponseSnaps return self._create_initial_response(event) if event.type == "response.output_item.added": - if event.item.type == "function_call": + item = getattr(event, "item", None) + if item is None: + return snapshot + + if item.type == "function_call": snapshot.output.append( construct_type_unchecked( - type_=cast(Any, ParsedResponseFunctionToolCall), value=event.item.to_dict() + type_=cast(Any, ParsedResponseFunctionToolCall), value=item.to_dict() ) ) - elif event.item.type == "message": + elif item.type == "message": snapshot.output.append( - construct_type_unchecked(type_=cast(Any, ParsedResponseOutputMessage), value=event.item.to_dict()) + construct_type_unchecked(type_=cast(Any, ParsedResponseOutputMessage), value=item.to_dict()) ) else: - snapshot.output.append(event.item) + snapshot.output.append(item) elif event.type == "response.content_part.added": output = snapshot.output[event.output_index] if output.type == "message": diff --git a/tests/lib/responses/test_responses.py b/tests/lib/responses/test_responses.py index 8e5f16df95..d267183985 100644 --- a/tests/lib/responses/test_responses.py +++ b/tests/lib/responses/test_responses.py @@ -8,6 +8,9 @@ from openai import OpenAI, AsyncOpenAI from openai._utils import assert_signatures_in_sync +from openai._models import construct_type_unchecked +from openai.types.responses import ResponseStreamEvent +from openai.lib.streaming.responses import ResponseStreamState from ...conftest import base_url from ..snapshots import make_snapshot_request @@ -61,3 +64,41 @@ def test_parse_method_definition_in_sync(sync: bool, client: OpenAI, async_clien checking_client.responses.parse, exclude_params={"tools"}, ) + + +def test_response_stream_state_ignores_output_item_added_without_item() -> None: + state = ResponseStreamState(text_format=object, input_tools=[]) + response = { + "id": "resp_123", + "object": "response", + "created_at": 0, + "model": "gpt-4o-mini", + "output": [], + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + } + state.handle_event( + construct_type_unchecked( + type_=ResponseStreamEvent, + value={ + "type": "response.created", + "sequence_number": 0, + "response": response, + }, + ) + ) + + snapshot = state.accumulate_event( + construct_type_unchecked( + type_=ResponseStreamEvent, + value={ + "type": "response.output_item.added", + "sequence_number": 1, + "output_index": 0, + "item": None, + }, + ) + ) + + assert snapshot.output == []