From 741e33db81b5d5fd2dec028fcae90c1299ccf900 Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 3 Aug 2026 11:24:55 +0800 Subject: [PATCH] fix(sampling): reject conflicting structural tag constraints (#32525) --- python/sglang/srt/sampling/sampling_params.py | 5 ++- .../unit/sampling/test_sampling_params.py | 36 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/python/sglang/srt/sampling/sampling_params.py b/python/sglang/srt/sampling/sampling_params.py index e7adfe6fe..77b72aab7 100644 --- a/python/sglang/srt/sampling/sampling_params.py +++ b/python/sglang/srt/sampling/sampling_params.py @@ -196,9 +196,12 @@ class SamplingParams(msgspec.Struct, kw_only=True, array_like=True): self.json_schema, self.regex, self.ebnf, + self.structural_tag, ] # since mutually exclusive, only one can be set if sum(x is not None for x in grammars) > 1: - raise ValueError("Only one of regex, json_schema, or ebnf can be set.") + raise ValueError( + "Only one of json_schema, regex, ebnf, or structural_tag can be set." + ) def normalize(self, tokenizer): # Process stop strings diff --git a/test/registered/unit/sampling/test_sampling_params.py b/test/registered/unit/sampling/test_sampling_params.py index 518ca88b1..6143c51da 100644 --- a/test/registered/unit/sampling/test_sampling_params.py +++ b/test/registered/unit/sampling/test_sampling_params.py @@ -77,6 +77,12 @@ class TestSamplingParamsInit(CustomTestCase): class TestSamplingParamsVerify(CustomTestCase): VOCAB_SIZE = 32000 + GRAMMAR_VALUES = { + "json_schema": '{"type":"object"}', + "regex": "abc", + "ebnf": 'root ::= "abc"', + "structural_tag": '{"structures":[],"triggers":[]}', + } def _make(self, **kwargs): """Helper: create SamplingParams with safe defaults, override with kwargs.""" @@ -273,21 +279,25 @@ class TestSamplingParamsVerify(CustomTestCase): sp.verify(self.VOCAB_SIZE) def test_multiple_grammars_raises(self): - """Test that verify() rejects setting both json_schema and regex (mutually exclusive).""" - sp = self._make(json_schema='{"type":"object"}', regex="abc") - with self.assertRaises(ValueError): - sp.verify(self.VOCAB_SIZE) + """Reject structural_tag combined with any other grammar constraint. + + Constraint selection is a fixed if/elif chain, so a constraint left out of + this check is silently dropped with no error to the caller. + """ + for other in ("json_schema", "regex", "ebnf"): + with self.subTest(other=other): + sp = self._make( + structural_tag=self.GRAMMAR_VALUES["structural_tag"], + **{other: self.GRAMMAR_VALUES[other]}, + ) + with self.assertRaisesRegex(ValueError, "Only one of"): + sp.verify(self.VOCAB_SIZE) def test_single_grammar_valid(self): - """Test that setting only one grammar type is accepted.""" - sp = self._make(json_schema='{"type":"object"}') - sp.verify(self.VOCAB_SIZE) - - def test_all_three_grammars_set_raises(self): - """Test that verify() rejects setting json_schema, regex, and ebnf together.""" - sp = self._make(json_schema="{}", regex="a", ebnf="rule") - with self.assertRaises(ValueError): - sp.verify(self.VOCAB_SIZE) + """Test that each grammar constraint is valid on its own.""" + for grammar, value in self.GRAMMAR_VALUES.items(): + with self.subTest(grammar=grammar): + self._make(**{grammar: value}).verify(self.VOCAB_SIZE) class TestSamplingParamsNormalize(CustomTestCase):