[Bugfix] Reject /v1/audio/speech for Qwen omni models (#2763)
Signed-off-by: Bvicii <yizhanhuang2002@gmail.com>
This commit is contained in:
1 parent
cf1fcd5acf
commit
4fb078a031
2 files changed
+44
No files matched your search
@@ -684,6 +684,32 @@ class TestTTSMethods:
|
||||
assert server._is_tts is True
|
||||
assert server._tts_stage is mock_stage
|
||||
|
||||
def test_prepare_speech_rejects_non_tts_omni_model(self, mocker: MockerFixture):
|
||||
"""Multi-stage omni models (e.g. Qwen3-Omni) must not use /v1/audio/speech."""
|
||||
mock_engine_client = mocker.MagicMock()
|
||||
mock_engine_client.errored = False
|
||||
mock_engine_client.tts_max_instructions_length = None
|
||||
|
||||
# Simulate Qwen3-Omni: multiple stages, none in _TTS_MODEL_STAGES
|
||||
thinker = SimpleNamespace(engine_args=SimpleNamespace(model_stage="thinker"), tts_args={})
|
||||
talker = SimpleNamespace(engine_args=SimpleNamespace(model_stage="talker"), tts_args={})
|
||||
code2wav = SimpleNamespace(engine_args=SimpleNamespace(model_stage="code2wav"), tts_args={})
|
||||
mock_engine_client.stage_configs = [thinker, talker, code2wav]
|
||||
|
||||
mock_models = mocker.MagicMock()
|
||||
mock_models.is_base_model.return_value = True
|
||||
server = OmniOpenAIServingSpeech(
|
||||
engine_client=mock_engine_client,
|
||||
models=mock_models,
|
||||
request_logger=mocker.MagicMock(),
|
||||
)
|
||||
assert server._is_tts is False
|
||||
|
||||
request = OpenAICreateSpeechRequest(input="Hello world")
|
||||
with pytest.raises(ValueError, match="only supported for dedicated TTS models"):
|
||||
asyncio.run(server._prepare_speech_generation(request))
|
||||
server.shutdown()
|
||||
|
||||
def test_estimate_prompt_len_fallback(self, speech_server):
|
||||
"""Test prompt length estimation falls back to 2048 when model is unavailable."""
|
||||
tts_params = {"text": ["Hello"], "task_type": ["CustomVoice"]}
|
||||
|
||||
@@ -1471,6 +1471,24 @@ class OmniOpenAIServingSpeech(OpenAIServing, AudioMixin):
|
||||
ph_len = await self._estimate_prompt_len_async(tts_params)
|
||||
prompt = {"prompt_token_ids": [1] * ph_len, "additional_information": tts_params}
|
||||
else:
|
||||
# Qwen omni models (Qwen3-Omni, Qwen2.5-Omni) use a "talker"
|
||||
# stage whose preprocess requires chat-templated tokens. The
|
||||
# async-chunk orchestrator prewarms the talker via
|
||||
# compute_talker_prompt_ids_length(), which scans for Qwen
|
||||
# chat-template markers (im_start_token_id 151644). A raw-text
|
||||
# prompt produces a 1-token placeholder that crashes the talker's
|
||||
# prefill/decode handoff. Reject early with an actionable message.
|
||||
stage_names = {
|
||||
getattr(getattr(s, "engine_args", None), "model_stage", None) for s in self.engine_client.stage_configs
|
||||
}
|
||||
if "talker" in stage_names:
|
||||
raise ValueError(
|
||||
"The /v1/audio/speech endpoint is only supported for "
|
||||
"dedicated TTS models (e.g., Qwen3-TTS, Voxtral, Fish "
|
||||
"Speech, CosyVoice3, OmniVoice, VoxCPM2). For omni "
|
||||
"models like Qwen3-Omni, use /v1/chat/completions with "
|
||||
'\'"modalities": ["audio"]\' instead.'
|
||||
)
|
||||
tts_params = {}
|
||||
prompt = {"prompt": request.input}
|
||||
|
||||
|
||||
Reference in new issue
Block a user