Qwen-Sharp-Chat-Templates

froggeric's Qwen-Fixed-Chat-Templates v22, with a terseness system prompt spliced in. Model-agnostic: it names no model, so it drops into any Qwen3.5, 3.6, or 3.8 build without claiming to be something it isn't.

v22 (the Qwen 3.8 update). Upstream now covers Qwen 3.8 alongside 3.5/3.6 and adds prompt-directed reasoning-effort steering (xhigh/high/medium/low), which upstream defaults to xhigh — it injects a "think at maximum effort" system line even when the caller asks for nothing. That default is a behavioral change from v21.3, so this Sharp build suppresses it: when no effort is requested you get the tuned terseness behavior and nothing else, exactly as v1. An explicit effort still passes straight through to upstream's machinery and renders — the feature survives, it just isn't forced on. Pass it via chat_template_kwargs (a bare top-level reasoning_effort field is dropped by OpenAI-style servers before the template sees it):

{"messages": [...], "chat_template_kwargs": {"reasoning_effort": "low"}}

Dagger-Qwen3.6-27B and Nail-Qwen3.6-35B-A3B shipped with the v1 template baked into their weights — that is the exact template embedded in those GGUF and MLX builds (template_version = "qwen3.6-froggeric-v21.3", terseness, no reasoning-effort steering), and it lives here in archive/v1-qwen3.6-froggeric-v21.3/. The chat_template.jinja at the root of this repo is the newer v22 described above; drop it in to move a model onto it. The template is published separately because it is the portable part — the thing worth reusing is not tied to either model.

What it changes

Fourteen inserted lines. Everything else is byte-identical to upstream v22.

{%- set _terse %}
Answer directly, after thinking. Lead with the answer, then only what it needs to be correct and usable.
Never: open with preamble or pleasantries; restate the question; add filler transitions; hedge with niceties; or repeat a point you've already made.
Always: keep essential steps, caveats, uncertainties, and specifics — never drop correctness or a needed warning for brevity. Keep the final answer lean. Use the least structure that conveys it (plain prose when short; lists or code only when they earn their place). If genuinely uncertain, say so and explain why — never omit uncertainty for the sake of brevity.
If a user request is genuinely ambiguous, ask a sharp question, don't guess.
{%- endset %}
{%- if not _sc %}
    {%- set _sc = _terse | trim %}
{%- else %}
    {%- set _sc = (_sc | trim) ~ '\n\n' ~ (_terse | trim) %}
{%- endif %}
{%- if reasoning_effort is not defined %}
    {%- set reasoning_instructions = '' %}
{%- endif %}

Two things happen here. The if/else keeps your own system prompt — the terseness block is appended after it, nothing you pass in is replaced. The final if blanks upstream's reasoning-effort line only when the caller didn't ask for one, so the forced-xhigh default is gone but an explicit reasoning_effort still renders (see the v22 note above).

What it does, measured

Every row below is the same weights with only the template swapped, so the difference is the prompt and nothing else. Measured on ThinkingCap-Qwen3.6-27B, MLX 6-bit, temp 1.0, n=3 seeds.

stock template Sharp template change
Claw-Eval, answer component 59.3 66.7 +7.4%
Claw-Eval, overall 55.0 58.8 +3.8%
Claw-Eval answer tokens 5393 2217 -59%
MMLU-Pro tokens per correct answer 1601 1248 -22%

Green is better in both directions: accuracy up, tokens down. Accuracy changes are absolute differences in score, token changes are relative.

Roughly: the same answers, in a bit over half the words. The accuracy column moves up rather than down, which is the part that matters — terseness that costs correctness is just truncation.

These figures come from one 27B model on two benchmarks. They are evidence that the prompt does something, not a promise of a specific number on your model.

Both benchmarks are single-turn, and that matters. froggeric's template defaults preserve_thinking to true, so reasoning from earlier turns stays in the context. With one turn there is nothing to preserve and it costs nothing. In a multi-turn agentic loop the prompt grows every step, and you can end up paying more in prefill than the shorter outputs save — one user measured a 19% wall-clock regression on a multi-turn coding and audit workload while output quality held. If that is your shape of work, pass preserve_thinking=false in chat_template_kwargs and measure both ways.

The terseness instruction targets prose padding: preamble, restating the question, filler transitions. Where the deliverable is mostly code or a structured artifact there is less padding to remove, so expect less from it — and the prompt deliberately protects those ("lists or code only when they earn their place", "never drop correctness for brevity").

Use

MLX / transformers — drop chat_template.jinja into the model directory.

hf download peculiar-ragdoll/Qwen-Sharp-Chat-Templates chat_template.jinja \
  --local-dir /path/to/your-model

Two places can hold a template, and old runtimes disagree about which wins. A model directory can carry it as chat_template.jinja and as a chat_template key inside tokenizer_config.json. Anything on transformers ≥ 4.51 — which includes current oMLX and LM Studio — prefers the .jinja file, so the drop-in just works. Older runtimes read only the embedded key and ignore the file, and then the drop-in silently does nothing.

