diff --git a/tests/entrypoints/openai_api/test_tts_adapter.py b/tests/entrypoints/openai_api/test_tts_adapter.py index 100f4fc06..e070bfebd 100644 --- a/tests/entrypoints/openai_api/test_tts_adapter.py +++ b/tests/entrypoints/openai_api/test_tts_adapter.py @@ -10,11 +10,14 @@ from types import SimpleNamespace from typing import Any import pytest +from vllm.sampling_params import SamplingParams +from vllm_omni.entrypoints.openai.protocol.audio import OpenAICreateSpeechRequest from vllm_omni.entrypoints.openai.tts_adapters import ( TTS_ADAPTER_REGISTRY, ARTTSAdapter, DiffusionTTSAdapter, + SpeechServingContext, all_tts_model_types, detect_tts_model_type, resolve_adapter, @@ -115,6 +118,45 @@ def test_moss_tts_applies_request_max_new_tokens(adapter_cls): assert stage_defaults[0].max_tokens == 4096 +def _build_moss_tts_request(adapter_cls, mocker, *, request_seed): + server = mocker.Mock() + server._build_moss_tts_params = mocker.AsyncMock(return_value={}) + adapter = adapter_cls(SpeechServingContext(server=server)) + request = OpenAICreateSpeechRequest(input="hello", seed=request_seed) + + return asyncio.run( + adapter.build( + request, + [SamplingParams(seed=42)], + has_inline_ref_audio=False, + ) + ) + + +# Full-family coverage pins the adapter contract; only Nano consumes this seed end to end today. +@pytest.mark.parametrize("adapter_cls", [MossTTSAdapter, MossTTSNanoAdapter]) +@pytest.mark.parametrize("request_seed", [0, 1234]) +def test_moss_tts_request_seed_overrides_stage_default(adapter_cls, request_seed, mocker): + prepared = _build_moss_tts_request( + adapter_cls, + mocker, + request_seed=request_seed, + ) + + assert prepared.tts_params["seed"] == [request_seed] + + +@pytest.mark.parametrize("adapter_cls", [MossTTSAdapter, MossTTSNanoAdapter]) +def test_moss_tts_seed_falls_back_to_stage_default(adapter_cls, mocker): + prepared = _build_moss_tts_request( + adapter_cls, + mocker, + request_seed=None, + ) + + assert prepared.tts_params["seed"] == [42] + + def test_qwen3_tts_metadata(): assert Qwen3TTSAdapter.backend == "ar" assert issubclass(Qwen3TTSAdapter, ARTTSAdapter) diff --git a/vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py b/vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py index e51b14c67..3d6e99552 100644 --- a/vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py +++ b/vllm_omni/entrypoints/openai/tts_adapters/moss_tts.py @@ -1,4 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project """MOSS-TTS serving adapters (Nano + full family). Both variants share the same build/validate flow (``_build_moss_tts_params`` @@ -97,9 +98,13 @@ class _MossTTSAdapterBase(ARTTSAdapter): if voice_lower in server.uploaded_speakers and not has_inline_ref_audio: tts_params["voice_name"] = [voice_lower] tts_params["voice_created_at"] = [server._voice_created_at(voice_lower)] - # MOSS reads the resolved seed at build time (it samples internally). - if sampling_params_list and getattr(sampling_params_list[0], "seed", None) is not None: - tts_params["seed"] = [sampling_params_list[0].seed] + # MOSS samples internally from additional_information. build() runs + # before the shared path applies request.seed to SamplingParams. + seed = request.seed + if seed is None and sampling_params_list: + seed = getattr(sampling_params_list[0], "seed", None) + if seed is not None: + tts_params["seed"] = [int(seed)] if isinstance(tts_params.get("prompt_token_ids"), list): prompt_token_ids = tts_params.pop("prompt_token_ids") prompt = tokens_input(prompt_token_ids=prompt_token_ids)