|
Mila
Deep Neural Network Library
|
Canonical Gemma 4 native token grammar: the turn template, the tool-declaration renderer, the tool-call grammar and answer extraction. More...
#include <string>#include <string_view>#include <optional>#include <span>#include <stdexcept>#include <vector>#include <cstdint>#include <cstdlib>#include <memory>import nlohmann.json;import Dnn.Models.Conversation;Namespaces | |
| namespace | Mila |
| Mila main API namespace. | |
Typedefs | |
| using | Mila::Dnn::Gemma::GemmaToolCall = Conversation::ToolCall |
| A tool call parsed out of the model's native <|tool_call> emission. | |
Functions | |
| std::string | Mila::Dnn::Gemma::extractAnswer (std::string_view text) |
| Reduce a channel-structured response to just the user-facing answer. | |
| std::string | Mila::Dnn::Gemma::formatPrompt (std::span< const Conversation::Turn > history, const std::string &tool_declarations={}, bool continue_open=false) |
| Render a conversation into Gemma's native prompt, primed for generation. | |
| std::string | Mila::Dnn::Gemma::formatToolCall (std::string_view name, std::string_view arguments_json) |
| Render an assistant tool call back into Gemma's native call grammar. | |
| std::string | Mila::Dnn::Gemma::formatToolResponse (std::string_view name, std::string_view result_json) |
| Render a client-executed tool result into Gemma's <|tool_response> grammar. | |
| std::string | Mila::Dnn::Gemma::formatTurn (const Conversation::Turn &message) |
| One closed turn: <|turn>{role} {content}<turn|> . | |
| std::optional< GemmaToolCall > | Mila::Dnn::Gemma::parseToolCall (std::string_view text) |
| Parse the most recent native Gemma tool call out of accumulated model output, once generation has stopped right after <tool_call|>. | |
| std::string_view | Mila::Dnn::Gemma::roleSpelling (Conversation::Role role) |
| How the template spells a role. | |
| std::string | Mila::Dnn::Gemma::serializeToolDeclarations (const std::string &tools_json) |
| Render tool schemas into Gemma's trained <|tool>declaration:...<tool|> grammar. | |
| std::string | Mila::Dnn::Gemma::stripControlTokens (std::string_view text) |
| Remove every enumerated control token, then scrub whatever the set missed. | |
Variables | |
| constexpr std::string_view | Mila::Dnn::Gemma::kBos = "<bos>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kChannelClose = "<channel|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kChannelLabels [] |
| The labels a reasoning channel is opened with, for answer extraction. | |
| constexpr std::string_view | Mila::Dnn::Gemma::kChannelOpen = "<|channel>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kEos = "<eos>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kOutputKeys [] = { "output", "result", "content", "stdout", "text" } |
| constexpr std::string_view | Mila::Dnn::Gemma::kPad = "<pad>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kStringDelimiter = "<|\"|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kThink = "<|think|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kThoughtPrime = "<|channel>thought\n<channel|>" |
| The empty reasoning channel that opens a fresh model turn. | |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolCallClose = "<tool_call|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolCallOpen = "<|tool_call>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolClose = "<tool|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolOpen = "<|tool>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolResponseClose = "<tool_response|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kToolResponseOpen = "<|tool_response>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kTurnClose = "<turn|>" |
| constexpr std::string_view | Mila::Dnn::Gemma::kTurnOpen = "<|turn>" |
Canonical Gemma 4 native token grammar: the turn template, the tool-declaration renderer, the tool-call grammar and answer extraction.
The Gemma 4 grammar is a property of the model, not of any single adaptor, so this runtime module is the one source of truth for it – the same rule Qwen's module states (see Qwen.Protocol.ixx). The Chat harness consumes it directly and the inference server reaches it through the binding, so neither carries a copy that can drift.
It was seeded from the union of the two prior implementations – the Python gemma_protocol.py, which carried the spec-verified behaviors (the <|"|> string delimiter, tool-response output-field distillation, failed-tool error surfacing), and the C++ GemmaToolCallParser it replaces. The template, the <|tool> declaration renderer and answer extraction came down later, from the same Python module, which then retired: until they did, Gemma's grammar was written THREE times (this module, gemma_protocol.py, and a second template in the server's prompt.py) and the adaptors disagreed – Chat advertised tools as a JSON array while the server rendered the trained declaration form the model was tuned on. See GemmaChatProtocol.md.
String-level parse/format only. Token-level splice into the live KV cache is the decided direction but is post-release (see MilaProductFamily.md).
|
export |
A tool call parsed out of the model's native <|tool_call> emission.
The family-neutral type, so a host describes a conversation once rather than once per family: arguments is a JSON object string, which is what the model wrote and what a dispatcher takes without re-parsing the grammar.
|
inlineexport |
Reduce a channel-structured response to just the user-facing answer.
Reasoning channels appear more than once and INTERLEAVED with answer text – the 12B emits mid-answer thought channels on the agentic path despite the empty-thought prime – so every channel span is removed, not just a leading run. Removing only the markers would leave the label and its reasoning body behind as literal text.
|
inlineexport |
Render a conversation into Gemma's native prompt, primed for generation.
The system turn is assembled here rather than taken from history, because the tool declarations are a SUFFIX of it and a caller that had already concatenated them could not put them in that order.
| history | The conversation so far, oldest turn first. Any system turn in it is ignored: this function assembles that turn itself, for the reason above. |
| tool_declarations | What serializeToolDeclarations returns. Empty advertises none. |
| continue_open | Emit the final turn OPEN, with no closing marker and no thought prime, so the next token continues it. That is the shape after a tool response: the turn already carries its thought channel from before the call, and priming a SECOND empty one mid-turn is off-distribution – the model parrots it back. Otherwise a fresh model turn is opened and primed, which is where the empty thought channel belongs and the only place it does. |
| std::invalid_argument | if continue_open is set on an empty history, since there would be no turn to resume. |
|
export |
Render an assistant tool call back into Gemma's native call grammar.
Non-object / unparseable arguments render as an empty body rather than throwing, so a malformed history entry degrades to a bare call.
|
export |
Render a client-executed tool result into Gemma's <|tool_response> grammar.
When the result is a JSON envelope only its primary output field is surfaced (the first non-empty of output/result/content/stdout/text); metadata siblings are dropped. A failed tool ({"content": "", "error": "..."}) has no usable output field, so its error is surfaced explicitly – without it the model sees an empty result and blind-retries. A non-JSON result is passed through under the value: key, which is what the canonical template emits for a non-mapping response.
|
inlineexport |
One closed turn: <|turn>{role}
{content}<turn|>
.
An Assistant turn appends each of its tool calls in the native call grammar. A Tool turn carries its result ALREADY rendered – formatToolResponse is the renderer, and it needs the tool's name, which a Conversation::Turn does not carry.
|
export |
Parse the most recent native Gemma tool call out of accumulated model output, once generation has stopped right after <tool_call|>.
Returns nullopt when no <|tool_call> ... call:name{...} block is present or the block is malformed (so the caller surfaces the text as-is rather than looping on a call it cannot dispatch).
|
inlineexport |
How the template spells a role.
The assistant is "model" in Gemma's vocabulary, and a tool result is a USER turn – rendering Tool by its own name would open a role the model was never trained to read.
|
inlineexport |
Render tool schemas into Gemma's trained <|tool>declaration:...<tool|> grammar.
There is ONE declaration form because Gemma has one. A plain-text JSON list is not a Gemma format at all – it is prose plus a JSON dump – and when it was measured against this the 12B invented tools that did not exist.
Deliberately NO call-syntax instructions: the model emits calls through its trained <|tool_call> protocol, so teaching a foreign call format only confuses it.
Declarations concatenate with no separator and no leading blank line, which is how the template appends them to the system turn – <|tool> is an atomic special token, so it needs no whitespace to separate it from the system prose.
ONE deliberate deviation from the template: it closes the parameters block from inside its type: branch, so a schema with no type leaves the block unclosed and a comma dangling. This always closes it. Malformed either way; this stays parseable.
| tools_json | A JSON array of tool schemas, each either an OpenAI function envelope ({type, function:{name, description, parameters}}) or a bare declaration. Filtering which tools to advertise is the host's business, not the model's – a harness with UI-only tools excludes them before calling. |
|
inlineconstexprexport |
The labels a reasoning channel is opened with, for answer extraction.
|
inlineconstexprexport |
The empty reasoning channel that opens a fresh model turn.
Load-bearing rather than cosmetic: without it the 12B emits ghost thought channels and generation degenerates. It belongs at the START of a fresh turn and nowhere else – priming a second one mid-turn is off-distribution and the model parrots it back (measured: empty-channel echoes and one 682-character runaway).