DGX Spark · documentation

The chat template

docs/chat-template.md Last pushed 3 September 2026

DeepSeek V4 ships no Jinja chat template. The reference for how a conversation is turned into tokens is a Python file in the model release, encoding/encoding_dsv4.py, and the release README points at it. Every Jinja template you find for V4 is somebody's reconstruction; most get the thinking switch wrong, and the visible symptom is a model that "thinks out loud" in the answer even though you asked it not to.

templates/deepseek_v4.jinja is a direct port of encode_messages() from that file. It was checked byte-for-byte against the reference encoder on eleven conversation shapes — plain, system prompt, multi-turn, thinking on and off at every effort level, tools with and without a system prompt, a tool-call round trip, and tool calls inside a thinking conversation. The only difference is whitespace inside JSON tool schemas, which the tokenizer does not care about.

What the encoder does

Written out, with <BOS> for the model's begin-of-sequence token:

Chat (thinking off) — the prompt ends with </think>, which tells the model the thinking phase is already over:

<BOS>{system prompt, bare, if any}<|User|>{user}<|Assistant|></think>

Thinking on — the prompt ends with <think> and, in thinking mode only, the effort text comes first, before anything else:

<BOS>{effort text}{system prompt}<|User|>{user}<|Assistant|><think>

reasoning_effort: low has empty effort text. high and max inject DeepSeek's own paragraphs asking for thorough deliberation; they are copied verbatim from the encoder.

Earlier assistant turns are encoded as </think>{content}<EOS> — previous reasoning is dropped (drop_thinking=True in the reference) so a long thinking conversation does not fill its own context with old deliberations.

Tools add a ## Tools block after the system prompt describing the DSML call syntax and listing the JSON schemas. Tool calls the model made are re-encoded as <|DSML|tool_calls>…</|DSML|tool_calls> blocks; tool results are wrapped in <tool_result> elements and consecutive results are merged into one user turn. When tools are present, the reference keeps reasoning on every assistant turn (drop_thinking=False), and so does the template.

Driving it from the API

The template reads four variables: thinking, enable_thinking, reasoning_effort, and the request's tools. Defaults come from template_vars_default in config.yml (effort low, no thinking, so it derives to off). Any request can override them:

{
  "model": "deepseek-v4-flash-0731",
  "messages": [...],
  "chat_template_kwargs": {"thinking": true, "reasoning_effort": "max"}
}

template_vars is accepted as an alias of chat_template_kwargs. The top-level OpenAI-style fields work as well, and can switch thinking on by themselves: when the request sets no thinking, the template uses enable_thinking if present, otherwise reasoning_effort (none/low → off, medium/high/max → on; xhigh is read as max). The server maps reasoning.effort / reasoning.enabled onto the same two variables. With thinking on, the server splits the output at </think> and returns the reasoning in reasoning_content.

If you edit it

Change it only against the reference. The check is mechanical: render a conversation with the template, encode the same conversation with encoding_dsv4.py, compare the strings. A template that "looks right" but differs by one <think> changes how the model behaves on every request.

Every number here was measured. Open an issue if one looks wrong.

All documentation