Repository navigation
Add SSE stream_format support for audio speech streaming - #4490
Conversation
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
linyueqian
left a comment
There was a problem hiding this comment.
Thanks @syd520zy, this is the right approach for SSE and much more complete than #4185 (which accepts stream_format="sse" but falls through to a single non-streaming blob). A few items before merge:
CI blockers
- DCO is failing (
action_required): commits need a sign-off. Fix and force-push:Make sure your gitgit rebase --signoff main # or: git commit --amend --signoff for a single commit git push --force-with-leaseuser.emailmatches your GitHub-verified email. - pre-commit is failing. Run locally and push the result:
pre-commit run --all-files(ruff/format usually auto-fixes). - FYI
buildanddocspass, but there's no unit-test job among the triggered checks, so the new tests aren't being exercised by CI yet. Worth confirming they pass locally / getting the full test CI triggered.
Event field name diverges from OpenAI (should be audio, not delta)
The payload is currently {"type":"speech.audio.delta","delta":"<b64>","response_format":"pcm"}. OpenAI's speech.audio.delta schema (required fields type + audio) puts the base64 chunk in audio:
{"type":"speech.audio.delta","audio":"<base64-encoded-audio-data>"}delta is the Realtime API convention (response.audio.delta), which is a different event. A stock OpenAI client reading event.audio would get nothing here. Suggest renaming delta -> audio (keeping response_format as an extra field is fine). OpenAI's speech.audio.done also carries a usage object; optional to add.
Tests
Happy path is well covered. Consider adding the negative paths: speech.audio.error (exception inside the generator), sse + non-pcm/wav -> 4xx, and sse + speed != 1.0 -> 4xx. The existing test_sse_streaming assertion will also need updating once the field is renamed to audio.
Optional follow-up
#4185's one useful idea is treating stream_format="audio" as a raw-streaming trigger (OpenAI uses stream_format as the switch). Folding that in here would cover both audio and sse, and let us close #4185 to avoid the merge conflict (both touch create_speech + the stream_format default/validator).
hsliuustc0106
left a comment
There was a problem hiding this comment.
SSE streaming implementation looks correct. Tests cover the new behavior well.
8b497bb to
0788afe
Compare
03525a9 to
0788afe
Compare
Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: faceless void <100186390+syd520zy@users.noreply.github.com> Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: syd520zy <529477025@qq.com>
…block (vllm-project#4449) Signed-off-by: Nick Cao <ncao@redhat.com> Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: Chendi Xue <chendi.xue@intel.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Signed-off-by: syd520zy <529477025@qq.com>
…m-project#4293) Signed-off-by: kTorp <Kristoffer.Torp@amd.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: Junhong Liu <98734602+LJH-LBJ@users.noreply.github.com> Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: lishunyang <lishunyang12@163.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: syd520zy <529477025@qq.com>
…I] (vllm-project#1386) Signed-off-by: Hyoseop Song <crad_on25@naver.com> Signed-off-by: Hyoseop Song <crad_on25@naver.com> Co-authored-by: Zhou Taichang <tzhouam@connect.ust.hk> Co-authored-by: Alicia <115451386+congw729@users.noreply.github.com> Signed-off-by: syd520zy <529477025@qq.com>
Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com> Signed-off-by: syd520zy <529477025@qq.com>
b8899a2 to
5bd73f5
Compare
|
Hi @linyueqian , |
linyueqian
left a comment
There was a problem hiding this comment.
Approving. Re-reviewed the latest revision and all earlier feedback is addressed, and I ran a live end-to-end smoke against a real Qwen3-TTS deploy.
Review items resolved
- Event field renamed
deltatoaudio, matching OpenAI'sspeech.audio.deltaschema. The docstring spelling outaudio(Speech API) vsdelta(Realtime API) is a nice touch. - Negative-path tests added: mid-stream
speech.audio.error, sse + non-pcm/wav returns 400, sse + speed != 1.0 returns 400. stream=Trueraw-audio precedence over sse is preserved and covered by a test.- CI is green (DCO, pre-commit, build, docs).
E2E smoke (vLLM 0.23, this PR's head, Qwen3-TTS-1.7B-CustomVoice on an H20-class GPU)
stream_format="sse"+ pcm: returnstext/event-stream, a sequence ofspeech.audio.deltaevents each carrying a base64audiochunk plusresponse_format, terminated byspeech.audio.done. Decoding and concatenating the deltas yields valid 24kHz mono PCM, and Whisper transcribes it back to the exact input text.stream_format="sse"+ wav: same envelope, reassembles to a valid WAV.stream=True+stream_format="sse": still returns rawaudio/pcmwith no SSE framing (precedence intact).- sse + mp3 returns 400, sse + speed=2.0 returns 400, both rejected before any stream begins.
Non-blocking nits (fine to defer)
- OpenAI's
speech.audio.donealso carries ausageobject, which this omits. - The
stream_format="audio"raw-streaming trigger you deferred to a follow-up is fine to leave for now.
Nice work, thanks for the thorough iteration.
Signed-off-by: syd520zy <529477025@qq.com>
Head branch was pushed to by a user without write access
…io_sse_stream # Conflicts: # vllm_omni/entrypoints/openai/serving_video_stream.py
|
@linyueqian The resulting request behavior is:
I also fixed the two failing tests:
The previous failures happened because the tests strictly expected an endpoint-level |
Signed-off-by: syd520zy <529477025@qq.com>
21f5dd0 to
9d1a6b3
Compare
Signed-off-by: syd520zy <529477025@qq.com>
Head branch was pushed to by a user without write access
# Conflicts: # vllm_omni/entrypoints/openai/serving_speech.py
…t#4490) Signed-off-by: syd520zy <529477025@qq.com> Signed-off-by: faceless void <100186390+syd520zy@users.noreply.github.com> Signed-off-by: Nick Cao <ncao@redhat.com> Signed-off-by: Chendi Xue <chendi.xue@intel.com> Signed-off-by: kTorp <Kristoffer.Torp@amd.com> Signed-off-by: Junhong Liu <98734602+LJH-LBJ@users.noreply.github.com> Signed-off-by: Hongsheng Liu <liuhongsheng4@huawei.com> Signed-off-by: Hyoseop Song <crad_on25@naver.com> Signed-off-by: Hyoseop Song <crad_on25@naver.com> Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com> Co-authored-by: Nick Cao <ncao@redhat.com> Co-authored-by: Chendi.Xue <chendi.xue@intel.com> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com> Co-authored-by: Kristoffer <kristoffer.torp@amd.com> Co-authored-by: Junhong Liu <ljh_lbj@163.com> Co-authored-by: lishunyang <lishunyang12@163.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Hyoseop Song <crad_on25@naver.com> Co-authored-by: Zhou Taichang <tzhouam@connect.ust.hk> Co-authored-by: Alicia <115451386+congw729@users.noreply.github.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
Summary
This PR adds
stream_format="sse"support to/v1/audio/speech.When
stream_format="sse"is requested withoutstream=True, the speech endpoint now returnstext/event-streamevents:speech.audio.deltaspeech.audio.donespeech.audio.errorEach
speech.audio.deltacontains a base64-encoded audio chunk in thedeltafield, plus theresponse_format.The existing raw audio streaming behavior remains the default streaming path. If a request sets both
stream=Trueandstream_format="sse", the endpoint still uses the existing non-SSE raw audio stream path and returnsaudio/pcmoraudio/wav.Changes
stream_format="sse"forOpenAICreateSpeechRequest.stream=Trueraw audio streaming behavior higher priority thanstream_format="sse".stream_format="sse"as a streaming generation request internally so multimodal output is emitted as deltas.text/event-streamto/v1/audio/speechOpenAPI response metadata.stream_format="sse"validationstream=True + stream_format="sse"preserving raw audio stream behaviorstream_format="sse"to be rejected for/v1/audio/speech.Compatibility
This preserves the current streaming default:
stream=Truecontinues to return raw audio bytes.stream=True + stream_format="sse"continues to return raw audio bytes.stream_format="sse"withoutstream=True.Open Question
Should we consider changing the default streaming behavior to SSE in a follow-up, to better match OpenAI-style streaming semantics? That would be a breaking behavior change for existing raw-audio streaming clients, so this PR keeps the current default unchanged.