[Bugfix][Parser] Scope reasoning-end detection to the current turn via turn-boundary tokens - #54089
Conversation
…a turn-boundary tokens ParserEngine.is_reasoning_end() walks the whole token sequence backwards and returns True on the first </think>, so a stale marker in conversation history makes the DelegatingParser stream the new turn's thinking into `content` Co-authored-by: Claude Code Signed-off-by: Mikhail Podvitskii <podvitskiymichael@gmail.com>
|
/ci run |
|
✅ Triggered Buildkite CI #85873 for commit |
bbrowning
left a comment
There was a problem hiding this comment.
Note that the SeedOSS models inherit from the Qwen3 parser here. That happens to be harmless in this case, even though the boundary turn tokens are now wrong for SeedOSS, because the code gracefully degrades when those tokens are not in the model vocab.
To get this fixed for SeedOSS models as well, need to override SeedOssParser.TURN_BOUNDARIES with <seed:bos> and <seed:eos> instead of the inherited ChatML tokens (and confirm those are the right turn boundary tokens for the target model and in that model's vocab) and add a multi-turn test containing a stale </seed:think> before the latest Seed turn boundary.
Given that this is harmless for SeedOSS models today, approving this as is. Thanks for the fix!
…a turn-boundary tokens (vllm-project#54089) Signed-off-by: Mikhail Podvitskii <podvitskiymichael@gmail.com>
…a turn-boundary tokens (vllm-project#54089) Signed-off-by: Mikhail Podvitskii <podvitskiymichael@gmail.com>
Purpose
ParserEngine.is_reasoning_end()walks the token sequence backwards and returnsTrueon the first</think>it sees. It is called on the full prompt (the entire rendered conversation) from two places:DelegatingParser.parse_delta(vllm/parser/abstract_parser.py) - seedsreasoning_endedat stream start. A stale</think>in history routes the new turn's entire<think>…</think>block intocontentinstead ofreasoning(and the answer is lost entirely underinclude_reasoning=false).StructuredOutputManager.should_fill_bitmask(vllm/v1/structured_output/__init__.py) - seeds the scheduler's reasoning-end gate. A stale</think>applies the grammar bitmask from the first generated token, constraining the model's thinking.Fix
Add
turn_boundary_tokenstoParserEngineConfig; the backward walk now stops at the first turn boundary (<|im_start|>/<|im_end|>for the ChatML family), so markers from earlier turns are no longer visible. Hitting aboundary falls back to
initial_state != REASONING(the same semantics as an empty prompt) which keeps the "template closed the think block in the generation prompt" case working (covered by a test). This generalizes into theengine what #44551 (Cohere) and
poolside_v1(here) already do per-parser, and follows the model-specific backward-scan direction maintainers requested when closing the generic serving-layer attempts (#46663 review).Backward compatible: the default boundary set is empty, and boundary tokens missing from a tokenizer's vocab resolve to nothing, so parsers on non-ChatML vocabs (e.g.
SeedOssParser) keep the previous behaviour. Both cases are covered by tests.Why this is not a duplicate
contenton v0.26.0 and current main #46042 / [Bugfix] Fix MiniMax M3 prompt reasoning initialization #50594 cover MiniMax-M3is_reasoning_end(prompt), this PR makes that check robust against stale history markersis_reasoning_end_streamingTest Plan
E2E: serve
Qwen/Qwen3-1.7Bwith--reasoning-parser qwen3, send streaming requests whose prompt contains a stale</think>(literal tag in user text), plus regression controls, attemperature=0; compare field routing before and after this change.Test Result
Tested on H100
tests/parser/engine/test_qwen3_reasoning.py: 57 passed (incl. 10 new turn-boundary tests)full
tests/parsersuite: 4086 passed, 0 failurespre-commit on changed files: all hooks passed
E2E (Qwen/Qwen3-1.7B,
--reasoning-parser qwen3, streaming, temperature=0):</think>in user text<think>tag leaked into content (602 ch)Control cases are byte-identical before/after.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.