165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from src.config.model_configs import APIProvider, ReasoningParseMode, ToolArgumentParseMode
|
|
from src.llm_models.model_client.openai_client import (
|
|
_OpenAIStreamAccumulator,
|
|
_build_reasoning_key,
|
|
_default_normal_response_parser,
|
|
_parse_tool_arguments,
|
|
_sanitize_messages_for_toolless_request,
|
|
)
|
|
from src.llm_models.payload_content.message import Message, RoleType, TextMessagePart
|
|
from src.llm_models.payload_content.tool_option import ToolCall
|
|
|
|
|
|
@pytest.mark.parametrize("parse_mode", list(ToolArgumentParseMode))
|
|
def test_parse_tool_arguments_treats_blank_arguments_as_empty_dict(parse_mode: ToolArgumentParseMode) -> None:
|
|
assert _parse_tool_arguments("", parse_mode, None) == {}
|
|
assert _parse_tool_arguments(" ", parse_mode, None) == {}
|
|
|
|
|
|
def test_normal_response_parser_accepts_empty_string_arguments_for_parameterless_tool() -> None:
|
|
response = SimpleNamespace(
|
|
choices=[
|
|
SimpleNamespace(
|
|
finish_reason="tool_calls",
|
|
message=SimpleNamespace(
|
|
content=None,
|
|
tool_calls=[
|
|
SimpleNamespace(
|
|
id="finish-call",
|
|
type="function",
|
|
function=SimpleNamespace(name="finish", arguments=""),
|
|
)
|
|
],
|
|
),
|
|
)
|
|
],
|
|
usage=None,
|
|
model="glm-5.1",
|
|
)
|
|
|
|
api_response, usage_record = _default_normal_response_parser(
|
|
response,
|
|
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
|
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
|
reasoning_key=None,
|
|
)
|
|
|
|
assert len(api_response.tool_calls) == 1
|
|
assert api_response.tool_calls[0].func_name == "finish"
|
|
assert api_response.tool_calls[0].args == {}
|
|
assert usage_record is None
|
|
|
|
|
|
def test_sanitize_messages_for_toolless_request_drops_assistant_tool_call_without_parts() -> None:
|
|
messages = [
|
|
Message(
|
|
role=RoleType.Assistant,
|
|
tool_calls=[
|
|
ToolCall(
|
|
call_id="call_1",
|
|
func_name="mute_user",
|
|
args={"target": "alice"},
|
|
)
|
|
],
|
|
),
|
|
Message(
|
|
role=RoleType.User,
|
|
parts=[TextMessagePart(text="继续")],
|
|
),
|
|
]
|
|
|
|
sanitized_messages = _sanitize_messages_for_toolless_request(messages)
|
|
|
|
assert len(sanitized_messages) == 1
|
|
assert sanitized_messages[0].role == RoleType.User
|
|
|
|
|
|
def test_normal_response_parser_ignores_reasoning_field_for_non_openrouter_provider() -> None:
|
|
response = SimpleNamespace(
|
|
choices=[
|
|
SimpleNamespace(
|
|
finish_reason="stop",
|
|
message=SimpleNamespace(
|
|
content="正式回复",
|
|
reasoning="推理内容",
|
|
tool_calls=None,
|
|
),
|
|
)
|
|
],
|
|
usage=None,
|
|
model="openrouter/test-model",
|
|
)
|
|
|
|
api_response, usage_record = _default_normal_response_parser(
|
|
response,
|
|
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
|
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
|
reasoning_key=_build_reasoning_key(
|
|
APIProvider(name="test", base_url="https://openrouter.ai.example.com/api/v1", api_key="test")
|
|
),
|
|
)
|
|
|
|
assert api_response.content == "正式回复"
|
|
assert api_response.reasoning_content is None
|
|
assert usage_record is None
|
|
|
|
|
|
def test_normal_response_parser_reads_provider_reasoning_field_for_reasoning_domains() -> None:
|
|
provider_urls = [
|
|
"https://openrouter.ai/compatible-api",
|
|
"https://api.groq.com/openai/v1",
|
|
]
|
|
|
|
for provider_url in provider_urls:
|
|
response = SimpleNamespace(
|
|
choices=[
|
|
SimpleNamespace(
|
|
finish_reason="stop",
|
|
message=SimpleNamespace(
|
|
content="正式回复",
|
|
reasoning="推理内容",
|
|
tool_calls=None,
|
|
),
|
|
)
|
|
],
|
|
usage=None,
|
|
model="test-model",
|
|
)
|
|
|
|
api_response, usage_record = _default_normal_response_parser(
|
|
response,
|
|
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
|
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
|
reasoning_key=_build_reasoning_key(
|
|
APIProvider(name="reasoning-provider", base_url=provider_url, api_key="test")
|
|
),
|
|
)
|
|
|
|
assert api_response.content == "正式回复"
|
|
assert api_response.reasoning_content == "推理内容"
|
|
assert usage_record is None
|
|
|
|
|
|
def test_stream_accumulator_reads_openrouter_reasoning_delta_field() -> None:
|
|
accumulator = _OpenAIStreamAccumulator(
|
|
reasoning_parse_mode=ReasoningParseMode.AUTO,
|
|
tool_argument_parse_mode=ToolArgumentParseMode.AUTO,
|
|
reasoning_key=_build_reasoning_key(
|
|
APIProvider(name="openrouter", base_url="https://openrouter.ai/compatible-api", api_key="test")
|
|
),
|
|
)
|
|
try:
|
|
accumulator.process_delta(SimpleNamespace(reasoning="流式推理", content=None, tool_calls=None))
|
|
accumulator.process_delta(SimpleNamespace(content="正式回复", tool_calls=None))
|
|
|
|
api_response = accumulator.build_response()
|
|
finally:
|
|
accumulator.close()
|
|
|
|
assert api_response.content == "正式回复"
|
|
assert api_response.reasoning_content == "流式推理"
|