Qwen3.8-27B β€” quimmedes Chat Template

A drop-in replacement Jinja chat template for the Qwen3.8-27B model. It is a hardened, more efficient fork of the stock Qwen3.8-27B template: same message format, same tool-calling protocol, same think block β€” but with a faster default reasoning mode, a finer-grained reasoning-effort dial, and robustness fixes that make it stop crashing on real-world (messy) message histories.

No weights, no tokenizer here. This repo ships only the template. Point it at your existing Qwen3.8-27B weights / tokenizer.

Files

File What it is
chat_template.jinja The template source (human-readable, editable).

Quick usage

All three major serving engines accept a chat-template file directly.

llama.cpp (llama-server / llama-cli):

llama-server -m Qwen3.8-27B.gguf --chat-template-file chat_template.jinja

--jinja (the engine toggle) is on by default, so no extra flag is needed. To pass a reasoning effort at the CLI: add --reasoning-effort low.

More flags to force the total limit of tokens spent, you can use it as a safeguard in case any other limitation fails, it's helpful with Hermes

 --reasoning-budget 2048 
 --chat-template-kwargs "{\"preserve_thinking\": true, \"reasoning_effort\": \"low\"}" 
  --reasoning-budget-message "... I am thinking for too long -- let me gather more info about the task."
  --chat-template-file chat_template_budget.jinja
  • --reasoning-budget The max size of tokens spent in thinking
  • --reasoning-budget-message Message sent after the thinking budget cut
  • --chat-template-kwargs Can also set to low, medium or xhigh by default in case the harness can't do that

vLLM (vllm serve):

vllm serve Qwen3.8-27B --chat-template chat_template.jinja

--chat-template accepts a path to a template file (or a built-in name). Per-request effort can be set via chat_template_kwargs in the API call.

SGLang (sglang.launch_server):

python -m sglang.launch_server --model-path Qwen3.8-27B \
    --chat-template chat_template.jinja

--chat-template accepts "the path of the chat template file" (per the official server args). This only applies to the OpenAI-compatible server.

Option D β€” apply to a tokenizer (transformers / any Jinja pipeline):

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("path/to/Qwen3.8-27B")
with open("chat_template.jinja", encoding="utf-8") as f:
    tok.chat_template = f.read()
tok.save_pretrained("path/to/Qwen3.8-27B")

Reasoning effort

The template reads an optional reasoning_effort variable. Pass it through your chat pipeline to dial the model's thinking budget:

prompt = tok.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
    enable_thinking=True,
    reasoning_effort="medium",   # minimal | low | medium | high | xhigh | ultra | max
)

low is the default when you don't pass anything (see below).


Changes vs. the stock Qwen3.8-27B template

Everything below is a superset of the stock template β€” the core user/assistant/system/tool message layout and the tool_call/function XML protocol are unchanged, so the model behaves identically on well-formed inputs. The differences are in the defaults and in edge-case handling.

1. Default reasoning effort: xhigh β†’ low

Stock quimmedes
Default reasoning_effort xhigh low
Levels supported xhigh, medium, low (3) minimal, low, medium, high, xhigh, ultra, max (7)

The stock template forces maximum reasoning (xhigh) unless you explicitly override it. That means every single turn β€” even a trivial one β€” burns the model's full thinking budget. This fork flips the default to low and expands the dial to 7 levels so you can spend tokens only when a task actually needs them.

2. Robust render_content (no more crashes on odd inputs)

The stock macro assumes every content item is a well-formed dict. This fork accepts the shapes that real frameworks actually send:

  • Plain strings inside a content list β€” ["hello", {"type":"text","text":"hi"}] now renders instead of raising Unexpected item type in content.
  • item.get('type') instead of item.type β€” safe when the item has no type key (no UndefinedError).
  • add_vision_id|default(false) β€” the stock template references add_vision_id with no default, which errors if the variable is never set.
  • {"type":"text","content":...} items β€” supports the alternate content-keyed text shape, not just the text-keyed one.
  • null items β€” silently skipped instead of raising.

3. Cleaner assistant turn (no phantom whitespace)

Stock always emitted think\n\n + content, even when there was no reasoning and no content β€” producing empty think blocks and stray double newlines. This fork:

  • only opens a think block when there is actual reasoning_content;
  • only adds the \n\n separator when there is real content to follow;
  • guards message.reasoning_content is defined before reading it.

Result: byte-clean output with no empty thinking blocks and no dangling newlines β€” important for KV-cache reuse and for downstream parsers.

4. Tool-call arguments: mapping or string

The stock template assumed tool_call.arguments is always a dict and called |items on it. Many frameworks (and some model outputs) send arguments as a JSON string. This fork handles both:

  • arguments is a mapping β†’ rendered as <parameter> blocks (as before);
  • arguments is a string β†’ emitted verbatim (with a trailing newline added if missing).

This is the single biggest compatibility win: it stops hard-failing on string-arg tool calls.

5. Tool-result messages as the first message

Stock: loop.previtem and loop.previtem.role != "tool" β€” if the very first message in the history is a tool result, loop.previtem is undefined and the template errors. This fork adds the loop.first or (...) guard, so a conversation that starts with a tool result renders correctly.

6. Smaller, safer "last user query" scan

The stock version carried a multi_step_tool flag and raised an exception if it couldn't find a user query. This fork replaces it with a simpler last_query_index scan (default -1) and drops the hard exception β€” so a history with no plain user message degrades gracefully instead of crashing.

7. Explicit generation-prompt branches

The think opening at the generation prompt is now spelled out for enable_thinking = true / false / unset (same output as before, just explicit and easier to audit).

8. Attribution markers

{#- quimmedes -#} comment markers at the top and bottom, plus an inline note at the xhigh branch, so the fork is identifiable in the wild.


Why this is better

  1. Cheaper & faster by default. The stock template's xhigh default makes every turn think at maximum effort. Defaulting to low (with 7 levels to scale up) means routine queries use a fraction of the reasoning tokens, and you opt into heavy thinking only when a task warrants it.

  2. It doesn't crash on real data. The stock template is written for perfectly-formed inputs. In practice, frameworks send string content items, string tool arguments, missing type keys, null entries, and tool-first histories β€” all of which the stock template rejects. This fork handles all of them, so it's safe to drop into a production serving stack.

  3. Cleaner output, better cache reuse. No empty think blocks and no phantom newlines means shorter, more predictable prompts β€” which improves prefix/KV-cache hit rates and keeps downstream parsers from choking on whitespace.

  4. Drop-in, no retraining, no behavior change on good inputs. Because the message layout and tool protocol are untouched, a model that works with the stock template works identically here for well-formed conversations β€” you only gain the faster default and the robustness.


Attribution

Forked from the stock Qwen3.8-27B chat template. Modified by quimmedes. Base model: Qwen/Qwen3-27B.

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