Gemma4 native tool calling: parser, lazy generation and chat-template hardening - #4525
Open
DassaultFalconKing wants to merge 13 commits into
Open
Conversation
Collaborator
|
Style check fails: Also I tried to build it, but it does not compile: |
Author
|
@mzegla saw the Compiling error yesterday, in the night. The problem is that this fork/PR is based on the version 2026.4, the release is the ver. 2026.3.1, and the upstream is the ver. 2026.5. My mistake was the very bad versioning work. after the successful testing of the 2026.4 based variant i even lost the reproducibly good binary and had to do the archaeology to recover it, while porting the changes to the Version 2026.5 core. As a result, i got the working .5 binary and recovered the 2026.4 but didnt prove the workability yet. |
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.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
JIRA/Issue if applicable.
Describe the changes.
🧪 Checklist
``## Summary
This PR hardens native Gemma4 tool calling in OVMS across three layers that must work together:
The work was driven by live Gemma4 failures observed with OpenAI-compatible agent clients and then cross-checked against the native Gemma4 protocol, existing OVMS/OpenVINO structured-generation primitives, vLLM parsing behavior, and llama.cpp lazy tool activation semantics.
The implementation remains native to OVMS/OpenVINO.
No vLLM or llama.cpp parser/generator implementation code is transplanted into OVMS.
Motivation
The existing Gemma4 path could fail in several distinct ways that looked similar from an agent client's perspective:
tool_choice=requiredor a named tool could silently lose its generation constraint after structured-output validation failure;tool_choice=autowas either effectively unguided or risked becoming equivalent torequired;call:text could be over-promoted into executable tool calls.These are separate Parser, Generator, and input-template problems. Fixing only the parser is insufficient if generation never emits the tool structure in the first place, and fixing only generation is insufficient if the emitted structure cannot be recovered safely.
A parser cannot recover a tool call that the Generator never caused the model to emit.
1. Gemma4 parser hardening
The Gemma4 tool parser now understands the native protocol recursively rather than treating tool arguments as a mostly flat serialization problem.
The parser handles:
<|tool_call>call:name...<tool_call|>boundaries;<|"|>;null;Lossless numeric handling
Tool arguments are API payload text, not values that OVMS needs to perform arithmetic on.
Large integers and precise decimals therefore should not be unnecessarily routed through
uint64_tordoubleduring normalization.The parser preserves numeric lexical representation while converting native Gemma4 argument syntax into JSON.
This avoids avoidable precision loss in values such as identifiers, counters, hashes represented numerically, or high-precision decimal fields.
Bounded malformed-call handling
Malformed tool syntax is kept bounded to the current candidate instead of poisoning the remainder of the stream.
A later valid tool call can still be recovered after a malformed one.
Request tool registry validation
When request tool schemas are available, executable tool calls are checked against the request's actual tool registry.
Unknown tools are not promoted into executable calls.
This is particularly important for recovery paths.
Guarded bare
call:recoveryLive Gemma4 traces showed a narrow variant where the model may close a reasoning channel and continue directly with:
without repeating
<|tool_call>.The parser can recover this observed shape, but only under bounded conditions:
call:must begin at a logical line/phase boundary;Arbitrary prose such as:
is deliberately not treated as an executable call.
This recovery is intentionally narrow rather than a general search for
call:substrings.2. Parser provenance: vLLM as an independent behavioral reference
While hardening the parser, the Gemma4 implementation in vLLM was used as an independent protocol reference.
The useful comparison points were:
<|"|>string delimiter;This is not a source-code port of the vLLM parser.
The useful part carried into OVMS is the protocol-aware parsing model and its edge cases, implemented inside OVMS's existing parser interfaces and streaming response machinery.
3. Explicit Gemma4 Generator policy
This PR also adds an explicit Gemma4 tool-constraint policy to generation.
The implementation separates tool behavior into:
Disabled
Used when:
tool_choice=none.No tool grammar is imposed.
Hard
Used for:
tool_choice=required;Hard choices are represented using native OpenVINO GenAI structural tags.
They:
parallel_tool_calls;A hard OpenAI API contract must not silently turn into unconstrained generation because structured-output validation failed.
Optional reasoning before a hard tool call
Gemma4 may emit a reasoning channel before selecting a required or named tool.
The hard grammar therefore accepts either:
or:
The optional path is represented as a
Union, rather than using an emptyConstString.This matters because xgrammar rejects empty
ConstStringnodes.4. Lazy
tool_choice=autoautomust not meanrequired.The model must remain able to answer with normal text when no tool is necessary, while any tool call it does initiate must conform to the available tool registry and JSON schema.
Gemma4
autotherefore uses OpenVINO GenAITriggeredTags:The trigger is the native Gemma4 tool marker:
Once triggered, the generated call is constrained to the request's available tool names and schemas.
Before the trigger, normal assistant text remains legal.
llama.cpp provenance
llama.cpp was used as an independent behavioral reference for this lazy/optional activation model.
Only the semantics were ported.
OVMS does not use llama.cpp's PEG grammar, sampler, or parser implementation.
The implementation here uses the existing native OpenVINO GenAI structured-output abstraction.
A concise description is:
5. Why
TriggeredTagsfits OVMS architectureThis PR does not introduce an external generation architecture into OVMS.
The layering is:
TriggeredTagsalready exists in OpenVINO GenAI and is already used by OVMS for other model-specific generation behavior.This patch applies that existing abstraction to Gemma4's native protocol.
6.
parallel_tool_callsBoth lazy and hard modes respect the OpenAI
parallel_tool_callsrequest field.When parallel tool calls are disabled:
When they are enabled, subsequent compatible tool calls remain legal.
This behavior is covered for:
auto;required;7. Tool schema and name validation
The Generator rejects active Gemma4 tool configurations that cannot be represented safely.
Examples include:
response_formatand active tool generation constraints.The same tool-name shape accepted by the Generator is compatible with what the parser can safely recognize and execute.
8. Google Gemma4 chat-template compatibility
The work was also tested against the canonical Google Gemma4 tool protocol and chat-template behavior.
A multi-turn incompatibility was found in OVMS input adaptation.
OVMS may parse JSON text from a
role: "tool"message into an object before rendering the chat template.That is not universally safe.
The canonical Gemma4 template includes a path that iterates message content parts and calls:
part.get('type')If a JSON object has already replaced the original content string, Jinja iterates the object's keys, producing strings rather than part objects.
The resulting failure is equivalent to:
Capability-driven fix
The chat-template analyzer now distinguishes between templates where converting tool-response JSON into an object is safe and templates where a parts scan makes that transformation unsafe.
A capability controls the history adaptation.
JSON tool-response content is converted to an object only where the detected template semantics support it.
This preserves the existing upstream tool-definition adaptation behavior as well.
9. Template adaptation is conservative
The tool-response conversion itself is intentionally narrow.
Only:
role == "tool";contentis a string;are eligible.
Arrays, scalars, invalid JSON, user messages, and assistant messages retain their original string semantics.
This avoids changing general OpenAI message behavior merely because content happens to look like JSON.
10. Regression coverage
The PR adds contract tests covering the failure modes that motivated the work.
Generation contracts
Coverage includes:
tool_choice=none;response_formatpreservation when tools are inactive;autousingTriggeredTags;autoremaining optional;parallel_tool_calls;ConstString.Parser contracts
Coverage includes:
call:examples;Chat-template contracts
Coverage includes:
part.get(...)scan;responseadaptation.11. Real-world validation background
This patch grew out of live Gemma4 deployment work using OVMS on Windows with Intel Arc hardware and OpenAI-compatible agent clients.
The local integration environment also includes:
tool_parser: gemma4;reasoning_parser: gemma4;An earlier exact-source runtime baseline in that environment produced:
through the OVMS REST path.
That result is useful supporting evidence for the architecture, but it is not claimed as exact-head validation of this PR, because additional parser, lazy-generator, and template-compatibility changes were made afterward.
The authoritative validation for this PR head should be the upstream CI results and any exact-head runtime testing performed during review.
12. External implementation references and code provenance
The implementation was informed by several independent sources, each for a different purpose:
Google Gemma4
Authority for:
vLLM
Independent behavioral reference for:
No vLLM parser code is copied into this patch.
llama.cpp
Independent behavioral reference for:
auto.No llama.cpp grammar, sampler, or parser code is copied into this patch.
OpenVINO GenAI / OVMS
Native implementation mechanism:
StructuredOutputConfig;TriggeredTags;13. What this PR intentionally does not include
The development fork contains additional deployment and diagnostics work used to validate Gemma4 on Windows and Intel Arc hardware.
That includes launch profiles, acceptance harnesses, long-context experiments, and local chat-template deployment helpers.
Those are intentionally not included in this upstream core PR.
This PR is limited to the reusable OVMS runtime changes:
autoconstraints;Current PR coordinates
Upstream base:
Proposed head:
The branch is based directly on the upstream commit above and contains only the Gemma4 runtime and contract-test scope described in this PR.