Skip to content

fix(frontend): leave uncompilable regex patterns out of automatic tool guidance - #17513

Open
bjck wants to merge 8 commits into
ai-dynamo:mainfrom
bjck:bkock/fix-structural-tag-unsupported-regex
Open

bjck wants to merge 8 commits into
ai-dynamo:mainfrom
bjck:bkock/fix-structural-tag-unsupported-regex

Conversation

@bjck

@bjck bjck commented Oct 28, 2026 •

Copy link
Copy Markdown

Overview

A tool schema with a pattern XGrammar cannot compile, such as the lookahead in #15687, makes the backend reject the whole request once structural tags enforce it. With this PR that pattern is left out of the grammar, and the rest of the schema and the tool call stay enforced.

Key Changes

About 390 lines of core logic: 259 Rust, 129 Python. The Rust and Python frontends build tags on separate paths. The Python frontend asks the installed XGrammar whether a pattern compiles. The Rust preprocessor has no XGrammar, so it checks a list of constructs, and the Python test checks that list against the compiler.

Validation

Cause: to_v2_tool maps omitted strict to true, so the tag carries the client's regex into XGrammar. Leaving the pattern out removes the only part XGrammar rejects. An unsupported patternProperties key cannot be left out without changing which names are allowed, so that tool falls back to strict: false.

Dynamo's tags from dynamo-parsers-v2 0.7.35, compiled with XGrammar 0.2.7, reproducer schema:

Case Before After
qwen3_coder, auto/required/named, strict omitted rejected: Lookahead is not supported yet compiles, pattern left out
kimi_k3, auto/required/named, strict omitted rejected compiles, pattern left out
kimi_k3 named, optional tags off rejected compiles, pattern left out
strict: true or schema mode strict rejected rejected (unchanged)
supported pattern ^[a-z]+$ enforced enforced (unchanged)
unsupported patternProperties key rejected compiles, arguments unconstrained

The Rust detector flags what any 0.2.7 tag format rejects: lookahead, lookbehind, backreferences, group modifiers, \b, \p{..}, [], malformed {..}, counts above 10^15 and possessive quantifiers. Fuzzed against the compiler on the qwen_xml path: 31.9k patterns, zero false flags, zero misses.

Key tests:

  • unsupported_regex_constructs_match_the_grammar_compiler (Rust, Python): one shared table. Rust checks its detector against it, Python checks it against the installed XGrammar, so the Rust list drifting from the compiler fails a test.
  • automatic_argument_enforcement_leaves_out_patterns_the_backend_cannot_compile: relaxing too much or too little across strict omitted/true/false and schema mode strict. Fails without the fix.
  • kimi_k3_named_tag_leaves_out_patterns_the_backend_cannot_compile_when_global_mode_is_off: the K3 named tag the opt-out keeps. Fails without the fix.
  • client_sized_repetition_counts_are_rejected_without_parsing (Rust): a crash on huge {n} counts.
  • TestConvertTools::test_guidance_copy_leaves_out_patterns_the_backend_cannot_compile: the SGLang call site. It needs sglang and the compiled bindings, so CI is its first run.

Run locally: Python policy tests with XGrammar 0.2.7, 96 passed. cargo test -p dynamo-llm --no-default-features --lib structural_tag, 59 passed. Not run end to end on vLLM, SGLang or TensorRT-LLM.

Cost: one scan per omitted-strict tool schema, only when a tag is built under schema mode auto. The Rust scan does not allocate. In Python each pattern costs one Grammar.from_regex call, under 0.1 ms locally. Tools are copied only when a pattern is left out.

Issues and Ongoing Work

I had AI help with the code, the probes and the fuzzing. I have read and validated the change.

Pull Request Checklist
  • I used Dynamo's /pr-checklist skill.

Summary by CodeRabbit

  • New Features
    • Automatic schema handling now accommodates regex patterns unsupported by XGrammar in auto mode. For tools without an explicit strictness setting, unsupported pattern constraints are omitted; schemas with unsupported patternProperties use non-strict enforcement.
    • Tool schemas are adapted consistently across supported backends, while explicitly strict tools and strict schema mode retain their existing behavior.
  • Documentation
    • Clarified structural-tag behavior for Kimi tool choices, schema modes, and unsupported regex constructs.

@copy-pr-bot

copy-pr-bot Bot commented Oct 28, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@bjck
bjck temporarily deployed to external_collaborator October 28, 2026 11:10 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator October 28, 2026 11:10 — with GitHub Actions Inactive
@github-actions github-actions Bot added the fix label Oct 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi bjck! Thank you for contributing to ai-dynamo/dynamo.

Just a reminder: The NVIDIA Test Github Validation CI runs an essential subset of the testing framework to quickly catch errors.Your PR reviewers may elect to test the changes comprehensively before approving your changes.

🚀

