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

350 lines
12 KiB
Python

"""State management tests for Response API.
Tests both previous_response_id and conversation-based state management.
These tests work across local (gRPC) and cloud (OpenAI, xAI) backends.
Source: Migrated from e2e_response_api/features/test_state_management.py
"""
from __future__ import annotations
import logging
import openai
import pytest
logger = logging.getLogger(__name__)
# =============================================================================
# Cloud Backend Tests (OpenAI, xAI)
# =============================================================================
@pytest.mark.parametrize("setup_backend", ["openai", "xai"], indirect=True)
class TestStateManagementCloud:
"""State management tests against cloud APIs."""
def test_basic_response_creation(self, setup_backend):
"""Test basic response creation without state."""
_, model, client, gateway = setup_backend
resp = client.responses.create(model=model, input="What is 2+2?")
assert resp.id is not None
assert resp.error is None
assert resp.status == "completed"
assert len(resp.output_text) > 0
assert resp.usage is not None
def test_streaming_response(self, setup_backend):
"""Test streaming response."""
_, model, client, gateway = setup_backend
resp = client.responses.create(
model=model, input="Count to 5", stream=True, max_output_tokens=50
)
events = list(resp)
created_events = [e for e in events if e.type == "response.created"]
assert len(created_events) > 0
assert any(
e.type in ["response.completed", "response.in_progress"] for e in events
)
def test_previous_response_id_chaining(self, setup_backend):
"""Test chaining responses using previous_response_id."""
_, model, client, gateway = setup_backend
# First response
resp1 = client.responses.create(
model=model, input="My name is Alice and my friend is Bob. Remember it."
)
assert resp1.error is None
assert resp1.status == "completed"
# Second response referencing first
resp2 = client.responses.create(
model=model, input="What is my name", previous_response_id=resp1.id
)
assert resp2.error is None
assert resp2.status == "completed"
assert "Alice" in resp2.output_text
# Third response referencing second
resp3 = client.responses.create(
model=model,
input="What is my friend name?",
previous_response_id=resp2.id,
)
assert resp3.error is None
assert resp3.status == "completed"
assert "Bob" in resp3.output_text
def test_conversation_with_multiple_turns(self, setup_backend):
"""Test state management using conversation ID."""
_, model, client, gateway = setup_backend
# Create conversation
conv_resp = client.conversations.create(metadata={"topic": "math"})
assert conv_resp.id is not None
assert conv_resp.created_at is not None
conversation_id = conv_resp.id
# First response in conversation
resp1 = client.responses.create(
model=model, input="I have 5 apples.", conversation=conversation_id
)
assert resp1.error is None
assert resp1.status == "completed"
# Second response in same conversation
resp2 = client.responses.create(
model=model,
input="How many apples do I have?",
conversation=conversation_id,
)
assert resp2.error is None
assert resp2.status == "completed"
assert "5" in resp2.output_text or "five" in resp2.output_text.lower()
# Third response in same conversation
resp3 = client.responses.create(
model=model,
input="If I get 3 more, how many total?",
conversation=conversation_id,
)
assert resp3.error is None
assert resp3.status == "completed"
assert "8" in resp3.output_text or "eight" in resp3.output_text.lower()
items = client.conversations.items.list(conversation_id)
assert items.data is not None
assert len(items.data) >= 6 # 3 inputs + 3 outputs
@pytest.mark.skip(reason="TODO: Add the invalid previous_response_id check")
def test_previous_response_id_invalid(self, setup_backend):
"""Test using invalid previous_response_id."""
_, model, client, gateway = setup_backend
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="Test",
previous_response_id="resp_invalid123",
max_output_tokens=50,
)
def test_mutually_exclusive_parameters(self, setup_backend):
"""Test that previous_response_id and conversation are mutually exclusive."""
_, model, client, gateway = setup_backend
conversation_id = "conv_123"
resp1 = client.responses.create(model=model, input="Test")
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="This should fail",
previous_response_id=resp1.id,
conversation=conversation_id,
)
# =============================================================================
# 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 TestStateManagementLocal:
"""State management tests against local gRPC backend."""
@pytest.mark.skip(reason="TODO: Add the invalid previous_response_id check")
def test_previous_response_id_invalid(self, setup_backend):
"""Test using invalid previous_response_id."""
_, model, client, gateway = setup_backend
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="Test",
previous_response_id="resp_invalid123",
max_output_tokens=50,
)
def test_basic_response_creation(self, setup_backend):
"""Test basic response creation without state."""
_, model, client, gateway = setup_backend
resp = client.responses.create(model=model, input="What is 2+2?")
assert resp.id is not None
assert resp.error is None
assert resp.status == "completed"
assert len(resp.output_text) > 0
assert resp.usage is not None
def test_streaming_response(self, setup_backend):
"""Test streaming response."""
_, model, client, gateway = setup_backend
resp = client.responses.create(
model=model, input="Count to 5", stream=True, max_output_tokens=50
)
events = list(resp)
created_events = [e for e in events if e.type == "response.created"]
assert len(created_events) > 0
assert any(
e.type in ["response.completed", "response.in_progress"] for e in events
)
def test_previous_response_id_chaining(self, setup_backend):
"""Test chaining responses using previous_response_id."""
_, model, client, gateway = setup_backend
# First response
resp1 = client.responses.create(
model=model, input="My name is Alice and my friend is Bob. Remember it."
)
assert resp1.error is None
assert resp1.status == "completed"
# Second response referencing first
resp2 = client.responses.create(
model=model, input="What is my name", previous_response_id=resp1.id
)
assert resp2.error is None
assert resp2.status == "completed"
assert "Alice" in resp2.output_text
# Third response referencing second
resp3 = client.responses.create(
model=model,
input="What is my friend name?",
previous_response_id=resp2.id,
)
assert resp3.error is None
assert resp3.status == "completed"
assert "Bob" in resp3.output_text
def test_mutually_exclusive_parameters(self, setup_backend):
"""Test that previous_response_id and conversation are mutually exclusive."""
_, model, client, gateway = setup_backend
conversation_id = "conv_123"
resp1 = client.responses.create(model=model, input="Test")
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="This should fail",
previous_response_id=resp1.id,
conversation=conversation_id,
)
# =============================================================================
# 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 TestStateManagementHarmony:
"""State management tests against local gRPC backend with Harmony model."""
@pytest.mark.skip(reason="TODO: Add the invalid previous_response_id check")
def test_previous_response_id_invalid(self, setup_backend):
"""Test using invalid previous_response_id."""
_, model, client, gateway = setup_backend
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="Test",
previous_response_id="resp_invalid123",
max_output_tokens=50,
)
def test_basic_response_creation(self, setup_backend):
"""Test basic response creation without state."""
_, model, client, gateway = setup_backend
resp = client.responses.create(model=model, input="What is 2+2?")
assert resp.id is not None
assert resp.error is None
assert resp.status == "completed"
assert len(resp.output_text) > 0
assert resp.usage is not None
def test_streaming_response(self, setup_backend):
"""Test streaming response."""
_, model, client, gateway = setup_backend
resp = client.responses.create(
model=model, input="Count to 5", stream=True, max_output_tokens=50
)
events = list(resp)
created_events = [e for e in events if e.type == "response.created"]
assert len(created_events) > 0
assert any(
e.type in ["response.completed", "response.in_progress"] for e in events
)
def test_previous_response_id_chaining(self, setup_backend):
"""Test chaining responses using previous_response_id."""
_, model, client, gateway = setup_backend
# First response
resp1 = client.responses.create(
model=model, input="My name is Alice and my friend is Bob. Remember it."
)
assert resp1.error is None
assert resp1.status == "completed"
# Second response referencing first
resp2 = client.responses.create(
model=model, input="What is my name", previous_response_id=resp1.id
)
assert resp2.error is None
assert resp2.status == "completed"
assert "Alice" in resp2.output_text
# Third response referencing second
resp3 = client.responses.create(
model=model,
input="What is my friend name?",
previous_response_id=resp2.id,
)
assert resp3.error is None
assert resp3.status == "completed"
assert "Bob" in resp3.output_text
def test_mutually_exclusive_parameters(self, setup_backend):
"""Test that previous_response_id and conversation are mutually exclusive."""
_, model, client, gateway = setup_backend
conversation_id = "conv_123"
resp1 = client.responses.create(model=model, input="Test")
with pytest.raises(openai.BadRequestError):
client.responses.create(
model=model,
input="This should fail",
previous_response_id=resp1.id,
conversation=conversation_id,
)