[Frontend] Remove obsolete experimental duplex client parameters (#7647)

Signed-off-by: natureofnature <wzliu@connect.hku.hk>
This commit is contained in:
NATURE authored and GitHub committed 2026-09-21 19:12:36 -07:00
1 parent d4570afabf
commit c1bfd60e43
3 files changed
+33 -38

No files matched your search

+27 -14
View File
@@ -26,11 +26,10 @@ from vllm_omni.model_executor.models.minicpmo_4_5.duplex.policy import (
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_realtime_client_builds_explicit_native_duplex_url():
def test_realtime_client_removes_obsolete_duplex_url_parameters():
url = build_realtime_url(
"ws://localhost:8099/v1/realtime?custom=1&duplex=0&model=stale&native_duplex=0&session_id=stale",
"openbmb/MiniCPM-o-4_5",
session_id="session-a",
)
query = parse_qs(urlsplit(url).query)
@@ -38,8 +37,6 @@ def test_realtime_client_builds_explicit_native_duplex_url():
"custom": ["1"],
"duplex": ["1"],
"model": ["openbmb/MiniCPM-o-4_5"],
"native_duplex": ["1"],
"session_id": ["session-a"],
}
@@ -56,16 +53,28 @@ def test_seed_tts_initial_text_is_part_of_native_duplex_context():
)
def test_realtime_client_builds_resume_only_url_when_autostart_disabled():
@pytest.mark.parametrize("scheme, expected_scheme", [("http", "ws"), ("https", "wss"), ("ws", "ws"), ("wss", "wss")])
def test_realtime_client_preserves_resume_url(scheme, expected_scheme):
url = build_realtime_url(
"ws://localhost:8099/v1/realtime?duplex=1&autostart=1",
"openbmb/MiniCPM-o-4_5",
f"{scheme}://localhost:8099/v1/realtime?model=existing&resume=1&custom=&autostart=1#fragment",
None,
autostart=False,
)
query = parse_qs(urlsplit(url).query)
assert query["autostart"] == ["0"]
assert query["native_duplex"] == ["1"]
parts = urlsplit(url)
assert (parts.scheme, parts.netloc, parts.path, parts.fragment) == (
expected_scheme,
"localhost:8099",
"/v1/realtime",
"fragment",
)
assert parse_qs(parts.query, keep_blank_values=True) == {
"model": ["existing"],
"resume": ["1"],
"custom": [""],
"duplex": ["1"],
"autostart": ["0"],
}
@pytest.mark.asyncio
@@ -90,6 +99,8 @@ async def test_realtime_client_configure_omits_ref_audio_by_default():
session = client.sent[0]["session"]
assert "ref_audio" not in session
assert session["idle_timeout_s"] == 900
assert "session_id" not in session
assert session["extra_body"] == {"auto_response": True, "force_listen_count": 0}
@pytest.mark.asyncio
@@ -209,7 +220,7 @@ async def test_realtime_client_configure_sends_seed_tts_text_condition():
@pytest.mark.asyncio
async def test_realtime_client_configure_explicit_tts_opts_out_of_native_duplex():
async def test_realtime_client_configure_disables_auto_response_without_obsolete_opt_in():
class Client(RealtimeDuplexClient):
def __init__(self):
super().__init__("ws://unused")
@@ -220,20 +231,22 @@ async def test_realtime_client_configure_explicit_tts_opts_out_of_native_duplex(
self.events.add({"type": "session.created"})
client = Client()
extra_body = {"ref_audio": "data:audio/wav;base64,AAAA", "native_duplex": False}
await client.configure(
"openbmb/MiniCPM-o-4_5",
native_duplex=False,
auto_response=False,
extra_body={"ref_audio": "data:audio/wav;base64,AAAA"},
extra_body=extra_body,
timeout_s=1,
)
session_extra_body = client.sent[0]["session"]["extra_body"]
assert session_extra_body == {
"ref_audio": "data:audio/wav;base64,AAAA",
"native_duplex": False,
"auto_response": False,
"force_listen_count": 0,
}
assert extra_body == {"ref_audio": "data:audio/wav;base64,AAAA", "native_duplex": False}
def test_realtime_event_collector_partitions_audio_by_response():
@@ -22,8 +22,7 @@ def _events(events: list[dict[str, object]], response_id: str, kind: str) -> lis
async def run_server_vad_interrupt(args) -> dict[str, object]:
initial = read_pcm16_wav(Path(args.input_wav))
interrupt = read_pcm16_wav(Path(args.interrupt_wav))[: PCM16_SAMPLE_RATE * PCM16_BYTES_PER_SAMPLE * 47 // 10]
session_id = f"server-vad-hard-interrupt-{id(args)}"
client = RealtimeDuplexClient(build_realtime_url(args.url, args.model, autostart=False, session_id=session_id))
client = RealtimeDuplexClient(build_realtime_url(args.url, args.model, autostart=False))
async def until(predicate, label: str) -> None:
await wait_for(predicate, timeout_s=args.timeout_s, label=label)
@@ -33,7 +32,6 @@ async def run_server_vad_interrupt(args) -> dict[str, object]:
await client.configure(
args.model,
ref_audio="data:audio/wav;base64," + base64.b64encode(Path(args.ref_audio).read_bytes()).decode(),
session_id=session_id,
turn_detection={"type": "server_vad", "interrupt_response": True},
timeout_s=args.timeout_s,
)
+5 -21
View File
@@ -116,25 +116,21 @@ def build_realtime_url(
model: str | None,
*,
autostart: bool | None = None,
native_duplex: bool | None = True,
session_id: str | None = None,
) -> str:
"""Add the explicit native-duplex query parameters to a Realtime URL."""
"""Select the duplex route; new session IDs are assigned by the server."""
parts = urlsplit(url)
if parts.scheme in {"http", "https"}:
parts = parts._replace(scheme="ws" if parts.scheme == "http" else "wss")
if parts.scheme not in {"ws", "wss"} or not parts.netloc:
raise ValueError(f"Unsupported Realtime URL: {url!r}")
query = dict(parse_qsl(parts.query, keep_blank_values=True))
query.pop("native_duplex", None)
query.pop("session_id", None)
query["duplex"] = "1"
if model:
query["model"] = model
if native_duplex is not None:
query["native_duplex"] = "1" if native_duplex else "0"
if autostart is not None:
query["autostart"] = "1" if autostart else "0"
if session_id:
query["session_id"] = session_id
return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), parts.fragment))
@@ -512,26 +508,16 @@ class RealtimeDuplexClient:
ref_audio: str | None = None,
instructions: str | None = None,
initial_user_text: str | None = None,
native_duplex: bool = True,
auto_response: bool = True,
temperature: float | None = None,
extra_body: dict[str, object] | None = None,
turn_detection: dict[str, object] | None = None,
session_id: str | None = None,
idle_timeout_s: float | None = None,
timeout_s: float = 20.0,
) -> None:
session_extra_body = dict(extra_body or {})
if native_duplex:
session_extra_body.update(
{
"auto_response": auto_response,
"native_duplex": True,
"force_listen_count": 0,
}
)
else:
session_extra_body["native_duplex"] = False
session_extra_body.pop("native_duplex", None)
session_extra_body.update(auto_response=auto_response, force_listen_count=0)
session: dict[str, object] = {
"model": model,
"modalities": ["audio", "text"],
@@ -556,8 +542,6 @@ class RealtimeDuplexClient:
session_extra = session["extra_body"]
assert isinstance(session_extra, dict)
session_extra["duplex_initial_user_text"] = initial_user_text
if session_id:
session["session_id"] = session_id
if idle_timeout_s is not None:
session["idle_timeout_s"] = idle_timeout_s
await self.send({"type": "session.update", "session": session})