[Core] Graduate MiniCPM-o 4.5 and PersonaPlex full-duplex serving out of experimental (#6196)
Signed-off-by: chickeyton <ngton2014@gmail.com> Signed-off-by: Gao Han <hgaoaf@connect.ust.hk> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk>
This commit is contained in:
179 files changed
+6462
-3832
No files matched your search
@@ -14,46 +14,59 @@ import numpy as np
|
||||
import pytest
|
||||
from starlette.websockets import WebSocketDisconnect
|
||||
|
||||
# Import the OpenAI entrypoints package at collection time, before pytest
|
||||
# attaches its caplog handlers to the root logger. vllm's api_server
|
||||
# transitively imports model_hosting_container_standards, whose import-time
|
||||
# configure_root_logger() raises the level of every handler then attached to
|
||||
# the root logger to ERROR. The serving runtime under test reaches this chain
|
||||
# lazily (runtime_bridge -> entrypoints.openai.protocol.audio), so without
|
||||
# this eager import the side effect fires mid-test and silences caplog for
|
||||
# the remainder of the session. Before the capability predicate moved to
|
||||
# vllm_omni.entrypoints.duplex.capability, this file imported the package at
|
||||
# module scope implicitly.
|
||||
import vllm_omni.entrypoints.openai # noqa: F401
|
||||
from vllm_omni.config.stage_config import DuplexSessionRuntimeConfig
|
||||
from vllm_omni.experimental.fullduplex.engine.duplex_control_client import DuplexControlRequestError
|
||||
from vllm_omni.experimental.fullduplex.engine.duplex_runtime import duplex_resource_request_id
|
||||
from vllm_omni.experimental.fullduplex.engine.lease import DuplexLeaseActivity
|
||||
from vllm_omni.experimental.fullduplex.engine.messages import DuplexFence, DuplexSessionLifecycleMessage
|
||||
from vllm_omni.experimental.fullduplex.minicpmo45 import (
|
||||
MiniCPMO45NativeDuplexServingAdapter,
|
||||
MiniCPMO45PcmAppendBuffer,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.minicpmo45.data_plane import (
|
||||
MiniCPMO45DataPlaneContext,
|
||||
MiniCPMO45DataPlaneSession,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.minicpmo45.runtime import (
|
||||
MiniCPMO45DuplexRuntimeExtension,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.minicpmo45.session import (
|
||||
MiniCPMO45ServingSessionState,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.openai import vad as realtime_vad
|
||||
from vllm_omni.experimental.fullduplex.openai.protocol import (
|
||||
DuplexCapabilities,
|
||||
from vllm_omni.engine.duplex.control_client import DuplexControlRequestError
|
||||
from vllm_omni.engine.duplex.lease import DuplexLeaseActivity
|
||||
from vllm_omni.engine.duplex.messages import DuplexFence, DuplexSessionLifecycleMessage
|
||||
from vllm_omni.engine.duplex.runtime import duplex_resource_request_id
|
||||
from vllm_omni.entrypoints.duplex import vad as realtime_vad
|
||||
from vllm_omni.entrypoints.duplex.protocol import (
|
||||
DuplexOverlapPolicy,
|
||||
DuplexPlaybackCommitPolicy,
|
||||
DuplexSession,
|
||||
DuplexSessionConfig,
|
||||
ResponseCreateOptions,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.openai.realtime_session import NativeRealtimeSessionProtocol
|
||||
from vllm_omni.experimental.fullduplex.openai.runtime_adapter import ServingRuntimeConfigError
|
||||
from vllm_omni.experimental.fullduplex.openai.serving import (
|
||||
from vllm_omni.entrypoints.duplex.realtime_session import NativeRealtimeSessionProtocol
|
||||
from vllm_omni.entrypoints.duplex.runtime_adapter import ServingRuntimeConfigError
|
||||
from vllm_omni.entrypoints.duplex.serving import (
|
||||
OmniDuplexSessionHandler,
|
||||
should_enable_duplex_endpoint,
|
||||
)
|
||||
from vllm_omni.experimental.fullduplex.openai.websocket import DuplexWebSocketActor
|
||||
from vllm_omni.experimental.fullduplex.output import attach_duplex_output_decision
|
||||
from vllm_omni.experimental.fullduplex.personaplex.serving_adapter import (
|
||||
from vllm_omni.entrypoints.duplex.websocket import DuplexWebSocketActor
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.duplex import (
|
||||
MiniCPMO45NativeDuplexServingAdapter,
|
||||
MiniCPMO45PcmAppendBuffer,
|
||||
)
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.duplex.capabilities import (
|
||||
minicpmo45_native_capabilities,
|
||||
)
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.duplex.data_plane import (
|
||||
MiniCPMO45DataPlaneContext,
|
||||
MiniCPMO45DataPlaneSession,
|
||||
)
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.duplex.runtime import (
|
||||
MiniCPMO45DuplexRuntimeExtension,
|
||||
)
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.duplex.session import (
|
||||
MiniCPMO45ServingSessionState,
|
||||
)
|
||||
from vllm_omni.model_executor.models.personaplex.duplex.serving_adapter import (
|
||||
PersonaPlexServingRuntimeAdapter,
|
||||
)
|
||||
from vllm_omni.outputs import OmniRequestOutput
|
||||
from vllm_omni.outputs.duplex import attach_duplex_output_decision
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
@@ -62,12 +75,12 @@ def test_native_input_append_supports_explicit_session_opt_out():
|
||||
session = DuplexSession(
|
||||
"sid-explicit-tts",
|
||||
DuplexSessionConfig(model="test-model"),
|
||||
capabilities=DuplexCapabilities.minicpmo45_native(),
|
||||
capabilities=minicpmo45_native_capabilities(),
|
||||
)
|
||||
|
||||
assert OmniDuplexSessionHandler._uses_native_input_append(session) is True
|
||||
|
||||
session.config.extra_body["minicpmo45_native_duplex"] = False
|
||||
session.config.extra_body["native_duplex"] = False
|
||||
assert OmniDuplexSessionHandler._uses_native_input_append(session) is False
|
||||
|
||||
|
||||
@@ -293,7 +306,7 @@ class FakeEngineClient:
|
||||
|
||||
class FakeChatService:
|
||||
duplex_serving_adapter_path = (
|
||||
"vllm_omni.experimental.fullduplex.minicpmo45.serving_adapter.MiniCPMO45ServingRuntimeAdapter"
|
||||
"vllm_omni.model_executor.models.minicpmo_4_5.duplex.serving_adapter.MiniCPMO45ServingRuntimeAdapter"
|
||||
)
|
||||
|
||||
def __init__(self, engine_client: FakeEngineClient) -> None:
|
||||
@@ -844,7 +857,7 @@ def test_native_response_options_ignore_private_runtime_config():
|
||||
session = DuplexSession(
|
||||
session_id="sid-response-private-config",
|
||||
config=DuplexSessionConfig(),
|
||||
capabilities=DuplexCapabilities.minicpmo45_native(),
|
||||
capabilities=minicpmo45_native_capabilities(),
|
||||
)
|
||||
|
||||
handler = OmniDuplexSessionHandler(chat_service=FakeChatService(FakeEngineClient()))
|
||||
@@ -878,7 +891,7 @@ def test_native_response_create_rejects_unwired_generation_options(payload):
|
||||
session = DuplexSession(
|
||||
session_id="sid-response-unwired-options",
|
||||
config=DuplexSessionConfig(),
|
||||
capabilities=DuplexCapabilities.minicpmo45_native(),
|
||||
capabilities=minicpmo45_native_capabilities(),
|
||||
)
|
||||
|
||||
handler = OmniDuplexSessionHandler(chat_service=FakeChatService(FakeEngineClient()))
|
||||
@@ -1244,7 +1257,7 @@ async def test_minicpmo_native_session_update_rejects_native_duplex_mode_flip():
|
||||
{
|
||||
"type": "turn.signal",
|
||||
"event": "session.update",
|
||||
"payload": {"extra_body": {"minicpmo45_native_duplex": False}},
|
||||
"payload": {"extra_body": {"native_duplex": False}},
|
||||
}
|
||||
)
|
||||
ws.put(
|
||||
@@ -1581,7 +1594,7 @@ def _native_session_create(
|
||||
event["session"]["model"] = "openbmb/MiniCPM-o-4_5"
|
||||
event["session"]["modalities"] = list(modalities or ["text"])
|
||||
event["session"]["instructions"] = "You are a concise assistant."
|
||||
event["session"]["extra_body"] = {"minicpmo45_native_duplex": True}
|
||||
event["session"]["extra_body"] = {"native_duplex": True}
|
||||
return event
|
||||
|
||||
|
||||
@@ -1598,7 +1611,7 @@ def _native_realtime_session_update(
|
||||
"modalities": list(modalities or ["text"]),
|
||||
"instructions": "You are a concise assistant.",
|
||||
"idle_timeout_s": 1,
|
||||
"extra_body": {"minicpmo45_native_duplex": True},
|
||||
"extra_body": {"native_duplex": True},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1674,7 +1687,7 @@ def _install_direct_silence_scheduler(
|
||||
handler: OmniDuplexSessionHandler,
|
||||
session: DuplexSession,
|
||||
) -> None:
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
|
||||
async def _schedule(payload: object, **kwargs: Any) -> bool:
|
||||
if handler._native_silence_continuation_is_stale(
|
||||
@@ -1819,7 +1832,7 @@ def test_legacy_auto_overlap_policy_falls_back_to_listen_only():
|
||||
overlap_policy=DuplexSessionConfig._normalize_overlap_policy("auto"),
|
||||
),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
|
||||
decision = handler._overlap_decision(
|
||||
session,
|
||||
@@ -2066,11 +2079,11 @@ def test_auto_response_post_response_silence_remains_model_owned_input():
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_response_terminal_advances_open_realtime_input_without_commit():
|
||||
handler, session = _auto_response_context("sid-auto-realtime-open-input", playback_active=True)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.active_response_id
|
||||
assert response_id is not None
|
||||
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.input_since_commit = True
|
||||
native.speech_since_commit = True
|
||||
session.accumulate_overlap_speech(800)
|
||||
@@ -2113,11 +2126,11 @@ async def test_auto_response_terminal_allows_next_model_unit_while_playback_drai
|
||||
"sid-auto-realtime-open-before-overlap",
|
||||
playback_active=True,
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.active_response_id
|
||||
assert response_id is not None
|
||||
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.input_since_commit = True
|
||||
native.speech_since_commit = True
|
||||
session.end_response(commit_text=False, preserve_request=True)
|
||||
@@ -2147,11 +2160,11 @@ async def test_auto_response_terminal_allows_next_model_unit_while_playback_drai
|
||||
@pytest.mark.asyncio
|
||||
async def test_realtime_committed_overlap_promotes_after_response_done():
|
||||
handler, session = _auto_response_context("sid-auto-realtime-committed-input", playback_active=True)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.active_response_id
|
||||
assert response_id is not None
|
||||
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.committed_audio_payload = _native_audio_payload()
|
||||
native.deferred_response_create = True
|
||||
session.accumulate_overlap_speech(1000)
|
||||
@@ -3061,7 +3074,7 @@ async def test_native_append_propagates_current_turn_fence_to_engine():
|
||||
session_id="sid-fenced-append",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.turn_id = 2
|
||||
session.begin_response(turn_id=2)
|
||||
ws = TimedWebSocket()
|
||||
@@ -3091,7 +3104,7 @@ async def test_minicpmo_auto_response_tts_segment_boundary_appends_silence_unit(
|
||||
session_id="sid-segment-boundary",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.begin_response(turn_id=0)
|
||||
session.turn_id = 1
|
||||
session.bind_request(request_id)
|
||||
@@ -3140,7 +3153,7 @@ async def test_minicpmo_auto_response_pre_response_tts_boundary_continues_model_
|
||||
session_id="sid-pre-response-boundary",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
_install_direct_silence_scheduler(handler, session)
|
||||
ws = TimedWebSocket()
|
||||
@@ -3187,11 +3200,11 @@ async def test_minicpmo_pre_response_continuation_drops_after_model_turn_ends():
|
||||
session_id="sid-stale-pre-response-boundary",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
model_turn_id = session.turn_id
|
||||
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
|
||||
async def _stale_before_append(payload: object, **kwargs: Any) -> bool:
|
||||
session.complete_model_turn(model_turn_id)
|
||||
@@ -3237,7 +3250,7 @@ async def test_minicpmo_auto_response_continuation_stops_at_large_safety_boundar
|
||||
session_id="sid-long-response",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.begin_response(turn_id=0)
|
||||
session.turn_id = 1
|
||||
session.bind_request(request_id)
|
||||
@@ -3283,10 +3296,10 @@ async def test_minicpmo_auto_response_boundary_listen_closes_response():
|
||||
session_id="sid-boundary-listen",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.begin_response(turn_id=0)
|
||||
session.bind_request(request_id)
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.continuation_owner_id = f"response:{response_id}"
|
||||
native.continuation_units = handler._NATIVE_AUTO_RESPONSE_MAX_CONTINUATION_UNITS
|
||||
_install_direct_silence_scheduler(handler, session)
|
||||
@@ -3313,6 +3326,11 @@ async def test_minicpmo_auto_response_boundary_listen_closes_response():
|
||||
assert emitted is True
|
||||
assert engine.appended == []
|
||||
assert ws.sent_types() == ["response.listen", "response.done"]
|
||||
# The listen terminates the precreated response: clients demultiplex the
|
||||
# decision by its id, so the payload must carry the response identity.
|
||||
listen_payload, done_payload = ws.sent[-2], ws.sent[-1]
|
||||
assert listen_payload["response_id"] == response_id
|
||||
assert done_payload["response_id"] == response_id
|
||||
assert session.active_response_id is None
|
||||
assert session.active_request_id == request_id
|
||||
assert session.turn_id == 1
|
||||
@@ -3327,10 +3345,10 @@ async def test_minicpmo_auto_response_boundary_allows_next_model_turn_response()
|
||||
session_id="sid-boundary-next-turn",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.begin_response(turn_id=0)
|
||||
session.bind_request(request_id)
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.continuation_owner_id = f"response:{session.active_response_id}"
|
||||
native.continuation_units = handler._NATIVE_AUTO_RESPONSE_MAX_CONTINUATION_UNITS
|
||||
ws = TimedWebSocket()
|
||||
@@ -3382,10 +3400,10 @@ async def test_minicpmo_auto_response_boundary_does_not_close_new_epoch_response
|
||||
session_id="sid-boundary-race",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
old_response_id = session.begin_response(turn_id=0)
|
||||
session.bind_request(request_id)
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
native.continuation_owner_id = f"response:{old_response_id}"
|
||||
native.continuation_units = handler._NATIVE_AUTO_RESPONSE_MAX_CONTINUATION_UNITS
|
||||
sent: list[dict[str, Any]] = []
|
||||
@@ -3418,7 +3436,7 @@ async def test_minicpmo_auto_response_pre_speak_listen_continues_same_response()
|
||||
session_id="sid-pre-speak-listen",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.begin_response(turn_id=0)
|
||||
session.turn_id = 1
|
||||
session.bind_request(request_id)
|
||||
@@ -3458,7 +3476,7 @@ async def test_minicpmo_auto_response_listen_before_response_keeps_resumable_req
|
||||
session_id="sid-listen-before-response",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
ws = TimedWebSocket()
|
||||
|
||||
@@ -3482,7 +3500,7 @@ async def test_minicpmo_auto_response_listen_before_response_keeps_resumable_req
|
||||
assert close_reason is None
|
||||
assert emitted is True
|
||||
assert session.active_request_id == request_id
|
||||
assert not handler._minicpmo_data_plane.is_terminal(request_id)
|
||||
assert not handler._serving_runtime_adapter.data_plane.is_terminal(request_id)
|
||||
assert ws.sent_types().count("response.listen") == 1
|
||||
|
||||
|
||||
@@ -3666,7 +3684,7 @@ async def test_minicpmo_auto_response_post_speak_listen_continues_same_response(
|
||||
session_id="sid-post-speak-listen",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.begin_response(turn_id=0)
|
||||
session.turn_id = 1
|
||||
session.bind_request(request_id)
|
||||
@@ -3707,7 +3725,7 @@ async def test_minicpmo_auto_response_turn_end_preserves_resumable_request():
|
||||
session_id="sid-turn-end-preserve-request",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.begin_response(turn_id=0)
|
||||
session.bind_request(request_id)
|
||||
ws = TimedWebSocket()
|
||||
@@ -3746,7 +3764,7 @@ async def test_minicpmo_auto_response_empty_turn_end_emits_model_listen():
|
||||
session_id="sid-empty-turn-end",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
ws = TimedWebSocket()
|
||||
|
||||
@@ -3789,7 +3807,7 @@ async def test_minicpmo_auto_response_drops_late_audio_from_completed_model_turn
|
||||
session_id="sid-late-completed-turn",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
session.complete_model_turn(0)
|
||||
ws = TimedWebSocket()
|
||||
@@ -4015,7 +4033,7 @@ async def test_duplex_auto_response_empty_terminal_does_not_create_empty_respons
|
||||
)
|
||||
|
||||
native_results = list(
|
||||
handler._minicpmo_data_plane.project(
|
||||
handler._serving_runtime_adapter.data_plane.project(
|
||||
{"data_plane_outputs": [output]},
|
||||
context=_data_plane_context(session),
|
||||
)
|
||||
@@ -4030,7 +4048,7 @@ async def test_duplex_auto_response_empty_terminal_does_not_create_empty_respons
|
||||
assert "response.created" not in [m.get("type") for m in sent]
|
||||
assert "response.done" not in [m.get("type") for m in sent]
|
||||
assert session.turn_id == 1
|
||||
assert not handler._minicpmo_data_plane.is_terminal(request_id)
|
||||
assert not handler._serving_runtime_adapter.data_plane.is_terminal(request_id)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -4040,11 +4058,11 @@ async def test_minicpmo_native_duplex_session_close_cleans_auto_response_data_pl
|
||||
engine = FakeEngineClient()
|
||||
chat_service = FakeChatService(engine)
|
||||
handler = OmniDuplexSessionHandler(chat_service=chat_service, config_timeout_s=0.1, idle_timeout_s=1)
|
||||
handler._minicpmo_data_plane.slice_cumulative_audio(request_id, np.zeros(24000, dtype=np.float32))
|
||||
handler._minicpmo_data_plane.segment_text_delta(request_id, "hello")
|
||||
handler._minicpmo_data_plane.mark_terminal(request_id)
|
||||
handler._serving_runtime_adapter.data_plane.slice_cumulative_audio(request_id, np.zeros(24000, dtype=np.float32))
|
||||
handler._serving_runtime_adapter.data_plane.segment_text_delta(request_id, "hello")
|
||||
handler._serving_runtime_adapter.data_plane.mark_terminal(request_id)
|
||||
native = MiniCPMO45ServingSessionState()
|
||||
handler._minicpmo_sessions[session_id] = native
|
||||
handler._serving_runtime_adapter.session_states[session_id] = native
|
||||
|
||||
event = _native_session_create(session_id)
|
||||
event["session"]["extra_body"]["auto_response"] = True
|
||||
@@ -4054,8 +4072,8 @@ async def test_minicpmo_native_duplex_session_close_cleans_auto_response_data_pl
|
||||
|
||||
await handler.handle_session(ws)
|
||||
|
||||
assert not handler._minicpmo_data_plane.has_request(request_id)
|
||||
assert session_id not in handler._minicpmo_sessions
|
||||
assert not handler._serving_runtime_adapter.data_plane.has_request(request_id)
|
||||
assert session_id not in handler._serving_runtime_adapter.session_states
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -4923,7 +4941,7 @@ async def test_realtime_disconnect_detaches_resumable_session_without_runtime_cl
|
||||
("sid-resumable-disconnect", DuplexLeaseActivity.DETACH),
|
||||
]
|
||||
assert handler._registry.get("sid-resumable-disconnect") is not None
|
||||
assert "sid-resumable-disconnect" in handler._minicpmo_sessions
|
||||
assert "sid-resumable-disconnect" in handler._serving_runtime_adapter.session_states
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -4947,7 +4965,7 @@ async def test_realtime_initial_resume_token_delivery_failure_closes_unrecoverab
|
||||
|
||||
assert engine.closed == [("sid-created-delivery-failure", "disconnect")]
|
||||
assert handler._registry.get("sid-created-delivery-failure") is None
|
||||
assert "sid-created-delivery-failure" not in handler._minicpmo_sessions
|
||||
assert "sid-created-delivery-failure" not in handler._serving_runtime_adapter.session_states
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -4983,7 +5001,7 @@ async def test_realtime_send_after_created_disconnect_preserves_resumable_sessio
|
||||
("sid-created-then-disconnected", DuplexLeaseActivity.DETACH),
|
||||
]
|
||||
assert handler._registry.get("sid-created-then-disconnected") is not None
|
||||
assert "sid-created-then-disconnected" in handler._minicpmo_sessions
|
||||
assert "sid-created-then-disconnected" in handler._serving_runtime_adapter.session_states
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -5007,7 +5025,7 @@ async def test_realtime_resume_rotates_token_replays_and_preserves_runtime_ident
|
||||
second = TimedWebSocket(receive_timeout_s=0.1)
|
||||
second.query_params = {
|
||||
"model": "openbmb/MiniCPM-o-4_5",
|
||||
"minicpmo45_native_duplex": "1",
|
||||
"native_duplex": "1",
|
||||
"resume": "1",
|
||||
}
|
||||
second.put(
|
||||
@@ -5083,7 +5101,7 @@ async def test_realtime_resume_preserves_append_tail_order_across_connections():
|
||||
assert created is not None
|
||||
session = handler._registry.get("sid-resume-append-order")
|
||||
assert session is not None
|
||||
native_state = handler._minicpmo_session_state(session)
|
||||
native_state = handler._runtime_session_state(session)
|
||||
first_scheduler = native_state.silence_continuation_scheduler
|
||||
assert first_scheduler is not None
|
||||
first.put(
|
||||
@@ -5101,7 +5119,7 @@ async def test_realtime_resume_preserves_append_tail_order_across_connections():
|
||||
second = TimedWebSocket(receive_timeout_s=0.5)
|
||||
second.query_params = {
|
||||
"model": "openbmb/MiniCPM-o-4_5",
|
||||
"minicpmo45_native_duplex": "1",
|
||||
"native_duplex": "1",
|
||||
"resume": "1",
|
||||
}
|
||||
second.put(
|
||||
@@ -5402,7 +5420,7 @@ async def test_realtime_disconnect_grace_cancels_only_orphan_response():
|
||||
assert engine.aborted == ["orphan-request"]
|
||||
assert orphan_task.cancelled()
|
||||
assert session.active_response_id is None
|
||||
assert "sid-grace-response" in handler._minicpmo_sessions
|
||||
assert "sid-grace-response" in handler._serving_runtime_adapter.session_states
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -5453,7 +5471,7 @@ async def test_realtime_idle_ttl_lifecycle_removes_detached_serving_projection()
|
||||
await asyncio.sleep(0.05)
|
||||
|
||||
assert handler._registry.get("sid-idle-expired") is None
|
||||
assert "sid-idle-expired" not in handler._minicpmo_sessions
|
||||
assert "sid-idle-expired" not in handler._serving_runtime_adapter.session_states
|
||||
assert "sid-idle-expired" not in handler._session_tasks
|
||||
assert engine.closed == []
|
||||
|
||||
@@ -6428,7 +6446,7 @@ async def test_minicpmo_native_duplex_separates_public_and_runtime_config(monkey
|
||||
return [0.25] * 1600, 16000
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.experimental.fullduplex.minicpmo45.MiniCPMO45NativeDuplexServingAdapter.resolve_ref_audio",
|
||||
"vllm_omni.model_executor.models.minicpmo_4_5.duplex.MiniCPMO45NativeDuplexServingAdapter.resolve_ref_audio",
|
||||
fake_resolve_ref_audio,
|
||||
)
|
||||
event = _native_session_create("sid-native-ref-audio", modalities=["text", "audio"])
|
||||
@@ -6554,7 +6572,7 @@ async def test_minicpmo_native_duplex_preserves_ref_audio_channels_until_normali
|
||||
return np.tile(np.array([[0.25, -0.25], [0.5, -0.5]], dtype=np.float32), (800, 1)), 16000
|
||||
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.experimental.fullduplex.minicpmo45.adapter.MediaConnector",
|
||||
"vllm_omni.model_executor.models.minicpmo_4_5.duplex.adapter.MediaConnector",
|
||||
FakeMediaConnector,
|
||||
)
|
||||
|
||||
@@ -6580,7 +6598,7 @@ async def test_minicpmo_native_duplex_rejects_ref_audio_path():
|
||||
handler = OmniDuplexSessionHandler(chat_service=chat_service, config_timeout_s=0.1, idle_timeout_s=1)
|
||||
event = _native_session_create("sid-native-ref-path")
|
||||
event["session"]["extra_body"] = {
|
||||
"minicpmo45_native_duplex": True,
|
||||
"native_duplex": True,
|
||||
"ref_audio_path": "/tmp/ref.wav",
|
||||
}
|
||||
ws = TimedWebSocket()
|
||||
@@ -6603,7 +6621,7 @@ def test_minicpmo_native_duplex_explicit_barge_in_request_interrupts():
|
||||
session = DuplexSession(
|
||||
session_id="sid-native-explicit-barge-disabled",
|
||||
config=DuplexSessionConfig(overlap_policy=DuplexOverlapPolicy.BARGE_IN_ON_SPEECH.value),
|
||||
capabilities=DuplexCapabilities.minicpmo45_native(),
|
||||
capabilities=minicpmo45_native_capabilities(),
|
||||
)
|
||||
payload = {
|
||||
"type": "audio",
|
||||
@@ -6767,9 +6785,9 @@ async def test_minicpmo_auto_response_restarts_drain_when_append_races_idle_exit
|
||||
session_id="sid-native-late-output",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
session.bind_request(request_id)
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
projected_batches: list[object] = []
|
||||
projected = asyncio.Event()
|
||||
|
||||
@@ -7521,7 +7539,7 @@ async def test_minicpmo_native_auto_response_preserves_silence_continuations_acr
|
||||
session.capabilities.chunk_period_ms = 50
|
||||
session.bind_request(request_id)
|
||||
response_id = session.begin_response(turn_id=session.turn_id)
|
||||
native = handler._minicpmo_session_state(session)
|
||||
native = handler._runtime_session_state(session)
|
||||
scheduler = native.silence_continuation_scheduler
|
||||
assert scheduler is not None
|
||||
|
||||
@@ -7598,7 +7616,7 @@ async def test_minicpmo_native_duplex_uses_segment_text_metadata_for_transcript_
|
||||
|
||||
async def emit(output: object) -> None:
|
||||
result = {"data_plane_outputs": [output]}
|
||||
for native_result in handler._minicpmo_data_plane.project(
|
||||
for native_result in handler._serving_runtime_adapter.data_plane.project(
|
||||
result,
|
||||
context=_data_plane_context(session),
|
||||
):
|
||||
@@ -7682,7 +7700,7 @@ async def test_minicpmo_native_duplex_continuous_speak_reuses_active_response_un
|
||||
|
||||
async def emit(output: object) -> None:
|
||||
result = {"data_plane_outputs": [output]}
|
||||
for native_result in handler._minicpmo_data_plane.project(
|
||||
for native_result in handler._serving_runtime_adapter.data_plane.project(
|
||||
result,
|
||||
context=_data_plane_context(session),
|
||||
):
|
||||
@@ -7932,7 +7950,7 @@ async def test_minicpmo_auto_response_segment_complete_continues_until_model_lis
|
||||
session_id="sid-native-segment-complete",
|
||||
config=DuplexSessionConfig(extra_body={"auto_response": True}),
|
||||
)
|
||||
session.capabilities = DuplexCapabilities.minicpmo45_native()
|
||||
session.capabilities = minicpmo45_native_capabilities()
|
||||
response_id = session.begin_response()
|
||||
request_id = "duplex-sid-native-segment-complete-stage0"
|
||||
session.bind_request(request_id)
|
||||
@@ -7968,7 +7986,7 @@ async def test_minicpmo_auto_response_segment_complete_continues_until_model_lis
|
||||
assert emitted is False
|
||||
assert session.active_response_id == response_id
|
||||
assert session.active_request_id == request_id
|
||||
assert not handler._minicpmo_data_plane.is_terminal(request_id)
|
||||
assert not handler._serving_runtime_adapter.data_plane.is_terminal(request_id)
|
||||
assert sent == []
|
||||
assert len(engine.appended) == 1
|
||||
_, mode, payload, final = engine.appended[0]
|
||||
|
||||
Reference in new issue
Block a user