Repository navigation
feat: Kimi K3 structural tag default - #196
Merged
Merged
Conversation
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
|
📊 Conformance matrix rendered — view in CI summary |
rmccorm4
reviewed
Aug 24, 2026
indrajit96
marked this pull request as ready for review
August 24, 2026 20:52
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
rmccorm4
reviewed
Sep 1, 2026
rmccorm4
left a comment
Contributor
There was a problem hiding this comment.
Local exact-head review findings.
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
keivenchang
reviewed
Sep 10, 2026
ccrvlh
pushed a commit
to aircloud-dev/frontend-crates
that referenced
this pull request
Sep 16, 2026
VENDORED, NOT OUR WORK. This is upstream pull request ai-dynamo#196 ("feat: Kimi K3 structural tag default", branch `ibhosale/k3-kvv-crates`), applied here byte for byte and authored by Indrajit Bhosale. It is carried only because that pull request has not merged yet, and it must be dropped the moment it does. The original subject line was `K3 structual tag defualt`; only this message has been rewritten, never the diff. What it does: the Kimi K3 structural-tag builder previously had a real grammar only for the mandatory tool-choice paths, so `tool_choice: auto` fell back to a loose tag. This adds `build_auto_structural_tag`, which derives the tag from each tool's declared JSON schema: - `JsonType` maps the JSON schema types onto their xtml names, resolving local `$ref` targets against the root `$defs` so referenced argument schemas keep their definitions in typed content. - Required arguments are emitted in canonical (schema-declaration) order, each with its own typed tag; optional arguments follow in a repeatable slot. - The thinking block is closed before the triggered-tools suffix, so a tool call cannot open inside an unterminated reasoning span. - `strict: false` still selects permissive argument bodies. `TriggeredTagsFormat` also gains an `excludes` list, serialized only when non-empty, threaded with an empty default through the DSML, Kimi K2 and generic triggered-tags constructors. Note for the reader of this branch: as vendored, this commit also reverts the split between Dynamo's generic argument-schema policy and Kimi's, because the pull request branch predates that split. The commit that follows restores it. Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
ccrvlh
added a commit
to aircloud-dev/frontend-crates
that referenced
this pull request
Sep 16, 2026
Review fix against the preceding commit (upstream pull request ai-dynamo#196), whose branch predates the split between Dynamo's generic argument-schema policy and Kimi's. Applying it routes every generic builder back through `uses_declared_tool_schema`, where an omitted `strict` field selects the tool's declared parameter schema. That is the wrong default off Kimi. In OpenAI's structured-outputs semantics `strict` is an opt-in: absent means not strict. The generic builders serve hermes, qwen25, qwen3_coder, inkling and deepseek_dsml, whose clients overwhelmingly omit the field, so constraining their arguments to a declared schema — one that is frequently incomplete — changes output that previously decoded freely, without anyone asking for it. Restore the split. `resolve_tool_schema` keeps `strict: true`-only semantics for the generic builders. The Kimi K2 and K3 builders use `kimi_uses_declared_tool_schema`, which matches vLLM/xgrammar in treating only an explicit `strict: false` as an opt-out, and global strict mode still overrides the per-tool opt-out on both paths. The two policies are pinned by separate tests so neither can drift into the other. Signed-off-by: Caio Rocha <caio@crafthouse.io>
ccrvlh
added a commit
to aircloud-dev/frontend-crates
that referenced
this pull request
Sep 16, 2026
Review fix against upstream pull request ai-dynamo#196, vendored two commits back. `optional_auto_arguments` built a tag only for properties that `auto_argument_format` could reduce to a single xtml type; every other property was dropped by the `filter_map`. Dropped means the grammar has no path to that argument at all, so the model can never emit it, however the caller declared it. The shapes that hit this are ordinary, not exotic: - `anyOf: [{"type": "string"}, {"type": "null"}]`, which is what Pydantic emits for `Optional[X]`, - an explicit multi-type list, `{"type": ["integer", "null"]}`, - a description-only property carrying no `type` at all. When a tool's optional properties are all of that kind and it declares no required ones, the arguments block collapses to a const empty string and the tool becomes callable only with no arguments. Give each such property a tag of its own instead, keyed to its name, with the `type` attribute restricted by regex to the xtml type names its schema actually admits and only the body left free. Keying the tag is the point: a bare permissive tag in the optional slot could repeat a required key and overwrite its constrained value, which is the duplicate-emission failure this path must not reopen. A `false` schema admits no value and is still dropped. Every existing fixture used exclusively single-typed properties, which is why the drop went unnoticed; the four tests added here pin the shapes real tool declarations contain. Signed-off-by: Caio Rocha <caio@crafthouse.io>
rmccorm4
reviewed
Sep 17, 2026
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
rmccorm4
reviewed
Sep 23, 2026
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
rmccorm4
reviewed
Sep 23, 2026
This was referenced Sep 24, 2026
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Signed-off-by: Ryan McCormick <rmccormick@nvidia.com>
Signed-off-by: Indrajit Bhosale <iamindrajitb@gmail.com>
Signed-off-by: Ryan McCormick <rmccormick@nvidia.com>
Contributor
|
/ok to test 75e882f |
rmccorm4
approved these changes
Sep 29, 2026
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
This is the grammar-building half of a coordinated change addressing two
tool-calling correctness problems:
Kimi K3 grammar correctness. K3
tool_choice="auto"requests could begina native XTML tool call but still emit undeclared tools, empty required
arguments, incorrectly typed values, duplicate required arguments, or
malformed XTML that the parser could not consume.
Schema enforcement for default-on structural guidance. The generic
structural-tag policy treated an omitted OpenAI
strictfield as relaxed.The new policy uses the declared schema when
strictis omitted ortrue;only explicit
strict:falseopts out.This PR implements those parser and grammar changes. The companion
ai-dynamo/dynamo#13654
owns deployment defaults, activation policy, frontend integration, and
compatibility fallback.
Relationship to #274 and #277
nullable Kimi K3 arguments. feat: Kimi K3 structural tag default #196 already emits a declared-key XTML
alternative for each representable type in
anyOfandtype: [...]unions. Its grammar retains the string/null constraints and excludes
numeric values and unknown argument keys in those cases.
local
$refarguments. feat: Kimi K3 structural tag default #196 already resolves references against thecomplete tool schema and retains typed object and enum constraints,
including recursive definitions. We adapted fix(parsers): resolve Kimi K3 argument schema references #277's remaining pointer
decoding, array-index lookup, expansion limits, and reference-scope guards
into this PR in merge commit
d53f4ca. When inlining is unsafe, theoriginal explicitly typed schema is retained.
Kimi K3 grammar hardening
Kimi K3 emits tool calls through its native XTML
tools,call, andargumentelements. The new
autobuilder follows that model-native format whilepreserving the semantics of automatic tool choice:
<|open|>tools<|sep|>;parallel_tool_calls:falselimits generation to one call;$ref,$defs,anyOf,oneOf,allOf,enum, andconstparticipate inXTML type inference;
requiredorder, avoidingfactorial grammar expansion;
argument tags, preventing duplicate required keys from overwriting a
constrained value;
malformed calls; and
being emitted in the response channel.
TriggeredTagsFormatalso gains optionalexcludes, allowing the K3 grammar toreject invalid reasoning and call-marker transitions before the tools trigger.
Existing builders serialize unchanged when no exclusions are configured.
flowchart LR A[K3 begins generation] --> B{Opens tools channel?} B -- No --> C[Normal assistant answer] B -- Yes --> D[Triggered K3 grammar] D --> E[Select a declared tool] E --> F[Generate call index] F --> G{Schema representable?} G -- Yes --> H[Required typed arguments followed by declared optional arguments] G -- No --> I[Parser-compatible XTML fallback] H --> J[Valid native K3 call] I --> J J --> K[K3 parser produces OpenAI tool_calls]tool_choice="auto"still does not force a tool call. The grammar constrains acall only after the model chooses to begin the native tools channel.
Shared schema behavior
StructuralTagSchemaMode::Autonow follows these rules for all structural-tagbuilders:
strictomittedstrict:truestrict:falsestrictstrict:falseThis PR does not change parser-to-model mappings or deployment defaults.
Correctness validation
With Dynamo and SGLang, K3 KVV correctness improved:
25/49 = 51.0%to48/49 = 98.0%;69/98 = 70.4%to94/98 = 95.9%.The controlled native SGLang ablation provides the strongest causal evidence:
47/48 = 97.9%non-thinking and90/92 = 97.8%thinking;22/48 = 45.8%and61/92 = 66.3%;An activation-only Dynamo patch improved non-thinking to
37/49but did notrepair thinking correctness, exposing the old empty and weakly typed argument
grammar. The complete builder was needed for the final result.
A later near-full Dynamo and vLLM run using this frontend-crates head reached
47/48 = 97.9%non-thinking and83/97 = 85.6%thinking after excludingcontext-length cases.
Performance validation
The tested Kimi workloads did not show a TTFT regression:
0.400 swithout the change and0.356 swith it;1.92 swithout the change and1.79 swith it.These are two measured workload shapes and should not be interpreted as a
general performance improvement.
Unit validation
Focused tests cover:
autoactivation and normal-answer passthrough;