Repository navigation
feat: support Gemma 4 tool calling with the <|"|> string delimiter - #923
Merged
Seven-Streams merged 4 commits intoSep 24, 2026
Merged
Conversation
Re-enable the `gemma_4` builtin structural tag by adding a `"gemma"` JSON-schema style that emits Gemma 4's argument spelling: unquoted object keys and strings delimited by the single `<|"|>` token with no escape sequences, at every nesting level. This picks up mlc-ai#684 (closed unmerged after the converter was rewritten to build grammar trees directly in mlc-ai#745) and ports it to the current converter API: - `GemmaToolCallingConverter` (cpp/converter_ext/gemma.cc) registers the delimiter as an exclusion, so pattern/format/length-bounded strings are intersected with "does not contain <|"|>" through `ExcludingString` and can no longer close a string early (the known limitation of mlc-ai#684), literals that would have to spell the delimiter become unsatisfiable, and declared property names are excluded from `additionalProperties` keys. - Two small virtual hooks on `JSONSchemaConverter` (`CreatePatternKeyRule`, `CreatePropertyNamesKeyRule`) let formats with unquoted keys emit bare key bodies for `patternProperties` / `propertyNames`. - `get_gemma_4_structural_tag` uses `style="gemma"` and keeps `<|tool_call>` excluded from the thought channel while leaving it available as the trigger in free text. - The alignment test renders the vendored Gemma-4 chat templates with jinja2 (no HF token or Gemma-4-capable transformers needed) and guards them against drift from the official templates when a token is available. Closes mlc-ai#587. Co-authored-by: iwanhae <iwanhae@gmail.com> Co-authored-by: wanhae.lee <wanhae.lee@linecorp.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
asanomitakanori
requested review from
CharlieFRuan,
DarkSharpness,
Seven-Streams and
Ubospica
as code owners
September 23, 2026 15:13
Signed-off-by: yuchuan <yuchuan.7streams@gmail.com>
Seven-Streams
approved these changes
Sep 24, 2026
Signed-off-by: yuchuan <yuchuan.7streams@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
"gemma"JSON-schema style that emits Gemma 4's tool-call argument spelling: unquoted object keys and strings delimited by the single<|"|>token with no escape sequences, at every nesting level.gemma_4builtin structural tag (auto / required / forced tool choice,<|channel>thoughtreasoning prefix,parallel_tool_calls), which has been unregistered with a "we will support it later" TODO since refactor: unify reasoning parameter and rename model keys #609.Closes #587. Continues #684 by @iwanhae, which was closed unmerged after the converter was rewritten to build grammar trees directly (#745) and the branch fell into conflict; the original implementation and authorship are preserved through
Co-authored-by.Why
Engines currently fall back to unconstrained decoding for Gemma 4 tool calls because the existing converters cannot express
<|"|>-delimited strings. This blockstool_choice="required"and JSON-schema-constrained arguments for Gemma 4 in SGLang and vLLM (vllm-project/vllm#53363, sgl-project/sglang users on #684).What changed since #684
Ported to the current
int32_t-based converter API and reworked aroundJSONSchemaFormat.excludes(#912):GemmaToolCallingConverterappends<|"|>toexcludes_, so:pattern/format/minLength-maxLengthstring bodies are intersected with "does not contain<|"|>" throughExcludingString. A permissive pattern such as.*can no longer close the string early — this was the documented limitation of feat: support Gemma tool calling format with <|"|> string delimiters #684. (Built-in format regexes the FSM engine cannot build keep the CFG expansion, as in the JSON converter.)const/enumliterals that would have to spell the delimiter become unsatisfiable instead of emitting a string that parses differently.additionalPropertieskeys (the original PR fell back to the plain identifier pattern).JSONSchemaFormat.excludeswork forstyle="gemma"too.JSONSchemaConverter:CreatePatternKeyRule(replaces the local lambda inGenerateObject) andCreatePropertyNamesKeyRule. Formats with unquoted keys override them to emit bare key bodies forpatternProperties/propertyNames; the JSON and XML converters keep their behaviour unchanged.(.|\n){m,n}intersected with the exclusion, so they count codepoints and allow lone</ delimiter prefixes without hand-written lookahead rules.jinja2under theGEMMA:model-id prefix (no HF token or Gemma-4-capabletransformersneeded), reusing the tokenizer prompt-diff path; a token-gated drift guard checks the vendored templates againstgoogle/gemma-4-E2B-it/google/gemma-4-31B-it.structural_tag.md,tool_calling_and_reasoning.md), the TypeScriptJSONSchemaFormat.styleunion, and the style error message are updated.Test plan
tests/python/test_function_calling_converter.py -k gemma(59 cases: strings, scalars, nesting,any, enum/const incl. large-int literal preservation,additionalProperties,patternProperties,propertyNames, length bounds incl. multi-byte and newline,.*pattern cannot emit the delimiter,format: date,excludes)tests/python/test_builtin_structural_tag.py(612 passed incl. the sharedget_*_structural_tagmatrix, a single-token<|"|>/<|tool_call>tokenizer walk, and the auto-dispatch regression)tests/python/test_builtin_structural_tag_alignment.py -k gemma(34 passed: e2b + 31b templates × thinking on/off × tool scenarios, plus the drift guard against the official templates with a HF token)test_function_calling_converter.py,test_structural_tag_converter.py,test_json_schema_excludes.py,test_grammar_matcher_json_schema.py,test_json_schema_converter.py: 2933 passed (-m "not hf_token_required")pre-commit run(black, isort, clang-format 19.1.7, taplo) andruff checkpassKnown limitations
patternProperties/propertyNameskey pattern whose language contains:cannot round-trip, because Gemma keys are unquoted (same as feat: support Gemma tool calling format with <|"|> string delimiters #684).constobjects/arrays are re-serialized through picojson; top-level scalar literals keep the schema's own text.🤖 Generated with Claude Code