From 2c0e2dd1a7f459dd05e6ed7ed2a16a681de7c49a Mon Sep 17 00:00:00 2001 From: linnea-lin-00638949 <15521435947@163.com> Date: Thu, 2 Jul 2026 22:42:15 +0800 Subject: [PATCH] [Bugfix][Structured Outputs] Reject empty `structural_tag` at request validation `structured_outputs.structural_tag=""` passes the `is not None` exclusivity check and reaches `json.loads("")` inside `compile_grammar`, raising JSONDecodeError. Since #47312 this surfaces as a per-request error instead of crashing the engine; reject it at request validation (-> 400) for consistency with the existing `json`/`grammar` empty-string guards. An empty or whitespace-only `regex` is a valid pattern (e.g. `regex="\\n"`, `regex=" "`) and is intentionally not checked. Extends the parametrized `test_degenerate_structured_outputs_rejected` with the structural_tag="" case. Co-authored-by: Claude Signed-off-by: linnea-lin-00638949 <15521435947@163.com> --- tests/v1/structured_output/test_validation.py | 9 +++++++-- vllm/sampling_params.py | 12 ++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/v1/structured_output/test_validation.py b/tests/v1/structured_output/test_validation.py index a82b3e51177b..739279280caf 100644 --- a/tests/v1/structured_output/test_validation.py +++ b/tests/v1/structured_output/test_validation.py @@ -56,13 +56,18 @@ def test_plain_request_allowed_for_diffusion_models(): [ (StructuredOutputsParams(json_object=False), "json_object must be True"), (StructuredOutputsParams(json=""), "json cannot be an empty string"), + ( + StructuredOutputsParams(structural_tag=""), + "structural_tag cannot be an empty string", + ), ], ) def test_degenerate_structured_outputs_rejected(structured_outputs, match): """json_object=False and an empty json schema pass the `is not None` exclusivity check but resolve to no structured-output key, so they must be - rejected at request validation (-> 400) instead of reaching and crashing - the engine.""" + rejected at request validation (-> 400). Empty `structural_tag` is rejected + for the same reason: `json.loads("")` in `compile_grammar` would otherwise + raise JSONDecodeError and surface as a per-request engine error.""" params = SamplingParams(structured_outputs=structured_outputs) with pytest.raises(VLLMValidationError, match=match): params._validate_structured_outputs( diff --git a/vllm/sampling_params.py b/vllm/sampling_params.py index 120dbaddfd80..b91d47d3dee5 100644 --- a/vllm/sampling_params.py +++ b/vllm/sampling_params.py @@ -1144,6 +1144,18 @@ def _validate_structured_outputs( raise VLLMValidationError( "structured_outputs.regex must not contain a NUL character ('\\x00')" ) + # Note(arpera): + # We do NOT check here structured output regex on emptiness because + # empty regex is indeed compiles to a valid grammar as well as + # whitespace-only regexps, for instance, regex="\n" or regex=" " + # are valid patterns and we MUST process them. + if ( + isinstance(self.structured_outputs.structural_tag, str) + and self.structured_outputs.structural_tag.strip() == "" + ): + raise VLLMValidationError( + "structured_outputs.structural_tag cannot be an empty string" + ) from vllm.v1.structured_output.backend_guidance import ( has_guidance_unsupported_json_features,