[Cleanup][Full Duplex] Remove generic server VAD
Signed-off-by: Sy03 <1370724210@qq.com>
This commit is contained in:
1 parent
2a7407fe85
commit
d3780fdcb7
13 files changed
+17
-854
No files matched your search
@@ -1,107 +0,0 @@
|
||||
"""MiniCPM-o server-VAD hard-interruption acceptance driver."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from pathlib import Path
|
||||
|
||||
from vllm_omni.experimental.fullduplex.client import (
|
||||
PCM16_BYTES_PER_SAMPLE,
|
||||
PCM16_SAMPLE_RATE,
|
||||
RealtimeDuplexClient,
|
||||
RealtimeEventCollector,
|
||||
build_realtime_url,
|
||||
read_pcm16_wav,
|
||||
wait_for,
|
||||
)
|
||||
|
||||
|
||||
def _events(events: list[dict[str, object]], response_id: str, kind: str) -> list[dict[str, object]]:
|
||||
return [
|
||||
event
|
||||
for event in events
|
||||
if event.get("type") == kind and RealtimeEventCollector.response_id(event) == response_id
|
||||
]
|
||||
|
||||
|
||||
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))
|
||||
interrupt = interrupt[: PCM16_SAMPLE_RATE * PCM16_BYTES_PER_SAMPLE * 4700 // 1000]
|
||||
session_id = f"server-vad-hard-interrupt-{id(args)}"
|
||||
client = RealtimeDuplexClient(build_realtime_url(args.url, args.model, autostart=False, session_id=session_id))
|
||||
try:
|
||||
await client.__aenter__()
|
||||
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,
|
||||
)
|
||||
await client.stream_pcm16(initial + bytes(16_000 * 2 * 1600 // 1000), chunk_ms=200, realtime=True)
|
||||
await client.commit()
|
||||
await wait_for(
|
||||
lambda: any(e.get("type") == "response.created" for e in client.events.events),
|
||||
timeout_s=args.timeout_s,
|
||||
label="response.created",
|
||||
)
|
||||
target = next(e for e in client.events.events if e.get("type") == "response.created")
|
||||
target_id = RealtimeEventCollector.response_id(target)
|
||||
assert target_id
|
||||
await wait_for(
|
||||
lambda: _events(client.events.events, target_id, "response.audio.delta"),
|
||||
timeout_s=args.timeout_s,
|
||||
label="response.audio.delta",
|
||||
)
|
||||
cursor = len(client.events.events)
|
||||
await client.stream_pcm16(interrupt + bytes(16_000 * 2 * 800 // 1000), chunk_ms=args.chunk_ms, realtime=True)
|
||||
await wait_for(
|
||||
lambda: _events(client.events.events, target_id, "response.done"),
|
||||
timeout_s=args.timeout_s,
|
||||
label="cancelled response.done",
|
||||
)
|
||||
await client.commit()
|
||||
await wait_for(
|
||||
lambda: any(
|
||||
e.get("type") == "response.done" and RealtimeEventCollector.response_id(e) != target_id
|
||||
for e in client.events.events[cursor:]
|
||||
),
|
||||
timeout_s=args.timeout_s,
|
||||
label="follow-up response.done",
|
||||
)
|
||||
events = client.events.events
|
||||
finally:
|
||||
try:
|
||||
await client.close_session(timeout_s=args.timeout_s)
|
||||
finally:
|
||||
await client.__aexit__(None, None, None)
|
||||
|
||||
done = _events(events, target_id, "response.done")
|
||||
done_index = events.index(done[0]) if len(done) == 1 else len(events)
|
||||
stale = (
|
||||
any(
|
||||
e.get("type") == "response.audio.delta" and RealtimeEventCollector.response_id(e) == target_id
|
||||
for e in events[done_index + 1 :]
|
||||
)
|
||||
if len(done) == 1
|
||||
else False
|
||||
)
|
||||
followup_audio = any(
|
||||
e.get("type") == "response.audio.delta" and RealtimeEventCollector.response_id(e) != target_id
|
||||
for e in events[cursor:]
|
||||
)
|
||||
ok = (
|
||||
len(done) == 1
|
||||
and isinstance(done[0].get("response"), dict)
|
||||
and done[0]["response"].get("status") == "cancelled"
|
||||
and done[0]["response"].get("status_details", {}).get("reason") == "turn_detected"
|
||||
and not stale
|
||||
and any(e.get("type") == "input_audio_buffer.committed" for e in events[cursor:])
|
||||
and followup_audio
|
||||
and not any(e.get("type") == "error" for e in events)
|
||||
)
|
||||
return {"ok": ok}
|
||||
@@ -21,7 +21,6 @@ from tests.e2e.online_serving.helpers.minicpmo_4_5_duplex import (
|
||||
from tests.e2e.online_serving.run_minicpmo_realtime_duplex_multi_session import (
|
||||
run_lifecycle_probes,
|
||||
)
|
||||
from tests.e2e.online_serving.run_minicpmo_realtime_duplex_server_vad import run_server_vad_interrupt
|
||||
from tests.e2e.online_serving.run_minicpmo_realtime_duplex_soft_interrupt import (
|
||||
run_soft_interrupt,
|
||||
)
|
||||
@@ -89,22 +88,3 @@ def test_duplex_soft_interrupt(omni_server, model_prefix: str, tmp_path: Path) -
|
||||
assert result["response_lifecycle_ok"] is True
|
||||
assert result["response_audio_contract_ok"] is True
|
||||
assert result["followup_response_transcript_ok"] is True
|
||||
|
||||
|
||||
@hardware_test(res={"cuda": "H100"}, num_cards=1)
|
||||
@pytest.mark.parametrize("omni_server", SERVER_PARAMS, indirect=True)
|
||||
def test_duplex_server_vad_hard_interrupt(omni_server, model_prefix: str) -> None:
|
||||
result = asyncio.run(
|
||||
run_server_vad_interrupt(
|
||||
SimpleNamespace(
|
||||
url=realtime_url(omni_server),
|
||||
model=omni_server.model,
|
||||
input_wav=str(validated_input_wav()),
|
||||
interrupt_wav=str(validated_soft_interrupt_wav()),
|
||||
ref_audio=str(resolve_ref_audio(model_prefix)),
|
||||
chunk_ms=200,
|
||||
timeout_s=180.0,
|
||||
)
|
||||
)
|
||||
)
|
||||
assert result["ok"] is True, json.dumps(result, ensure_ascii=False, indent=2)
|
||||
Reference in new issue
Block a user