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_injection kwarg parameter (defaults to false).

3. Hardened Tool Calling and Flow Control

  • Prevents crashes and exceptions caused by empty tool_calls arrays 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:
    --temp 0.7 --top-p 1.0 --min-p 0.05 --top-k 20
    
    (Note: --top-p 0.95 is 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.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support