Repository navigation
fix(vllm): keep reasoning out of streamed content - #12873
pos-ei-don wants to merge 2 commits 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>
|
@mudler source review complete against master I pushed Validation: clean integration with current master and whitespace checks pass. Runtime tests remain unverified: the available Python executables require a loader absent from this environment. Please validate the backend suite, including the new fake-parser tests on older profiles, before merging. Source-review sign-off, conditional on backend test validation and human DCO certification of the added commit. It includes Assisted-by attribution and no AI Signed-off-by. |
Mock the request constructor so fake parser tests can run on older vLLM profiles without ParserManager. Document the streaming version requirement and fallback behavior. Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com>
c37aa56 to
ff0130f
Compare
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