Signed-off-by: chickeyton <ngton2014@gmail.com> Signed-off-by: Gao Han <hgaoaf@connect.ust.hk> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
147 lines
4.3 KiB
Python
147 lines
4.3 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from vllm_omni.entrypoints.duplex.runtime_adapter import (
|
|
validate_serving_runtime_adapter,
|
|
)
|
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"module_name",
|
|
[
|
|
"vllm_omni.entrypoints.duplex.session_runner",
|
|
"vllm_omni.entrypoints.duplex.runtime_bridge",
|
|
"vllm_omni.entrypoints.duplex.serving",
|
|
],
|
|
)
|
|
def test_generic_openai_runtime_import_does_not_load_model_adapters(module_name: str) -> None:
|
|
repo_root = Path(__file__).resolve().parents[3]
|
|
script = f"""
|
|
import sys
|
|
|
|
import {module_name}
|
|
|
|
model_duplex_prefixes = (
|
|
"vllm_omni.model_executor.models.minicpmo_4_5.duplex",
|
|
"vllm_omni.model_executor.models.nemotron_voicechat.duplex",
|
|
"vllm_omni.model_executor.models.personaplex.duplex",
|
|
)
|
|
loaded = sorted(
|
|
name
|
|
for name in sys.modules
|
|
if any(
|
|
name == prefix or name.startswith(prefix + ".")
|
|
for prefix in model_duplex_prefixes
|
|
)
|
|
)
|
|
if loaded:
|
|
raise SystemExit("generic OpenAI runtime loaded model duplex modules: " + ", ".join(loaded))
|
|
"""
|
|
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", script],
|
|
cwd=repo_root,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def test_generic_handler_requires_explicit_serving_runtime_adapter() -> None:
|
|
repo_root = Path(__file__).resolve().parents[3]
|
|
script = """
|
|
from types import SimpleNamespace
|
|
|
|
from vllm_omni.entrypoints.duplex.serving import OmniDuplexSessionHandler
|
|
|
|
chat_service = SimpleNamespace(engine_client=SimpleNamespace())
|
|
try:
|
|
OmniDuplexSessionHandler(chat_service=chat_service)
|
|
except ValueError as exc:
|
|
if "serving runtime adapter" not in str(exc).lower():
|
|
raise
|
|
else:
|
|
raise SystemExit("generic handler silently selected a model serving adapter")
|
|
"""
|
|
|
|
result = subprocess.run(
|
|
[sys.executable, "-c", script],
|
|
cwd=repo_root,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
def _valid_runtime_adapter() -> SimpleNamespace:
|
|
data_plane = SimpleNamespace(
|
|
begin_request=lambda request_id: None,
|
|
is_terminal=lambda request_id: False,
|
|
mark_terminal=lambda request_id: None,
|
|
close_stream=lambda request_id: None,
|
|
close_session=lambda session_id, **kwargs: None,
|
|
project=lambda result, **kwargs: (),
|
|
)
|
|
return SimpleNamespace(
|
|
adapter_id="test-adapter",
|
|
session_states={},
|
|
data_plane=data_plane,
|
|
clean_response_done_prefix="clean",
|
|
interrupted_tts_prefix="interrupted",
|
|
private_runtime_config_keys=frozenset({"private_key"}),
|
|
create_session_state=lambda: object(),
|
|
session_state=lambda session_id: object(),
|
|
remove_session_state=lambda session_id: None,
|
|
is_enabled=lambda config: True,
|
|
capabilities=lambda **kwargs: object(),
|
|
validate_client_extra_body=lambda extra_body: None,
|
|
prepare_runtime_config=lambda config, **kwargs: {},
|
|
runtime_config_for_update=lambda config, current: {},
|
|
data_plane_context=lambda **kwargs: object(),
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("attribute", "invalid_value"),
|
|
[
|
|
("session_states", None),
|
|
("clean_response_done_prefix", None),
|
|
("interrupted_tts_prefix", None),
|
|
("private_runtime_config_keys", {"private_key"}),
|
|
],
|
|
)
|
|
def test_runtime_adapter_validator_rejects_invalid_protocol_attributes(
|
|
attribute: str,
|
|
invalid_value: object,
|
|
) -> None:
|
|
adapter = _valid_runtime_adapter()
|
|
setattr(adapter, attribute, invalid_value)
|
|
|
|
with pytest.raises(TypeError, match=attribute):
|
|
validate_serving_runtime_adapter(adapter)
|
|
|
|
|
|
def test_runtime_adapter_validator_rejects_incomplete_data_plane() -> None:
|
|
adapter = _valid_runtime_adapter()
|
|
adapter.data_plane.close_session = None
|
|
|
|
with pytest.raises(TypeError, match="data_plane.*close_session"):
|
|
validate_serving_runtime_adapter(adapter)
|