evalstone/evalscope/tests/models/test_litellm_reasoning_history.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

103 lines
3.4 KiB
Python

from typing import Any, Dict, List
import litellm
from openai.types.chat import ChatCompletion
from evalscope.api.messages import (
ChatMessage,
ChatMessageAssistant,
ChatMessageUser,
ContentAudio,
ContentReasoning,
ContentText,
)
from evalscope.api.model import GenerateConfig
from evalscope.api.tool import ToolChoice, ToolInfo
from evalscope.models.litellm_compatible import LiteLLMAPI
def _completion_response() -> ChatCompletion:
return ChatCompletion.model_validate({
'id': 'completion-id',
'created': 1,
'model': 'test-model',
'object': 'chat.completion',
'choices': [{
'index': 0,
'finish_reason': 'stop',
'message': {'role': 'assistant', 'content': 'answer'},
}],
'usage': {'prompt_tokens': 1, 'completion_tokens': 1, 'total_tokens': 2},
})
def _conversation() -> List[ChatMessage]:
return [
ChatMessageUser(content='question'),
ChatMessageAssistant(content=[ContentReasoning(reasoning='prior thoughts'), ContentText(text='prior answer')]),
]
def _capturing_completion(calls: List[Dict[str, Any]]) -> Any:
def _completion(**request: Any) -> ChatCompletion:
calls.append(request)
return _completion_response()
return _completion
def test_reasoning_history_defaults_to_reasoning_field(monkeypatch: Any) -> None:
calls: List[Dict[str, Any]] = []
monkeypatch.setattr(litellm, 'completion', _capturing_completion(calls))
api = LiteLLMAPI(model_name='openai/test-model')
api.generate(input=_conversation(), tools=[], tool_choice='none', config=GenerateConfig())
assistant_payload = calls[0]['messages'][1]
# parity with OpenAICompatibleAPI: reasoning lives in the top-level field,
# not smuggled into the content as a <think> tag
assert assistant_payload['reasoning_content'] == 'prior thoughts'
assert '<think>' not in assistant_payload['content']
assert assistant_payload['content'].strip() == 'prior answer'
def test_reasoning_history_none_is_honored(monkeypatch: Any) -> None:
calls: List[Dict[str, Any]] = []
monkeypatch.setattr(litellm, 'completion', _capturing_completion(calls))
api = LiteLLMAPI(model_name='openai/test-model')
api.generate(
input=_conversation(),
tools=[],
tool_choice='none',
config=GenerateConfig(reasoning_history='none'),
)
assistant_payload = calls[0]['messages'][1]
assert 'reasoning_content' not in assistant_payload
assert '<think>' not in assistant_payload['content']
assert assistant_payload['content'].strip() == 'prior answer'
def test_base_url_forwarded_for_dashscope_audio_encoding(monkeypatch: Any) -> None:
calls: List[Dict[str, Any]] = []
monkeypatch.setattr(litellm, 'completion', _capturing_completion(calls))
api = LiteLLMAPI(model_name='openai/test-model', base_url='https://dashscope.aliyuncs.com/compatible-mode/v1')
api.generate(
input=[
ChatMessageUser(content=[
ContentAudio(audio='data:audio/wav;base64,YXVkaW8=', format='wav'),
ContentText(text='transcribe'),
])
],
tools=[],
tool_choice='none',
config=GenerateConfig(),
)
audio_part = calls[0]['messages'][0]['content'][0]
# DashScope endpoints require the data-URI prefix on input audio
assert audio_part['input_audio']['data'] == 'data:audio/wav;base64,YXVkaW8='