Repository navigation
fix(model): declare real WAV sizes in the final streamed audio block - #3242
Merged
qbc2016 merged 2 commits intoOct 12, 2026
Conversation
The first audio delta of a streamed voice response carries the streaming WAV header, whose RIFF and data chunk sizes are the 0xFFFFFFFF placeholder. The accumulated final is_last block used to keep that placeholder, so consumers trusting the header (e.g. wave.getnframes, duration, seeking) saw garbage instead of the real audio length, unlike the non-streaming path which wraps the payload in a real header. Finalize the WAV header when the accumulator joins the stream deltas, so the completed audio block is a well-formed WAV.
Contributor
|
Thanks for the pull request. One thing worth sorting out before it is reviewed:
If you think it belongs here anyway, say so and a maintainer will take a look. |
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
AgentScope Version
2.0.10dev(main @ e4c1e07)Description
Summary
In streaming mode, the final
is_lastaudioDataBlockkept the streaming WAV header's0xFFFFFFFFRIFF/data placeholder sizes instead of the real chunk sizes. The completed audio block is now finalized into a well-formed WAV, matching what the non-streaming path produces for the same call.Why
_build_streaming_wav_header()(src/agentscope/_utils/_audio.py:47-73) deliberately sets the RIFF anddatachunk sizes to0xFFFFFFFFsince the total length isn't known yet, and its docstring states: "For a complete buffer use_build_wav_headerinstead, which declares real chunk sizes" (src/agentscope/_utils/_audio.py:64-65).src/agentscope/model/_openai_chat/_model.py:412,src/agentscope/model/_dashscope/_model.py:412). The stream accumulator then joins the deltas verbatim into the finalis_lastblock via_AccBase64Source.build(), so the completed artifact kept the placeholder sizes.src/agentscope/model/_openai_chat/_model.py:519, fix(model): wrap non-streaming chat audio in a real WAV header #3028), so streaming and non-streaming responses disagreed: consumers of the final block that trust the header —wave.getnframes(), duration, seeking, strict players — saw4294967295instead of the actual audio length.Changes
src/agentscope/_utils/_audio.py: add_finalize_streaming_wav(), which rewrites bytes 4-8 and 40-44 of a complete streaming buffer with the real RIFF (36 + data_len) anddata(data_len) sizes. Buffers without the placeholder header are returned unchanged, so complete WAVs and other audio payloads are untouched.src/agentscope/model/_utils.py:_AccBase64Source.build()finalizes the joined buffer whenmedia_type == "audio/wav". This is the single place where streamed audio deltas are joined for the final response, so both the OpenAI Chat and DashScope stream parsers are covered.Testing
TestOpenAIChatStream::test_stream_audio_final_block_declares_real_wav_sizes(tests/model_openai_chat_test.py) andTestDashScopeStream::test_stream_audio_final_block_declares_real_wav_sizes(tests/model_dashscope_test.py). They fail on unpatchedmain:and pass with this change (
2 passed); each test also parses the final block end-to-end withwave.openand assertsgetnframes().python3 -m pytest tests/model_openai_chat_test.py tests/model_dashscope_test.py→39 passed.python3 -m pytest tests(excluding the four files that fail to import without optional extras:blob_store_s3,mem0_agentscope_adapter,tui,webrtc_audio_transport) →2457 passed, 159 failed; the 159 failures are byte-identical to the same run on unpatchedmain(they require external services: MySQL, Qdrant, MongoDB, Ollama, DingTalk).pre-commit run --all-files→ all hooks Passed (mypy, black, flake8, pylint included).Checklist
Fixes #3245