@github-actions github-actions Bot added external-contribution Pull request is from an external contributor documentation Improvements or additions to documentation frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` labels Oct 28, 2026
@bjck
bjck temporarily deployed to external_collaborator October 28, 2026 11:49 — with GitHub Actions Inactive
bjck added 2 commits October 28, 2026 13:05
…l guidance

Under schema mode auto, structural tags enforce the argument schema of a
tool that omits strict. A pattern XGrammar cannot compile (lookahead,
lookbehind, backreferences, group modifiers, \b, \p{..}, empty classes,
malformed or oversized repetitions, possessive quantifiers) made the
backend reject the whole request.

Leave such patterns out of the grammar for omitted-strict tools and keep
the rest of the schema enforced. An unsupported patternProperties key
falls back to strict: false, since dropping it would change which names
are allowed. Explicit strict and schema mode strict are unchanged.

Applied in the Rust preprocessor and in the Python vLLM and SGLang
frontend paths.

Part of ai-dynamo#15687

Signed-off-by: Bjarne Kock <bjarnekock@hotmail.com>
Keep only why-comments in code; measurements and issue history live in the PR description.

Signed-off-by: Bjarne Kock <bjarnekock@hotmail.com>
@bjck
bjck force-pushed the bkock/fix-structural-tag-unsupported-regex branch from 967dc59 to 0cac921 Compare October 28, 2026 12:06
@bjck
bjck temporarily deployed to external_collaborator October 28, 2026 12:06 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator October 28, 2026 12:54 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator October 29, 2026 06:08 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator October 30, 2026 09:58 — with GitHub Actions Inactive
@bjck
bjck marked this pull request as ready for review October 30, 2026 09:59
@bjck
bjck requested review from a team as code owners October 30, 2026 10:00
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 46193c6 and ae10c58.

Files selected for processing: 7
  • components/src/dynamo/frontend/prepost.py
  • components/src/dynamo/frontend/sglang_prepost.py
  • components/src/dynamo/frontend/structural_tag_policy.py
  • components/src/dynamo/frontend/tests/test_sglang_processor_unit.py
  • components/src/dynamo/frontend/tests/test_structural_tag_policy.py
  • docs/fern/pages/use-cases/tool-calling-and-reasoning/structural-tag.md
  • lib/llm/src/preprocessor/structural_tag.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

Walkthrough

The change detects regex constructs unsupported by XGrammar in tool schemas. In automatic schema mode, preprocessing removes unsupported pattern constraints when strictness is omitted, or disables enforcement for unsupported patternProperties keys. Python and Rust paths apply the policy, with tests and documentation updated.

Changes

Structural-tag schema regex compatibility

Layer / File(s) Summary
Detect unsupported regex and schema constructs
components/src/dynamo/frontend/structural_tag_policy.py, lib/llm/src/preprocessor/structural_tag.rs, components/src/dynamo/frontend/tests/test_structural_tag_policy.py
Python and Rust code scan regex patterns and nested schema keywords for unsupported constructs. Tests cover supported and unsupported syntax and schema traversal.
Adapt schemas according to strictness
components/src/dynamo/frontend/structural_tag_policy.py, lib/llm/src/preprocessor/structural_tag.rs, components/src/dynamo/frontend/tests/test_structural_tag_policy.py
Python and Rust code remove unsupported pattern constraints for eligible tools. If a patternProperties key is unsupported, they disable enforcement instead. Tests cover adaptation rules and input preservation.
Apply policy in structural-tag paths
components/src/dynamo/frontend/prepost.py, components/src/dynamo/frontend/sglang_prepost.py, lib/llm/src/preprocessor/structural_tag.rs, components/src/dynamo/frontend/tests/test_sglang_processor_unit.py, lib/llm/src/preprocessor/structural_tag.rs, docs/fern/pages/use-cases/tool-calling-and-reasoning/structural-tag.md
The Python frontend and Rust tool-choice paths pass adapted schemas into structural-tag processing. Tests and documentation cover the behavior, including Kimi choice cases.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: Minimal · up to ae10c

No identified issue blocks merging after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the main change: uncompilable regex patterns are left out of automatic tool guidance. It follows the fix(frontend) convention and is specific. It is 79 characters, which is within a no…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description follows the repository template closely. It has Overview, Key Changes, Validation, and Issues and Ongoing Work sections, plus the checklist item marked as done. The Overview states the…
Full details: Title check

Explanation

The title names the main change: uncompilable regex patterns are left out of automatic tool guidance. It follows the fix(frontend) convention and is specific. It is 79 characters, which is within a normal title length. The title does not mention the Rust and Python paths, but that detail is not needed.

Full details: Description check

Explanation

The description follows the repository template closely. It has Overview, Key Changes, Validation, and Issues and Ongoing Work sections, plus the checklist item marked as done. The Overview states the problem and the fix: an XGrammar-incompatible tool schema pattern makes the backend reject the whole request, and the PR leaves that pattern out while keeping the rest of the schema enforced. Key Changes lists the Rust and Python files and what each changes. Validation gives a root cause, a before/after table covering the affected cases, the tests added, and the local runs (Python 96 passed, cargo 59 passed). It states that end-to-end runs on vLLM, SGLang, and TensorRT-LLM were not done, and that the SGLang call-site test awaits its first CI run. Issues and Ongoing Work links the work to #15687 and explains the scope limits. The template's 'Closes #XXXX' line is absent. The PR references #15687 as 'Part of', which is not a closing link. The template comment says the PR Issue Link check is advisory today. Because the check is advisory and the PR links an issue, this gap is minor.

Full details: Docstring Coverage

Explanation

Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 21 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

The Python frontend runs next to vLLM or SGLang, which ship XGrammar, so
compile each pattern with Grammar.from_regex instead of mirroring its
regex syntax. The verdict now follows the installed version.

The Rust preprocessor has no XGrammar and keeps its list. The shared
test table now checks that list against the compiler.

Part of ai-dynamo#15687

Signed-off-by: Bjarne Kock <bjarnekock@hotmail.com>
@bjck
bjck temporarily deployed to external_collaborator October 31, 2026 12:19 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator October 31, 2026 12:21 — with GitHub Actions Inactive
@bjck
bjck temporarily deployed to external_collaborator November 2, 2026 15:43 — with GitHub Actions Inactive

This branch was previously deployed

1 inactive deployment
external_collaborator — f99e4d61 Deployed Nov 2, 2026 by bjck via ok-to-test #43377
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation external-contribution Pull request is from an external contributor fix frontend `python -m dynamo.frontend` and `dynamo-run in=http|text|grpc` size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant