Co-authored-by: Chang Su <chang.s.su@oracle.com> Co-authored-by: key4ng <rukeyang@gmail.com>
257 lines
9.3 KiB
Python
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
|