[smg][ci] Migrate Response API e2e tests to shared infrastructure (#16680)
Co-authored-by: Chang Su <chang.s.su@oracle.com> Co-authored-by: key4ng <rukeyang@gmail.com>
This commit is contained in:
co-authored by
Chang Su
key4ng
parent
bb798a1c26
commit
109fe03ad1
@@ -0,0 +1,256 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user