"""Streaming events tests for Response API. Tests for streaming event validation including: - Zero-based output_index for content - OutputItemDone event emission and output array construction - Reasoning content with proper output_index Source: Migrated from e2e_response_api/features/test_streaming_events.py """ from __future__ import annotations import logging import pytest logger = logging.getLogger(__name__) # ============================================================================= # Local Backend Tests (gRPC with Qwen model) # ============================================================================= @pytest.mark.e2e @pytest.mark.model("qwen-14b") @pytest.mark.gateway( extra_args=["--tool-call-parser", "qwen", "--history-backend", "memory"] ) @pytest.mark.parametrize("setup_backend", ["grpc"], indirect=True) class TestStreamingEventsLocal: """Streaming event tests against local gRPC backend.""" def test_output_item_event_emitted(self, setup_backend): """Test that output_index is zero-based in streaming responses. Verifies that the first output item has output_index: 0. """ _, model, client, gateway = setup_backend resp = client.responses.create( model=model, input="Count from 1 to 3", stream=True, max_output_tokens=50, ) events = list(resp) assert len(events) > 0 # Find output_item.added events output_item_added_events = [ event for event in events if event.type == "response.output_item.added" ] assert len(output_item_added_events) > 0, "Should have output_item.added events" # Verify first output item has output_index: 0 first_item_event = output_item_added_events[0] assert first_item_event.item is not None assert first_item_event.output_index is not None assert ( first_item_event.output_index == 0 ), "First output item must have output_index: 0 (zero-based indexing)" # Verify subsequent items increment correctly for i, event in enumerate(output_item_added_events): assert ( event.output_index == i ), f"Output item {i} should have output_index: {i}" # Verify output_item.done event exists output_item_done_events = [ event for event in events if event.type == "response.output_item.done" ] assert len(output_item_done_events) > 0 # Verify output_item.done event structure for event in output_item_done_events: assert event.item is not None assert event.output_index is not None assert event.item.type is not None # Find response.completed event completed_events = [ event for event in events if event.type == "response.completed" ] assert len(completed_events) == 1, "Should have exactly one completed event" # Verify output array exists and contains items completed_event = completed_events[0] assert completed_event.response.output is not None output_array = completed_event.response.output assert isinstance(output_array, list) assert len(output_array) > 0, "Output array should contain at least one item" # Verify each item in output array has proper structure for item in output_array: assert item.type is not None # Verify output_item.added events match items in final output array output_item_added_events = [ event for event in events if event.type == "response.output_item.added" ] assert len(output_item_added_events) == len( output_array ), "Number of output_item.added events should match output array length" # ============================================================================= # Local Backend Tests (gRPC with Harmony/Reasoning model) # ============================================================================= @pytest.mark.e2e @pytest.mark.model("gpt-oss") @pytest.mark.gateway( extra_args=["--reasoning-parser=gpt-oss", "--history-backend", "memory"] ) @pytest.mark.parametrize("setup_backend", ["grpc"], indirect=True) class TestStreamingEventsHarmony: """Streaming event tests against local gRPC backend with Harmony model.""" def test_output_item_event_emitted(self, setup_backend): """Test that output_index is zero-based in streaming responses. Verifies that the first output item has output_index: 0. """ _, model, client, gateway = setup_backend resp = client.responses.create( model=model, input="Count from 1 to 3", stream=True, max_output_tokens=50, ) events = list(resp) assert len(events) > 0 # Find output_item.added events output_item_added_events = [ event for event in events if event.type == "response.output_item.added" ] assert len(output_item_added_events) > 0, "Should have output_item.added events" # Verify first output item has output_index: 0 first_item_event = output_item_added_events[0] assert first_item_event.item is not None assert first_item_event.output_index is not None assert ( first_item_event.output_index == 0 ), "First output item must have output_index: 0 (zero-based indexing)" # Verify subsequent items increment correctly for i, event in enumerate(output_item_added_events): assert ( event.output_index == i ), f"Output item {i} should have output_index: {i}" # Verify output_item.done event exists output_item_done_events = [ event for event in events if event.type == "response.output_item.done" ] assert len(output_item_done_events) > 0 # Verify output_item.done event structure for event in output_item_done_events: assert event.item is not None assert event.output_index is not None assert event.item.type is not None # Find response.completed event completed_events = [ event for event in events if event.type == "response.completed" ] assert len(completed_events) == 1, "Should have exactly one completed event" # Verify output array exists and contains items completed_event = completed_events[0] assert completed_event.response.output is not None output_array = completed_event.response.output assert isinstance(output_array, list) assert len(output_array) > 0, "Output array should contain at least one item" # Verify each item in output array has proper structure for item in output_array: assert item.type is not None # Verify output_item.added events match items in final output array output_item_added_events = [ event for event in events if event.type == "response.output_item.added" ] assert len(output_item_added_events) == len( output_array ), "Number of output_item.added events should match output array length" def test_reasoning_content(self, setup_backend): """Test that reasoning content has correct zero-based output_index. Specifically tests that reasoning item has output_index: 0 and message item has output_index: 1. """ _, model, client, gateway = setup_backend resp = client.responses.create( model=model, input="What is the capital of France? Think step by step.", stream=True, max_output_tokens=200, ) events = list(resp) assert len(events) > 0 # Find output_item.added events output_item_added_events = [ event for event in events if event.type == "response.output_item.added" ] assert len(output_item_added_events) > 0 reasoning_items = [ item for item in output_item_added_events if item.item.type == "reasoning" ] message_items = [ item for item in output_item_added_events if item.item.type == "message" ] # If reasoning is present, verify it has output_index: 0 if reasoning_items: reasoning_item = reasoning_items[0] assert ( reasoning_item.output_index == 0 ), "Reasoning item should have output_index: 0" # If message is present after reasoning, verify it has output_index: 1 if reasoning_items and message_items: message_item = message_items[0] assert ( message_item.output_index == 1 ), "Message item after reasoning should have output_index: 1" # Find response.completed event completed_events = [ event for event in events if event.type == "response.completed" ] assert len(completed_events) == 1 # Get output array from completed event output_array = completed_events[0].response.output assert len(output_array) > 0 # Check if reasoning items are in output array reasoning_items_in_output = [ item for item in output_array if item.type == "reasoning" ] assert len(reasoning_items_in_output) > 0