Jinja2 prompt template fix to get GLM5.2 running in LM Studio and Unsloth Studio

#6
by Ackerka - opened

I have recently downloaded the Unsloth Q3_K_M quant of GLM5.2 to test on a Mac Studio M3 Ultra with 512GB. The model loaded completely in both the Unsloth Studio and in LM Studio but inference failed silently in Unsloth Studio. Fortunately LM Studio gave the following error message: "Failed to parse Jinja template: Expected identifier following dot operator". As I'm not a Jinja2 specialist ( :-) ) I asked unsloth/qwen3.6-35b-a3b-mtp Q4_K_XL within Unsloth Studio to find the reason and fix the problem. It pointed out the following:
"The Problem
In Jinja2, you cannot use numeric indices like .0 with the dot operator. The dot operator (.) is only for accessing named attributes/keys (e.g., m.role, item.type). Numeric indexing requires bracket notation: [0].
So m.content.0.type is invalid — Jinja2 sees the dot after content and expects an identifier, not a number.
The Fix
Replace all .0 accesses with bracket notation [0]"
After carried out the few changes (e.g. m.content.0.type → m.content[0].type) within LM Studio the model started to work fine.

Concerning the Unsloth Studio I could not test the fix. Although Unsloth Studio also has a chat template editor where I could save the fixes but it had not effect. The GUI said I have to reload the model but if I reloaded the model then it restored the original (wrong) Jinja2 template. As I know the template is within the GGUF among meta-data but I did not figure out how can I update it, yet.
Nevertheless, based on my experience with LM Studio, I suggest to implement the above described Jinja2 prompt template fixes to get the model running locally.

Here is a working Jinja template to copy/paste for LMStudio (essentially implementing the recommendation above):

Huggingface comment didn’t format correctly…

Just need to
find: m.content.0
Replace: m.content[0]

Ok, here is the complete fixed template that works with LM Studio and probably it would work with Unsloth Studio:

[gMASK]<sop>
{%- set effective_reasoning_effort = 'high' if reasoning_effort is defined and reasoning_effort == 'high' else 'max' -%}
{%- if (enable_thinking is not defined or enable_thinking) and effective_reasoning_effort is not none -%}<|system|>Reasoning Effort: {{ effective_reasoning_effort | capitalize }}{%- endif -%}
{%- if tools -%}
{%- macro tool_to_json(tool) -%}
    {%- set ns_tool = namespace(first=true) -%}
    {{ '{' -}}
    {%- for k, v in tool.items() -%}
        {%- if k != 'defer_loading' and k != 'strict' -%}
            {%- if not ns_tool.first -%}{{- ', ' -}}{%- endif -%}
            {%- set ns_tool.first = false -%}
            "{{ k }}": {{ v | tojson(ensure_ascii=False) }}
        {%- endif -%}
    {%- endfor -%}
    {{- '}' -}}
{%- endmacro -%}
<|system|>
# Tools

You may call one or more functions to assist with the user query.

You are provided with function signatures within <tools></tools> XML tags:
<tools>
{% for tool in tools %}
{%- if tool is not none and tool is mapping and 'function' in tool -%}
    {%- set tool = tool['function'] -%}
{%- endif -%}
{% if tool.defer_loading is not defined or not tool.defer_loading %}
{{ tool_to_json(tool) }}
{% endif %}
{% endfor %}
</tools>

