diff --git a/src/openai/lib/streaming/responses/_responses.py b/src/openai/lib/streaming/responses/_responses.py index 6975a9260d..513ca5dbb5 100644 --- a/src/openai/lib/streaming/responses/_responses.py +++ b/src/openai/lib/streaming/responses/_responses.py @@ -328,18 +328,20 @@ 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 = event.item + 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() - ) + construct_type_unchecked(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..8f5923c580 100644 --- a/tests/lib/responses/test_responses.py +++ b/tests/lib/responses/test_responses.py @@ -8,6 +8,10 @@ from openai import OpenAI, AsyncOpenAI from openai._utils import assert_signatures_in_sync +from openai._types import omit +from openai._models import construct_type_unchecked +from openai.types.responses import ResponseStreamEvent +from openai.lib.streaming.responses._responses import ResponseStreamState from ...conftest import base_url from ..snapshots import make_snapshot_request @@ -61,3 +65,40 @@ def test_parse_method_definition_in_sync(sync: bool, client: OpenAI, async_clien checking_client.responses.parse, exclude_params={"tools"}, ) + + +def test_stream_accumulator_ignores_missing_output_item() -> None: + state = ResponseStreamState(input_tools=omit, text_format=omit) + + state.handle_event( + construct_type_unchecked( + type_=ResponseStreamEvent, + value={ + "type": "response.created", + "sequence_number": 0, + "response": { + "id": "resp_123", + "object": "response", + "created_at": 0, + "status": "in_progress", + "model": "gpt-4o-mini", + "output": [], + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + }, + }, + ) + ) + + event = construct_type_unchecked( + type_=ResponseStreamEvent, + value={ + "type": "response.output_item.added", + "sequence_number": 1, + "output_index": 0, + "item": None, + }, + ) + + assert state.handle_event(event) == [event]