Summary
Extend the schema/grammar-constrained tool-argument decoding from #374 (PR #375) to the Qwen and Llama families, which emit JSON tool-call arguments rather than Gemma's bespoke <|"|>-quoted form.
Context
#374 added IToolCallAdapter.BuildArgumentConstraint(tools, vocab) (default null) plus a Gemma-specific byte-level pushdown automaton (GemmaToolArgumentConstraint). The hook is the generalization seam: each adapter owns its wire format. Gemma is wired end-to-end; everyone else returns null (generates unconstrained).
Scope
- A
JsonToolArgumentConstraint (reusable across QwenToolCallAdapter / LlamaToolCallAdapter / QwenCoderToolCallAdapter / DeepSeekToolCallAdapter) targeting standard JSON: {"key": "value", ...} with double-quoted keys/strings, bare numbers/booleans/null, [...] arrays, nested {...} objects, and enum restriction.
- Each adapter's
BuildArgumentConstraint override returns it for its envelope (Qwen <tool_call>{json}</tool_call>, Qwen3-Coder <function=NAME> XML wrapper — note: Coder uses <parameter=…> XML, not JSON, so it may need its own matcher; Llama <|python_tag|>{json}, DeepSeek name<|tool_sep|>{json}).
- Reuse the existing
ToolSchema/CompiledObject/CompiledNode/GrammarVocabulary infra and the same correctness invariants (only declared keys, required-once, value shapes, enum; free string content; default-off byte-identical; allocation-free hot path).
- The JSON form needs the early-engage trick too (the model can emit a merged
{} token) and double-quote handling (JSON " vs Gemma's <|"|> special token — the structural quote is a regular byte here, so string content must be matched char-level with escape handling, or kept permissive between the quotes).
Tests
Mirror ToolGrammarMockTests with a fake JSON tokenizer + model-gated conformance on a real Qwen/Llama GGUF (required-key, foreign-key rejection, enum, array, nested).
Follow-up to #374 / #375.
Summary
Extend the schema/grammar-constrained tool-argument decoding from #374 (PR #375) to the Qwen and Llama families, which emit JSON tool-call arguments rather than Gemma's bespoke
<|"|>-quoted form.Context
#374 added
IToolCallAdapter.BuildArgumentConstraint(tools, vocab)(defaultnull) plus a Gemma-specific byte-level pushdown automaton (GemmaToolArgumentConstraint). The hook is the generalization seam: each adapter owns its wire format. Gemma is wired end-to-end; everyone else returnsnull(generates unconstrained).Scope
JsonToolArgumentConstraint(reusable acrossQwenToolCallAdapter/LlamaToolCallAdapter/QwenCoderToolCallAdapter/DeepSeekToolCallAdapter) targeting standard JSON:{"key": "value", ...}with double-quoted keys/strings, bare numbers/booleans/null,[...]arrays, nested{...}objects, andenumrestriction.BuildArgumentConstraintoverride returns it for its envelope (Qwen<tool_call>{json}</tool_call>, Qwen3-Coder<function=NAME>XML wrapper — note: Coder uses<parameter=…>XML, not JSON, so it may need its own matcher; Llama<|python_tag|>{json}, DeepSeekname<|tool_sep|>{json}).ToolSchema/CompiledObject/CompiledNode/GrammarVocabularyinfra and the same correctness invariants (only declared keys, required-once, value shapes, enum; free string content; default-off byte-identical; allocation-free hot path).{}token) and double-quote handling (JSON"vs Gemma's<|"|>special token — the structural quote is a regular byte here, so string content must be matched char-level with escape handling, or kept permissive between the quotes).Tests
Mirror
ToolGrammarMockTestswith a fake JSON tokenizer + model-gated conformance on a real Qwen/Llama GGUF (required-key, foreign-key rejection, enum, array, nested).Follow-up to #374 / #375.