evalstone/evalscope/tests/models/test_openai_stream_retry.py
sora 13274243a0 Bump vendored EvalScope and add K3-ready DPV4 configs.
Keep K3 suite selection and report-schema scoring in bash, merge K3/vision dataset_args into dpv4 yamls, and pin EvalScope at 735d920ee911 with local patches.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-02 07:30:48 +00:00

101 lines
3.3 KiB
Python

import asyncio
from types import SimpleNamespace
from typing import Optional
import pytest
from openai.types.chat import ChatCompletionChunk
from evalscope.api.model import GenerateConfig
from evalscope.models.openai_compatible import OpenAICompatibleAPI
def _chunk(content: str, *, finish_reason: Optional[str] = None) -> ChatCompletionChunk:
return ChatCompletionChunk.model_validate({
'id': 'completion-id',
'created': 1,
'model': 'test-model',
'object': 'chat.completion.chunk',
'choices': [{
'index': 0,
'finish_reason': finish_reason,
'delta': {'content': content},
}],
})
def _prepare_api(monkeypatch: pytest.MonkeyPatch) -> OpenAICompatibleAPI:
api = object.__new__(OpenAICompatibleAPI)
api.base_url = 'https://example.test/v1'
api.model_name = 'test-model'
api.resolve_tools = lambda tools, tool_choice, config: (tools, tool_choice, config)
api.completion_params = lambda config, tools: {'model': 'test-model', 'stream': True}
api.validate_request_params = lambda request: None
api.on_response = lambda response: None
api.chat_choices_from_completion = lambda completion, tools: []
monkeypatch.setattr('evalscope.models.openai_compatible.openai_chat_messages', lambda *args, **kwargs: [])
def model_output(completion, choices):
return SimpleNamespace(
content=completion.choices[0].message.content,
usage=None,
message=SimpleNamespace(),
time=None,
)
monkeypatch.setattr('evalscope.models.openai_compatible.model_output_from_openai', model_output)
return api
def test_generate_retries_when_stream_consumption_fails(monkeypatch):
api = _prepare_api(monkeypatch)
attempts = 0
def create(**request):
nonlocal attempts
attempts += 1
attempt = attempts
def stream():
if attempt == 1:
yield _chunk('discarded partial response')
raise ConnectionError('stream interrupted by upstream gateway')
yield _chunk('complete response', finish_reason='stop')
return stream()
api.client = SimpleNamespace(chat=SimpleNamespace(completions=SimpleNamespace(create=create)))
result = api.generate([], [], None, GenerateConfig(retries=2, retry_interval=0, stream=True))
assert attempts == 2
assert result.content == 'complete response'
def test_generate_async_retries_when_stream_consumption_fails(monkeypatch):
api = _prepare_api(monkeypatch)
attempts = 0
async def create(**request):
nonlocal attempts
attempts += 1
attempt = attempts
async def stream():
if attempt == 1:
yield _chunk('discarded partial response')
raise ConnectionError('stream interrupted by upstream gateway')
yield _chunk('complete response', finish_reason='stop')
return stream()
async_client = SimpleNamespace(chat=SimpleNamespace(completions=SimpleNamespace(create=create)))
monkeypatch.setattr(OpenAICompatibleAPI, 'async_client', property(lambda self: async_client))
result = asyncio.run(
api.generate_async([], [], None, GenerateConfig(retries=2, retry_interval=0, stream=True))
)
assert attempts == 2
assert result.content == 'complete response'