[Frontend] Remove obsolete experimental duplex client parameters (#7647)
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
This commit is contained in:
1 parent
d4570afabf
commit
c1bfd60e43
3 files changed
+33
-38
No files matched your search
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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})
|
||||
|
||||
Reference in new issue
Block a user