[Bugfix][Structured Output] Avoid spurious FSM errors after speculative reasoning end (#53046)
Signed-off-by: chaunceyjiang <chaunceyjiang@gmail.com>
Chauncey committed
Aug 21, 2026 at 10:01 UTC
c6e19b3be24338759a443e03c8325d76da9ee202
2 files changed
+8
-2
tests/v1/spec_decode/test_mtp_structured_output.py
+2
-1
@@ -189,7 +189,7 @@ def test_bitmask_constrained_when_reasoning_ends_midwindow(backend):
189
190
191
@pytest.mark.parametrize("backend", ["xgrammar", "guidance"])
192
-def test_bitmask_post_reasoning_end_drafts_skip_grammar_advance(backend):
192
+def test_bitmask_post_reasoning_end_drafts_skip_grammar_advance(backend, caplog):
193
"""Post-marker drafts predate the bitmask and may be grammar-invalid;
194
grammar_bitmask must skip the grammar advance instead of asserting.
195
"""
@@ -235,6 +235,7 @@ def test_bitmask_post_reasoning_end_drafts_skip_grammar_advance(backend):
235
assert not (bitmask[2] == -1).all()
236
# Grammar must not have advanced through the unvalidated draft.
237
assert not grammar.is_terminated()
238
+ assert "Failed to advance FSM" not in caplog.text
239
240
241
@pytest.mark.parametrize("backend", ["xgrammar", "guidance"])
vllm/v1/structured_output/__init__.py
+6
-1
@@ -326,7 +326,12 @@ class StructuredOutputManager:
326
advance_grammar = False
327
post_reasoning_end_in_window = True
328
if advance_grammar and not grammar.is_terminated():
329
- accepted = grammar.accept_tokens(req_id, [token])
329
+ if post_reasoning_end_in_window:
330
+ accepted = bool(grammar.validate_tokens([token]))
331
+ if accepted:
332
+ accepted = grammar.accept_tokens(req_id, [token])
333
+ else:
334
+ accepted = grammar.accept_tokens(req_id, [token])
335
if accepted:
336
state_advancements += 1
337
elif not post_reasoning_end_in_window: