Laguna XS / S 2.1 Improved Chat Template
This repository provides an improved and hardened version of the chat_template.jinja for Poolside's Laguna models (specifically Laguna-XS-2.1 and Laguna-S-2.1).
In addition to the template refinements, the project includes a custom GBNF grammar (laguna-tool-call-guard.gbnf). This grammar restricts model outputs to ensure strict parsing of tool calls and reasoning blocks. It is designed to address critical failure modes in the Laguna 2.X lineup, such as models attempting to reason while calling tools or abruptly ending a turn immediately after reasoning without providing non-reasoning output to the user. This mechanism restricts model output structure rather than performing conventional parsing.
The main motivation behind the template version was to provide a reliable way to make the model think consistently. By default, with thinking enabled, the model is supposed to decide on its own when to invoke reasoning; however, it tends to be quite lazy about doing so (especially the XS variant). This leads to inconsistent output quality across complex tasks. This template addresses that limitation while also incorporating hardening against edge-case failures, runaway tool-calling behavior, and prompt injection risks based on Poolside's original template (laguna_glm_thinking_v8).
Key Improvements and Fixes
1. Granular Reasoning Controls
Forced Reasoning
Addresses "lazy" reasoning behavior where the model skips thinking when it should by introducing the force_thinking parameter. This primes the reasoning block with force_thinking_prefill (defaults to newline) directly into the generation prompt.
Reasoning Effort Presets
Added a reasoning_effort kwarg accepting "none", "auto", and "max" to easily toggle reasoning behaviors without manually juggling multiple boolean flags.
Configurable History Retention (preserve_thinking)
In the original template, historical reasoning preservation was permanently enforced with no control over it. While this maximizes reasoning continuity and KV prefix-cache reuse in complex agentic workflows, retaining thousands of historical CoT tokens is often unnecessary in standard, non-agentic conversational settings. This template exposes the preserve_thinking parameter (defaults to true to maintain original behavior), allowing you to set it to false to reclaim context window space while still keeping active reasoning intact during intermediate tool loops.
2. Token Smuggling and Injection Protection
- Implements validation checks to detect and block forbidden special and control tokens across all roles (
system,user,assistant,tool, and tool parameters). - Active by default to prevent role hijacking and prompt smuggling, but can be bypassed when needed via the
allow_injectionkwarg parameter (defaults tofalse).
3. Hardened Tool Calling and Flow Control
- Prevents crashes and exceptions caused by empty
tool_callsarrays and handles non-mapping tool arguments safely. - Incorporates concrete operational rules into the
<system>tool block to mitigate speculative, runaway tool calling once a response has already been formulated.
Kwargs Summary
| Parameter | Type | Default | Description |
|---|---|---|---|
enable_thinking |
bool |
true |
Enables or disables reasoning generation. |
preserve_thinking |
bool |
true |
Retains past reasoning traces across user turns to maximize reasoning quality and KV prefix-cache hits. Can be set to false to save context tokens in non-agentic environments. |
force_thinking |
bool |
false |
Forces the model to think on each turn via prompt priming. |
reasoning_effort |
string |
unset |
Presets: "none" (disable), "auto" (model decides), or "max" (force thinking). |
allow_injection |
bool |
false |
Disables token smuggling protection against reserved control tokens. |
Out of the box, all additional parameters default to matching the upstream configuration (enable_thinking=true, preserve_thinking=true, and force_thinking=false). Without tuning any of the extra knobs, the template functions as a direct drop-in replacement—behaving identically to the original Poolside template while transparently applying the underlying stability, boundary, and security fixes.
Recipes
1. llama.cpp
Recommended Sampling Parameters
- Full-precision (BF16):
--temp 1.0 --top-p 1.0 --min-p 0 --top-k 20 - Quantised models:
(Note:--temp 0.7 --top-p 1.0 --min-p 0.05 --top-k 20--top-p 0.95is also worth evaluating depending on the task).
Execution Modes
- Thinking by default (forced reasoning):
--reasoning on --chat-template-file /path/to/chat_template.jinja --chat-template-kwargs '{"force_thinking": true}' - Instruct mode (no thinking):
--reasoning off --chat-template-file /path/to/chat_template.jinja --chat-template-kwargs '{"force_thinking": false}' - Thinking by default with context saving (purging historical reasoning):
--reasoning on --chat-template-file /path/to/chat_template.jinja --chat-template-kwargs '{"force_thinking": true, "preserve_thinking": false}'
Using the Grammar File:
You can also provide the tool-call guard grammar directly to llama.cpp using the --grammar-file argument, pointing it to laguna-tool-call-guard.gbnf for strict tool-call format support.
2. Pi (models.json)
Provider and model configuration entry for Pi with configurable thinking levels:
{
"providers": {
"llama-swap": {
"baseUrl": "http://localhost:1337/v1",
"api": "openai-completions",
"apiKey": "*",
"models": [
{
"id": "laguna-xs-2.1",
"name": "Laguna XS 2.1",
"input": [
"text"
],
"reasoning": true,
"compat": {
"thinkingFormat": "chat-template",
"chatTemplateKwargs": {
"enable_thinking": {
"$var": "thinking.enabled"
},
"reasoning_effort": {
"$var": "thinking.effort",
"omitWhenOff": true
},
"preserve_thinking": true
},
"thinkingLevelMap": {
"off": "none",
"minimal": null,
"low": null,
"medium": null,
"high": null,
"xhigh": null,
"max": "max"
},
"supportsDeveloperRole": false,
"supportsReasoningEffort": true
},
"contextWindow": 262144,
"maxTokens": 32768,
"cost": {
"input": 0,
"output": 0,
"cacheRead": 0,
"cacheWrite": 0
}
}
]
}
}
}
Compatibility
This implementation has only been tested on llama.cpp, but it should work anywhere the Jinja2 engine supports standard filters like is string, is mapping, and default.
Credits and License
This implementation incorporates contributions from Poolside (original version), Reddit user u/SnooPaintings8639 (method to force reasoning), and Artem Boldariev.
This project is distributed under the terms of the OpenMDW License, version 1.1.