Canonical Qwen 3.8 chat protocol: the ChatML turn structure, the reasoning gate and the tool-call grammar.
More...
|
| std::string | Mila::Dnn::Qwen::formatPrompt (std::span< const Conversation::Turn > history, bool enable_thinking, ReasoningEffort effort=ReasoningEffort::Medium, const std::string &tool_signatures={}) |
| | Render a history into Qwen's ChatML prompt, primed for generation.
|
| std::string | Mila::Dnn::Qwen::formatToolCall (const std::string &name, const std::string &arguments) |
| | One tool call rendered in the grammar the model emits it in.
|
| std::string | Mila::Dnn::Qwen::formatTurn (const Conversation::Turn &message) |
| | Render one completed turn, delimiters included.
|
| std::optional< Conversation::ToolCall > | Mila::Dnn::Qwen::parseToolCall (const std::string &response) |
| | The first tool call in a response, or nothing when it holds none.
|
| ReasoningEffort | Mila::Dnn::Qwen::reasoningEffortFromScale (int level) |
| | Map a 1..5 effort scale onto the three levels the model knows.
|
|
std::string_view | Mila::Dnn::Qwen::reasoningInstruction (ReasoningEffort effort) |
| | The instruction the template prepends to the system turn, verbatim. Empty for Medium.
|
| std::string | Mila::Dnn::Qwen::serializeToolSignatures (const std::string &tools_json) |
| | The tool signatures as the template renders them: one JSON object per line.
|
| std::string | Mila::Dnn::Qwen::toolsSection (const std::string &signatures) |
| | The tools section of the system turn, verbatim from the template.
|
| nlohmann::json | Mila::Dnn::Qwen::typedParameterValue (const std::string &text) |
| | The value of one <parameter=...> block, typed the way the schema-free text allows.
|
|
|
constexpr std::string_view | Mila::Dnn::Qwen::kThinkClose = "</think>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kThinkOpen = "<think>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kToolCallClose = "</tool_call>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kToolCallOpen = "<tool_call>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kToolResponseClose = "</tool_response>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kToolResponseOpen = "<tool_response>" |
|
constexpr std::string_view | Mila::Dnn::Qwen::kTurnClose = "<|im_end|>" |
| constexpr std::string_view | Mila::Dnn::Qwen::kTurnOpen = "<|im_start|>" |
| | The turn delimiters.
|
Canonical Qwen 3.8 chat protocol: the ChatML turn structure, the reasoning gate and the tool-call grammar.
The 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 that brought Gemma's down (see Gemma.Protocol.ixx). It was folded here from the Chat harness, where it was written first.
Rendered from the checkpoint's own chat template rather than from the Qwen 3 conventions it resembles, which differ in three places that all break silently: the call grammar is nested tags and not a JSON object, reasoning effort is a trained parameter with its own instruction text, and the generation primer leaves the reasoning span OPEN.
One file for the whole protocol because the pieces agree by construction – the markers here are the ones BpeVocabulary::loadQwen registers from the checkpoint vocabulary, so a template written apart from its parser could drift from it unnoticed.
String-level parse/format only, matching Gemma's module.
| std::string Mila::Dnn::Qwen::formatPrompt |
( |
std::span< const Conversation::Turn > | history, |
|
|
bool | enable_thinking, |
|
|
ReasoningEffort | effort = ReasoningEffort::Medium, |
|
|
const std::string & | tool_signatures = {} ) |
|
inlineexport |
Render a history into Qwen's ChatML prompt, primed for generation.
There is no BOS: the checkpoint sets add_bos_token false, and a leading marker the model was not trained with costs a position and shifts every one after it.
The system turn is assembled here rather than taken from history, because the template orders it – reasoning instruction, then tools, then whatever the caller configured – and a caller that had already concatenated them could not produce that order.
- Parameters
-
| history | The conversation so far, oldest turn first. A system turn in it is ignored: this function assembles that turn itself, for the reason above. |
| enable_thinking | False makes the primer carry a CLOSED, empty reasoning span, which is the model's own suppression mechanism: a finished channel is one it continues past. True leaves the span OPEN, so the response begins inside the reasoning and carries a closing marker with no opening one. |
| effort | Selects the trained instruction text for the reasoning budget. Medium is the checkpoint's default and emits NOTHING – its instruction is the absence of one – so it is not a middle setting between the other two but the unmodified model. Ignored entirely when enable_thinking is false, since the instruction would then describe a channel that never opens. |
| tool_signatures | One JSON object per tool, newline separated – what serializeToolSignatures returns. Empty omits the whole tools section, which is what tells the model there are none. |
- Exceptions
-
| std::invalid_argument | if history is empty or ends on an Assistant turn, since the primer would then open a second consecutive assistant turn. |