Repository navigation
fix(vllm): keep reasoning out of streamed content - #12873
Open
pos-ei-don wants to merge 1 commit into
Open
pos-ei-don wants to merge 1 commit into
pos-ei-don wants to merge 1 commit into
Conversation
With a reasoning parser configured, the streaming path sent every raw text delta as ChatDelta.content. LocalAI uses the backend's ChatDeltas as they are, so with thinking enabled the reasoning arrived as delta.content, and the final chunk then repeated the whole answer and the reasoning a second time. Non-streaming requests were split correctly. Use vLLM's composed parser (ParserManager.get_parser, vLLM >= 0.30), the one vLLM's own OpenAI server streams with: one instance per request, built with the request's chat_template_kwargs (enable_thinking), fed each delta via parse_delta. Reasoning, content and tool calls go out in their own fields as they are produced; the final chunk carries only token counts and logprobs. If the parser raises mid-stream, the rest of the output is buffered and the final chunk sends what the full-text parse has beyond what was already streamed (suffixes per field and per tool-call index). Where the full parse disagrees with what went out, nothing is guessed and the mismatch is logged. Only active when a reasoning_parser is set; without one, or on a vLLM without ParserManager, streaming is unchanged. Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com>
This branch has not been deployed
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.
Description
With a
reasoning_parserconfigured, streamed chat completions from the vLLM backend carry the model's reasoning indelta.content, and the final chunk repeats the answer and the reasoning a second time. Non-streaming responses are split correctly.Reproduce with a thinking model (e.g. Qwen3 family,
reasoning_parser: qwen3) and"stream": truewith thinking enabled: the reasoning text arrives aschoices[0].delta.content,delta.reasoning_contentstays empty until the last chunk, which then contains the full reasoning and the full answer again. We saw the reasoning incontentin 11 of 12 streamed requests with high reasoning effort.Cause: the streaming path in
_predictsends each raw text delta asChatDelta.content(reasoning is only split after the loop, on the full text), and LocalAI forwards the backend's ChatDeltas as they are.This PR uses vLLM's composed parser (
ParserManager.get_parser, available since vLLM 0.30), the one vLLM's own OpenAI server streams with:chat_template_kwargs(soenable_thinking=falsestarts it in content mode) and tools,parse_deltaper delta; reasoning, content and tool calls go out in their own fields as they are produced,If the parser raises mid-stream, the rest of the output is buffered and the final chunk sends what the full-text parse has beyond what was already streamed (suffixes per field and per tool-call index); where the full parse disagrees with what went out, nothing is guessed and the mismatch is logged.
Only active when a
reasoning_parseris set. Without one, or on a vLLM withoutParserManager, streaming is unchanged.get_parseris called without thetokenizerargument, which only exists in 0.32.Notes for Reviewers
New server-less tests in
backend/python/vllm/test.py(TestStreamingReasoningSplit, fake engine + fake parser like the existingTestStreamingToolParser): no reasoning in content and nothing repeated in the final chunk; parser failure mid-reasoning and after a partial tool call (rest of the arguments on the same index, no raw markup in content); unchanged raw streaming withoutParserManager;chat_template_kwargs/tools forwarded. On unpatched master 3 of them fail (reasoning and a duplicated answer in content).Run in the backend's venv against vLLM 0.31.0 and 0.32.0: new and existing server-less tests pass. Additionally checked outside the suite with vLLM's real
qwen3reasoning parser,qwen3_codertool parser and a Qwen3 tokenizer, streaming a recorded completion in chunks of 1–4 tokens (as with speculative decoding): reasoning, content and tool calls separate correctly with thinking on and off, the final chunk is empty, and non-streaming output is unchanged.Independent of #12872 (same file, no overlap; merges cleanly in either order).
Signed commits