From c1bfd60e43ed63ca4e0f6fd5fd438089a8ca388d Mon Sep 17 00:00:00 2001 From: NATURE Date: Tue, 22 Sep 2026 10:12:36 +0800 Subject: [PATCH] [Frontend] Remove obsolete experimental duplex client parameters (#7647) Signed-off-by: natureofnature --- tests/e2e/features/fullduplex/test_client.py | 41 ++++++++++++------- ...run_minicpmo_realtime_duplex_server_vad.py | 4 +- vllm_omni/experimental/fullduplex/client.py | 26 +++--------- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/tests/e2e/features/fullduplex/test_client.py b/tests/e2e/features/fullduplex/test_client.py index 250391fe2..9bf6c3ba9 100644 --- a/tests/e2e/features/fullduplex/test_client.py +++ b/tests/e2e/features/fullduplex/test_client.py @@ -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(): diff --git a/tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py b/tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py index 0fb1c57b3..7850ceeaf 100644 --- a/tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py +++ b/tests/e2e/online_serving/run_minicpmo_realtime_duplex_server_vad.py @@ -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, ) diff --git a/vllm_omni/experimental/fullduplex/client.py b/vllm_omni/experimental/fullduplex/client.py index 45cdde482..a5b08863b 100644 --- a/vllm_omni/experimental/fullduplex/client.py +++ b/vllm_omni/experimental/fullduplex/client.py @@ -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})