evalstone/evalscope/tests/test_openai_responses.py
2026-07-08 08:57:50 +00:00

266 lines
7.4 KiB
Python

import pytest
from openai.types.responses import Response
from types import SimpleNamespace
from evalscope.api.messages import ChatMessageUser, ContentImage, ContentText
from evalscope.api.model import GenerateConfig
from evalscope.models.openai_responses import OpenAIResponsesAPI
from evalscope.models.utils.openai_responses import (
chat_choices_from_openai_response,
openai_response_messages,
response_text_from_dict,
response_usage_from_dict,
)
from evalscope.perf.arguments import Arguments
from evalscope.perf.plugin.api.openai_responses_api import OpenAIResponsesPlugin, _extract_sse_data
def test_openai_response_messages_normalize_multimodal_content():
messages = openai_response_messages([
ChatMessageUser(
content=[
ContentText(text='Describe the image.'),
ContentImage(image='https://example.com/cat.png', detail='low'),
]
)
])
assert messages == [{
'role': 'user',
'content': [
{
'type': 'input_text',
'text': 'Describe the image.'
},
{
'type': 'input_image',
'image_url': 'https://example.com/cat.png',
'detail': 'low'
},
],
}]
def test_openai_responses_provider_builds_native_request():
api = OpenAIResponsesAPI(
model_name='gpt-4.1-mini',
base_url='https://api.openai.com/v1/responses',
api_key='dummy',
)
captured = {}
def fake_create(**kwargs):
captured.update(kwargs)
return Response(
id='resp_test',
created_at=0,
error=None,
incomplete_details=None,
instructions=None,
metadata={},
model='gpt-4.1-mini',
object='response',
output=[],
parallel_tool_calls=True,
temperature=0,
tool_choice='auto',
tools=[],
top_p=1,
status='completed',
)
api.client.responses.create = fake_create
api._valid_params = {
'input',
'max_output_tokens',
'model',
'stream',
'temperature',
'tool_choice',
'tools',
}
output = api.generate(
input=[ChatMessageUser(content='hello')],
tools=[],
tool_choice='none',
config=GenerateConfig(stream=True, max_tokens=16, temperature=0, retries=1),
)
assert api.base_url == 'https://api.openai.com/v1'
assert captured['model'] == 'gpt-4.1-mini'
assert captured['input'] == [{
'role': 'user',
'content': 'hello',
}]
assert captured['stream'] is True
assert captured['max_output_tokens'] == 16
assert 'messages' not in captured
assert output.model == 'gpt-4.1-mini'
def test_openai_responses_provider_requires_responses_client():
api = SimpleNamespace(client=SimpleNamespace())
with pytest.raises(RuntimeError, match='openai>=1.56.0'):
OpenAIResponsesAPI._validate_responses_client(api)
def test_openai_responses_perf_build_request_and_endpoint_resolution():
args = Arguments(
model='gpt-4.1-mini',
api='openai_responses',
url='https://api.openai.com/v1',
number=1,
parallel=1,
stream=True,
max_tokens=64,
)
plugin = OpenAIResponsesPlugin(args)
request = plugin.build_request([{'role': 'user', 'content': [{'type': 'text', 'text': 'hello'}]}])
assert args.url == 'https://api.openai.com/v1/responses'
assert request['model'] == 'gpt-4.1-mini'
assert request['stream'] is True
assert request['max_output_tokens'] == 64
assert request['input'] == [{
'role': 'user',
'content': [{
'type': 'input_text',
'text': 'hello'
}],
}]
def test_openai_responses_perf_warns_only_for_multiple_choices(monkeypatch):
warnings = []
monkeypatch.setattr(
'evalscope.perf.plugin.api.openai_responses_api.logger.warning',
lambda msg: warnings.append(msg),
)
args = Arguments(
model='gpt-4.1-mini',
api='openai_responses',
url='https://api.openai.com/v1/responses',
number=1,
parallel=1,
n_choices=1,
)
OpenAIResponsesPlugin(args).build_request('hello')
assert warnings == []
args = Arguments(
model='gpt-4.1-mini',
api='openai_responses',
url='https://api.openai.com/v1/responses',
number=1,
parallel=1,
n_choices=2,
)
OpenAIResponsesPlugin(args).build_request('hello')
assert warnings == ['OpenAI Responses API does not support n_choices > 1; ignoring --n-choices.']
def test_openai_responses_perf_defaults_to_responses_endpoint_and_rejects_tokenized_prompt():
args = Arguments(
model='gpt-4.1-mini',
api='openai_responses',
number=1,
parallel=1,
)
assert args.url == 'http://127.0.0.1:8877/v1/responses'
with pytest.raises(ValueError, match='--tokenize-prompt is not supported'):
Arguments(
model='gpt-4.1-mini',
api='openai_responses',
number=1,
parallel=1,
tokenize_prompt=True,
tokenizer_path='dummy',
)
def test_openai_responses_perf_parse_stream_events():
args = Arguments(
model='gpt-4.1-mini',
api='openai_responses',
url='https://api.openai.com/v1/responses',
number=1,
parallel=1,
)
plugin = OpenAIResponsesPlugin(args)
responses = [
{
'type': 'response.output_text.delta',
'delta': 'hel',
},
{
'type': 'response.output_text.delta',
'delta': 'lo',
},
{
'type': 'response.completed',
'response': {
'usage': {
'input_tokens': 3,
'output_tokens': 2,
}
},
},
]
assert plugin.parse_responses(responses, request='{}') == (3, 2)
assert plugin._collect_output_text(responses)[0] == ['hel', 'lo']
def test_openai_responses_extracts_semantic_sse_data():
message = 'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"hi"}'
assert _extract_sse_data(message) == '{"type":"response.output_text.delta","delta":"hi"}'
def test_response_dict_text_and_usage_parsing():
payload = {
'output': [{
'type': 'message',
'content': [{
'type': 'output_text',
'text': 'hello'
}],
}],
'usage': {
'input_tokens': 3,
'output_tokens': 1,
'total_tokens': 4,
},
}
assert response_text_from_dict(payload) == 'hello'
assert response_usage_from_dict(payload) == (3, 1)
def test_chat_choices_from_openai_response_object():
response = SimpleNamespace(
id='resp_1',
model='gpt-4.1-mini',
status='completed',
incomplete_details=None,
output=[SimpleNamespace(
type='message',
content=[
SimpleNamespace(type='output_text', text='hello'),
],
)],
)
choices = chat_choices_from_openai_response(response, tools=[])
assert len(choices) == 1
assert choices[0].message.text == 'hello'
assert choices[0].stop_reason == 'stop'