Signed-off-by: psv666 <2693925048@qq.com>
This commit is contained in:
1 parent
825ab20b07
commit
e3be42e052
37 files changed
+3792
-1629
No files matched your search
@@ -0,0 +1,144 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
"""``protocol.duplex`` is the only door a duplex consumer uses.
|
||||
|
||||
The layering is a chain::
|
||||
|
||||
protocol.realtime <- protocol.duplex <- engine / entrypoints / clients
|
||||
|
||||
The middle link only earns its keep if the last arrow is the *only* one. If the
|
||||
engine may also reach past it straight into ``protocol.realtime``, then giving a
|
||||
Tier 1 helper a duplex-specific version later means touching every call site
|
||||
instead of one file --- which is exactly the change
|
||||
``convert_input_audio_with_rate`` is expected to need (it resamples to
|
||||
MiniCPM-o's 16 kHz, not the client's rate).
|
||||
|
||||
So this asserts two things: nobody skips the middle link, and the middle link is
|
||||
complete enough that nobody has to.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ast
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
#: Every module that speaks the duplex protocol. These import
|
||||
#: ``vllm_omni.protocol.duplex``; reaching into ``vllm_omni.protocol.realtime``
|
||||
#: from here skips the extension point.
|
||||
CONSUMER_PATHS = (
|
||||
"vllm_omni/engine/duplex",
|
||||
"vllm_omni/engine/duplex_omni_engine.py",
|
||||
"vllm_omni/entrypoints/duplex",
|
||||
"vllm_omni/entrypoints/duplex_omni.py",
|
||||
"vllm_omni/clients/duplex.py",
|
||||
"vllm_omni/clients/inline_duplex.py",
|
||||
)
|
||||
|
||||
|
||||
def _consumer_modules() -> list[Path]:
|
||||
found: list[Path] = []
|
||||
for entry in CONSUMER_PATHS:
|
||||
path = REPO_ROOT / entry
|
||||
found.extend(sorted(path.rglob("*.py")) if path.is_dir() else [path])
|
||||
assert found, "consumer list is stale"
|
||||
return found
|
||||
|
||||
|
||||
@pytest.mark.parametrize("module", _consumer_modules(), ids=lambda p: p.name)
|
||||
def test_a_duplex_consumer_does_not_skip_the_duplex_protocol_package(module: Path) -> None:
|
||||
# Source-level, so a lazy import inside a function body is caught too.
|
||||
tree = ast.parse(module.read_text())
|
||||
offenders = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("vllm_omni.protocol.realtime"):
|
||||
offenders.append(f"line {node.lineno}: from {node.module} import ...")
|
||||
elif isinstance(node, ast.Import):
|
||||
offenders.extend(
|
||||
f"line {node.lineno}: import {alias.name}"
|
||||
for alias in node.names
|
||||
if alias.name.startswith("vllm_omni.protocol.realtime")
|
||||
)
|
||||
assert not offenders, (
|
||||
f"{module.relative_to(REPO_ROOT)} imports the Tier 1 package directly; "
|
||||
f"import from vllm_omni.protocol.duplex instead:\n " + "\n ".join(offenders)
|
||||
)
|
||||
|
||||
|
||||
def test_the_duplex_package_carries_the_whole_command_vocabulary() -> None:
|
||||
from vllm_omni.engine.duplex import commands as engine_commands
|
||||
from vllm_omni.protocol.duplex import commands as duplex_wire
|
||||
|
||||
# Every command the engine handles is nameable through the one door.
|
||||
engine_names = {
|
||||
name
|
||||
for name in engine_commands.__all__
|
||||
if isinstance(getattr(engine_commands, name), type)
|
||||
and issubclass(getattr(engine_commands, name), engine_commands.DuplexCommand)
|
||||
and getattr(engine_commands, name) is not engine_commands.DuplexCommand
|
||||
}
|
||||
assert engine_names <= set(duplex_wire.__all__)
|
||||
|
||||
|
||||
def test_the_duplex_package_carries_the_whole_event_vocabulary() -> None:
|
||||
from vllm_omni.engine.duplex import events as engine_events
|
||||
from vllm_omni.protocol.duplex import events as duplex_wire
|
||||
|
||||
engine_names = {
|
||||
name
|
||||
for name in engine_events.__all__
|
||||
if isinstance(getattr(engine_events, name), type)
|
||||
and issubclass(getattr(engine_events, name), engine_events.DuplexEvent)
|
||||
}
|
||||
assert engine_names <= set(duplex_wire.__all__)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"module_pair",
|
||||
[("events", "events"), ("commands", "commands")],
|
||||
ids=["events", "commands"],
|
||||
)
|
||||
def test_a_reexported_tier1_name_is_the_tier1_object(module_pair: tuple[str, str]) -> None:
|
||||
"""Re-export, not re-declaration: the Tier 1 class is one object."""
|
||||
import importlib
|
||||
|
||||
duplex_mod = importlib.import_module(f"vllm_omni.protocol.duplex.{module_pair[0]}")
|
||||
realtime_mod = importlib.import_module(f"vllm_omni.protocol.realtime.{module_pair[1]}")
|
||||
|
||||
shared = [n for n in duplex_mod.__all__ if hasattr(realtime_mod, n)]
|
||||
assert shared, "nothing re-exported --- the facade is not doing its job"
|
||||
for name in shared:
|
||||
duplex_obj = getattr(duplex_mod, name)
|
||||
realtime_obj = getattr(realtime_mod, name)
|
||||
if duplex_obj is realtime_obj:
|
||||
continue
|
||||
# The only permitted divergence is a Tier 2 class extending its twin.
|
||||
assert isinstance(duplex_obj, type) and issubclass(duplex_obj, realtime_obj), (
|
||||
f"{name} in protocol.duplex is neither the Tier 1 object nor a subclass of it"
|
||||
)
|
||||
|
||||
|
||||
def test_duplex_event_is_an_alias_but_duplex_command_is_not() -> None:
|
||||
"""The asymmetry is deliberate, and a later reader should not 'fix' it.
|
||||
|
||||
An event has no engine-internal half --- what the session emits is what the
|
||||
socket sends --- so ``DuplexEvent`` is simply ``RealtimeEvent``. A command
|
||||
does: ``payload()`` renders the session runner's mailbox dictionary, and for
|
||||
four of the seventeen the mailbox channel is not even the client event type.
|
||||
"""
|
||||
from vllm_omni.engine.duplex.commands import DuplexCommand
|
||||
from vllm_omni.protocol.duplex.events import DuplexEvent
|
||||
from vllm_omni.protocol.realtime.commands import RealtimeCommand
|
||||
from vllm_omni.protocol.realtime.events import RealtimeEvent
|
||||
|
||||
assert DuplexEvent is RealtimeEvent
|
||||
|
||||
assert DuplexCommand is not RealtimeCommand
|
||||
assert issubclass(DuplexCommand, RealtimeCommand)
|
||||
assert hasattr(DuplexCommand, "payload") and not hasattr(RealtimeCommand, "payload")
|
||||
Reference in new issue
Block a user