If the directory has both and you are unsure of your runtime, patch both — that is what chat_template_oneline.txt is for: paste it as the chat_template value. Or run scripts/check_applied.py (below), which reports every source and flags a mismatch.

oMLX — drop chat_template.jinja into the model directory and rescan. Verified on oMLX (transformers 5.12.1) by loading a model with the Sharp template as chat_template.jinja and a deliberately different template embedded in tokenizer_config.json: the .jinja file won, and the model reported the terseness rules with the caller's own system prompt still in force.

GGUF — rewrite the embedded template without requantizing:

pip install gguf
gguf-new-metadata \
  --chat-template-file chat_template.jinja \
  input.gguf output.gguf

tokenizer_config.json — use chat_template_oneline.txt, the minified single-line form. It renders identically to the full template (verified by scripts/verify_template.py).

llama.cpp at runtime, without touching the file — pass it per-run instead:

llama-server -m model.gguf --chat-template-file chat_template.jinja -ngl 99
llama-cli    -m model.gguf --chat-template-file chat_template.jinja -ngl 99

Same effect, and it fully replaces whatever is embedded in the GGUF — verified against a build whose embedded template names a specific model: with the flag, the served template is byte-identical to this file and the model name is gone. Check it yourself with curl localhost:8080/props | jq -r .chat_template, or render a prompt through POST /apply-template.

Two caveats. --jinja is enabled by default in current llama.cpp, so you usually do not need it — on older builds you do, and it must come before --chat-template-file. And the flag is per-invocation: forget it once and you silently get the embedded template back. Rewriting the GGUF with gguf-new-metadata is the durable version; the flag is right for trying it out or for running one template across several models.

Did it actually apply?

Point check_applied.py at a model directory or a .gguf. It finds every template source, renders each, and tells you whether they agree — exits non-zero if the prompt is missing or the two sources disagree.

python3 scripts/check_applied.py /path/to/model-dir
python3 scripts/check_applied.py model.gguf
  [chat_template.jinja]  17143 bytes
     terseness prompt ......... yes
     keeps your system prompt . yes

  [tokenizer_config.json]  110 bytes
     terseness prompt ......... NO (found 0x)

  *** THE TWO SOURCES DISAGREE ***
  Recent transformers uses chat_template.jinja; oMLX and others read the
  copy embedded in tokenizer_config.json. Right now those differ, so what
  you get depends on your runtime. Patch both to the same content.

That case — a fresh .jinja dropped in next to a stale embedded copy — is the most common way this silently does nothing. It also warns if the template names a specific model, which happens when the file was taken from a model repo rather than from here.

Setting reasoning effort

By default there is no reasoning-effort instruction — you get the tuned terseness behavior and nothing else. To turn steering back on for a request, set reasoning_effort to low, medium, high, or xhigh. How you pass it depends on the runtime, and one obvious-looking channel does not work:

How you pass it oMLX llama.cpp transformers Works?
chat_template_kwargs: {"reasoning_effort": "low"} (in the request body) yes — use this
apply_chat_template(..., reasoning_effort="low") (Python) yes
top-level reasoning_effort field (the OpenAI API param) no
{"messages": [...], "chat_template_kwargs": {"reasoning_effort": "low"}}

The last row is the trap. The OpenAI-style top-level reasoning_effort field is consumed by the server (oMLX and llama.cpp both use it internally to pick reasoning-parse behavior for formats like harmony/gpt-oss) and is never handed to the chat template — so a custom Qwen template can't see it, and it silently has no effect here. This isn't something the template can fix: a template only reads the variables the runtime binds at render time. If you need the literal top-level field to work against these servers, put a thin proxy in front that copies reasoning_effort into chat_template_kwargs before forwarding. Otherwise, use the chat_template_kwargs channel above — it works everywhere and needs no code.

Verified on both runtimes: with chat_template_kwargs the steering line renders (oMLX prompt grows +38 tokens for xhigh, +26 for low; llama.cpp/minja POST /apply-template shows the same line); with the bare top-level field it does not.

What it doesn't do

  • It is not a fine-tune, despite the base_model_relation: finetune tag — that is the closest vocabulary HuggingFace offers for "derived from," and it exists so this repo is linked from froggeric's. No weights are involved. It changes what the model is asked for, not what it knows.
  • It does not fix thinking retention by itself — that comes from froggeric's upstream template, which this builds on. If you splice only the terseness block into a stock Qwen template, you get the brevity and not the retention.
  • It is not tuned per model. Every model responds a little differently to a terseness instruction; measure yours. The numbers above are from a 27B; a 4B may need firmer wording.
  • The benchmarks are Qwen3.6. The template covers 3.5, 3.6, and 3.8 alike — upstream unified them into one file — but every figure in the table was measured on a 3.6 model.

Credits

Everything structural here is froggeric's work — the retention fix, the tool-calling handling, the whole template. This repo adds a system prompt and nothing else. scripts/minify_jinja.py is froggeric's, with one patch: it now preserves newlines inside {% set %}…{% endset %} blocks, which upstream's template doesn't contain and this one does.

Apache-2.0, matching upstream.

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

Model tree for peculiar-ragdoll/Qwen-Sharp-Chat-Templates

Finetuned
(4)
this model