Files
sglang/sgl-model-gateway/e2e_test/responses/test_streaming_events.py
2026-01-07 17:40:03 -08:00

257 lines
9.3 KiB
Python

"""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