Skip to content

Commit 8d5dc08

Browse files
mmoskalMu Huai
authored and
Mu Huai
committed
implement Structural Tag with Guidance backend (vllm-project#17333)
Signed-off-by: Michal Moskal <[email protected]> Signed-off-by: Mu Huai <[email protected]>
1 parent 239e630 commit 8d5dc08

File tree

2 files changed

+32
-10
lines changed

2 files changed

+32
-10
lines changed

tests/v1/entrypoints/llm/test_struct_output_generate.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,13 +435,10 @@ def test_structured_output(
435435
"""
436436

437437
# Change this once other backends support structural_tag
438-
if guided_decoding_backend.startswith("xgrammar"):
439-
outputs = llm.generate(prompts=prompt,
440-
sampling_params=sampling_params,
441-
use_tqdm=True)
442-
assert outputs is not None
443-
else:
444-
outputs = []
438+
outputs = llm.generate(prompts=prompt,
439+
sampling_params=sampling_params,
440+
use_tqdm=True)
441+
assert outputs is not None
445442

446443
for output in outputs:
447444
assert output is not None

vllm/v1/structured_output/backend_guidance.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,18 @@ def serialize_guidance_grammar(
173173
disable_any_whitespace: bool = False,
174174
no_additional_properties: bool = False,
175175
) -> str:
176-
if request_type == StructuredOutputOptions.JSON:
176+
177+
def _process_schema(grammar_spec: Union[str, dict[str, Any]], ) -> str:
177178
if no_additional_properties:
178179
grammar_spec = process_for_additional_properties(grammar_spec)
179180
return llguidance.LLMatcher.grammar_from_json_schema(
180181
grammar_spec,
181182
defaults={
182183
"whitespace_flexible": not disable_any_whitespace,
183184
})
185+
186+
if request_type == StructuredOutputOptions.JSON:
187+
return _process_schema(grammar_spec)
184188
elif request_type == StructuredOutputOptions.JSON_OBJECT:
185189
return llguidance.LLMatcher.grammar_from_json_schema(
186190
'{"type": "object"}',
@@ -195,8 +199,29 @@ def serialize_guidance_grammar(
195199
elif request_type == StructuredOutputOptions.CHOICE:
196200
tp = "choice"
197201
elif request_type == StructuredOutputOptions.STRUCTURAL_TAG:
198-
raise ValueError("Structural tag is not supported "
199-
"for guidance backend yet")
202+
if isinstance(grammar_spec, str):
203+
s_tag = json.loads(grammar_spec)
204+
else:
205+
s_tag = grammar_spec
206+
triggers: list[str] = s_tag["triggers"]
207+
tags: list[llguidance.StructTag] = []
208+
for s in s_tag["structures"]:
209+
begin: str = s["begin"]
210+
trig = next((t for t in triggers if begin.startswith(t)), None)
211+
if trig is None:
212+
raise ValueError(
213+
f"Trigger {begin} not found in triggers {triggers}")
214+
tags.append(
215+
llguidance.StructTag(
216+
trigger=trig,
217+
begin=s["begin"],
218+
grammar=_process_schema(s["schema"]),
219+
end=s["end"],
220+
))
221+
if not tags:
222+
raise ValueError(
223+
"No structural tags found in the grammar spec.")
224+
return llguidance.StructTag.to_grammar(tags)
200225
else:
201226
logger.error("Validation should have already occurred. "
202227
"Please file an issue.")

0 commit comments

Comments
 (0)