For each function call, output the function name and arguments within the following XML format:
<tool_call>{function-name}<arg_key>{arg-key-1}</arg_key><arg_value>{arg-value-1}</arg_value><arg_key>{arg-key-2}</arg_key><arg_value>{arg-value-2}</arg_value>...</tool_call>{%- endif -%}
{%- macro visible_text(content) -%}
    {%- if content is string -%}
        {{- content }}
    {%- elif content is iterable and content is not mapping -%}
        {%- for item in content -%}
            {%- if item is mapping and item.type == 'text' -%}
                {{- item.text }}
            {%- elif item is string -%}
                {{- item }}
            {%- elif item is mapping and item.type in ['image', 'image_url', 'video', 'video_url', 'audio', 'audio_url', 'input_audio'] -%}
                {%- set media_type = item.type | replace('_url', '') | replace('input_', '') -%}
                {{- "<reminder>You are unable to process this " ~ media_type ~ " because you don't have multi-modal input ability. Try different methods.</reminder>" }}
            {%- endif -%}
        {%- endfor -%}
    {%- else -%}
        {{- content }}
    {%- endif -%}
{%- endmacro -%}
{%- set ns = namespace(last_user_index=-1) -%}
{%- for m in messages %}
    {%- if m is not none and m is mapping and m.role == 'user' %}
        {%- set ns.last_user_index = loop.index0 -%}
    {%- endif %}
{%- endfor %}
{%- for m in messages -%}
{%- if m is not none and m is mapping and m.role == 'user' -%}<|user|>{{ visible_text(m.content) }}
{%- elif m.role == 'assistant' -%}
<|assistant|>
{%- set content = visible_text(m.content) %}
{%- if m.reasoning_content is string %}
    {%- set reasoning_content = m.reasoning_content %}
{%- elif '</think>' in content %}
    {%- set reasoning_content = content.split('</think>')[0].split('<think>')[-1] %}
    {%- set content = content.split('</think>')[-1] %}
{%- endif %}
{%- if ((clear_thinking is defined and not clear_thinking) or loop.index0 > ns.last_user_index) and reasoning_content is defined -%}
{{ '<think>' + reasoning_content +  '</think>'}}
{%- else -%}
{{ '<think></think>' }}
{%- endif -%}
{%- if content.strip() -%}
{{ content.strip() }}
{%- endif -%}
{% if m.tool_calls %}
{% for tc in m.tool_calls %}
{%- if tc.function %}
    {%- set tc = tc.function %}
{%- endif %}
{{- '<tool_call>' + tc.name -}}
{% set _args = tc.arguments %}{% for k, v in _args.items() %}<arg_key>{{ k }}</arg_key><arg_value>{{ v | tojson(ensure_ascii=False) if v is not string else v }}</arg_value>{% endfor %}</tool_call>{% endfor %}
{% endif %}
{%- elif m.role == 'tool' -%}
{%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
    {{- '<|observation|>' -}}
{%- endif %}
{%- if m.content is string -%}
    {{- '<tool_response>' + m.content + '</tool_response>' -}}
{%- elif m.content is iterable and m.content is not mapping and m.content and m.content[0].type == "tool_reference" -%}
    {{- '<tool_response><tools>\n' -}}
    {% for tr in m.content %}
        {%- for tool in tools -%}
            {%- if tool is not none and tool is mapping and 'function' in tool -%}
                {%- set tool = tool['function'] -%}
            {%- endif -%}
            {%- if tool is not none and tool is mapping and tool.name == tr.name -%}
                {{- tool_to_json(tool) + '\n' -}}
            {%- endif -%}
        {%- endfor -%}
    {%- endfor -%}
    {{- '</tools></tool_response>' -}}
{%- elif m.content is iterable and m.content is not mapping and m.content and m.content[0] is mapping and m.content[0].output is defined -%}
    {%- for tr in m.content -%}
        {{- '<tool_response>' + tr.output + '</tool_response>' -}}
    {%- endfor -%}
{%- else -%}
    {{- '<tool_response>' + visible_text(m.content) + '</tool_response>' -}}
{% endif -%}
{%- elif m.role == 'system' -%}
<|system|>{{ visible_text(m.content) }}
{%- endif -%}
{%- endfor -%}
{%- if add_generation_prompt -%}
    <|assistant|>{{- '<think></think>' if (enable_thinking is defined and not enable_thinking) else '<think>' -}}
{%- endif -%}
Unsloth AI org

Hello OP @Ackerka thanks for this, when you mean it didnt load in unsloth what do you mean? Like it fails completely. Are you sure you installed the latest version of unsloth and binaries?

Sign up or log in to comment