A pure-Go Jinja template engine purpose-built for rendering LLM chat templates. It compiles and executes the Jinja templates embedded in GGUF model files, turning conversations and tool definitions into the exact token sequences each model expects. Zero dependencies, zero CGO.
Copyright 2026 Ardan Labs
go get github.com/ardanlabs/jinja
The API has two steps: compile a template once, then render it with data as many times as needed. Compiled templates are safe for concurrent use.
package main
import (
"fmt"
"log"
"github.com/ardanlabs/jinja"
)
func main() {
tmpl, err := jinja.Compile("Hello {{ name }}!")
if err != nil {
log.Fatal(err)
}
result, err := tmpl.Render(map[string]any{
"name": "World",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
// Output: Hello World!
}
The primary use case is rendering LLM chat templates. Each model ships a Jinja template that formats conversations into the token layout the model was trained on.
const chatTemplate = `{%- if messages[0].role == 'system' -%}
<|im_start|>system
{{ messages[0].content }}<|im_end|>
{%- endif %}
{%- for message in messages -%}
{%- if message.role != 'system' %}
<|im_start|>{{ message.role }}
{{ message.content }}<|im_end|>
{%- endif -%}
{%- endfor %}
{%- if add_generation_prompt %}
<|im_start|>assistant
{%- endif -%}`
tmpl, err := jinja.Compile(chatTemplate)
if err != nil {
log.Fatal(err)
}
result, err := tmpl.Render(map[string]any{
"messages": []any{
map[string]any{"role": "system", "content": "You are a helpful assistant."},
map[string]any{"role": "user", "content": "What is the capital of France?"},
map[string]any{"role": "assistant", "content": "The capital of France is Paris."},
map[string]any{"role": "user", "content": "What about Germany?"},
},
"add_generation_prompt": true,
})
See the examples directory for more, including tool calling.
{{ expr }}{% if %}, {% for %}, {% set %}, {% macro %}, {% block %}{%- ... -%}, {{- ... -}}{# ... #}~{{ x if condition else y }}{{ items[::-1] }}abs · batch · capitalize · count · default / d · dictsort · escape / e · first · float · fromjson · indent · int · items · join · last · length · list · lower · map · max · min · reject · rejectattr · replace · reverse · round · safe · select · selectattr · sort · string · sum · title · tojson · trim · unique · upper · wordcount
defined · undefined · none · boolean · integer · float · number · string · mapping · iterable · sequence · callable · true · false · odd · even · upper · lower · sameas · eq · ne · gt · ge · lt · le · in
namespace · range · dict · joiner · cycler · raise_exception · strftime_now
.strip() · .split() · .startswith() · .endswith() · .upper() · .lower() · .replace() · .find() · .count() · .lstrip() · .rstrip()
.get() · .items() · .keys() · .values() · .update()
The test suite compiles and renders the chat templates from these models:
| Model | Family |
|---|---|
| DeepSeek-V4-Flash | DeepSeek V4 |
| gemma-4-26B-A4B-it | Gemma 4 |
| GLM-5.2 | GLM 5 |
| gpt-oss-20b | GPT-OSS |
| Kimi-K3 | Kimi K3 |
| laguna | Laguna |
| LFM2.5-VL-1.6B | LFM2.5 |
| Llama-3.2-1B-Instruct | Llama 3.2 |
| Ministral-3-14B-Instruct-2512 | Mistral 3 |
| Mistral-Medium-3.5-128B | Mistral Medium 3.5 |
| NVIDIA-Nemotron-3-Super-120B-A12B | Nemotron 3 |
| Qwen2-Audio-7B | Qwen2-Audio |
| Qwen3-8B | Qwen3 |
| Qwen3-VL-30B-A3B-Instruct | Qwen3-VL |
| Qwen3.5-35B-A3B | Qwen3.5 |
| Qwen3.6-35B-A3B | Qwen3.6 |
| rnj-1-instruct | RNJ-1 |
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
197 followers · starred Apr 2026
1,028 followers · starred Apr 2026
A pure-Go Jinja template engine purpose-built for rendering LLM chat templates. It compiles and executes the Jinja templates embedded in GGUF model files, turning conversations and tool definitions into the exact token sequences each model expects. Zero dependencies, zero CGO.
Copyright 2026 Ardan Labs
go get github.com/ardanlabs/jinja
The API has two steps: compile a template once, then render it with data as many times as needed. Compiled templates are safe for concurrent use.
package main
import (
"fmt"
"log"
"github.com/ardanlabs/jinja"
)
func main() {
tmpl, err := jinja.Compile("Hello {{ name }}!")
if err != nil {
log.Fatal(err)
}
result, err := tmpl.Render(map[string]any{
"name": "World",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(result)
// Output: Hello World!
}
The primary use case is rendering LLM chat templates. Each model ships a Jinja template that formats conversations into the token layout the model was trained on.
const chatTemplate = `{%- if messages[0].role == 'system' -%}
<|im_start|>system
{{ messages[0].content }}<|im_end|>
{%- endif %}
{%- for message in messages -%}
{%- if message.role != 'system' %}
<|im_start|>{{ message.role }}
{{ message.content }}<|im_end|>
{%- endif -%}
{%- endfor %}
{%- if add_generation_prompt %}
<|im_start|>assistant
{%- endif -%}`
tmpl, err := jinja.Compile(chatTemplate)
if err != nil {
log.Fatal(err)
}
result, err := tmpl.Render(map[string]any{
"messages": []any{
map[string]any{"role": "system", "content": "You are a helpful assistant."},
map[string]any{"role": "user", "content": "What is the capital of France?"},
map[string]any{"role": "assistant", "content": "The capital of France is Paris."},
map[string]any{"role": "user", "content": "What about Germany?"},
},
"add_generation_prompt": true,
})
See the examples directory for more, including tool calling.
{{ expr }}{% if %}, {% for %}, {% set %}, {% macro %}, {% block %}{%- ... -%}, {{- ... -}}{# ... #}~{{ x if condition else y }}{{ items[::-1] }}abs · batch · capitalize · count · default / d · dictsort · escape / e · first · float · fromjson · indent · int · items · join · last · length · list · lower · map · max · min · reject · rejectattr · replace · reverse · round · safe · select · selectattr · sort · string · sum · title · tojson · trim · unique · upper · wordcount
defined · undefined · none · boolean · integer · float · number · string · mapping · iterable · sequence · callable · true · false · odd · even · upper · lower · sameas · eq · ne · gt · ge · lt · le · in
namespace · range · dict · joiner · cycler · raise_exception · strftime_now
.strip() · .split() · .startswith() · .endswith() · .upper() · .lower() · .replace() · .find() · .count() · .lstrip() · .rstrip()
.get() · .items() · .keys() · .values() · .update()
The test suite compiles and renders the chat templates from these models:
| Model | Family |
|---|---|
| DeepSeek-V4-Flash | DeepSeek V4 |
| gemma-4-26B-A4B-it | Gemma 4 |
| GLM-5.2 | GLM 5 |
| gpt-oss-20b | GPT-OSS |
| Kimi-K3 | Kimi K3 |
| laguna | Laguna |
| LFM2.5-VL-1.6B | LFM2.5 |
| Llama-3.2-1B-Instruct | Llama 3.2 |
| Ministral-3-14B-Instruct-2512 | Mistral 3 |
| Mistral-Medium-3.5-128B | Mistral Medium 3.5 |
| NVIDIA-Nemotron-3-Super-120B-A12B | Nemotron 3 |
| Qwen2-Audio-7B | Qwen2-Audio |
| Qwen3-8B | Qwen3 |
| Qwen3-VL-30B-A3B-Instruct | Qwen3-VL |
| Qwen3.5-35B-A3B | Qwen3.5 |
| Qwen3.6-35B-A3B | Qwen3.6 |
| rnj-1-instruct | RNJ-1 |
Licensed under the Apache License, Version 2.0. See LICENSE for the full text.
197 followers · starred Apr 2026
1,028 followers · starred Apr 2026