Skip to content

Commit a21f05a

Browse files
authored
add upstream minja fix: ochafik/minja#7
1 parent af5216e commit a21f05a

File tree

2 files changed

+176
-2
lines changed

2 files changed

+176
-2
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
{# Unsloth & community template fixes #}
2+
{# ----------‑‑‑ special token variables ‑‑‑---------- #}
3+
{%- set toolcall_begin_token = '<minimax:tool_call>' -%}
4+
{%- set toolcall_end_token = '</minimax:tool_call>' -%}
5+
{#- Tool Rendering Functions ============================================== -#}
6+
{%- macro render_tool_namespace(namespace_name, tool_list) -%}
7+
{%- for tool in tool_list -%}
8+
<tool>{{ tool.function | tojson | string }}</tool>
9+
{% endfor -%}
10+
{%- endmacro -%}
11+
{%- macro visible_text(content) -%}
12+
{%- if content is string -%}
13+
{{ content }}
14+
{%- elif content is iterable and content is not mapping -%}
15+
{%- for item in content -%}
16+
{%- if item is mapping and item.type == 'text' -%}
17+
{{- item.text }}
18+
{%- elif item is string -%}
19+
{{- item }}
20+
{%- endif -%}
21+
{%- endfor -%}
22+
{%- else -%}
23+
{{- content }}
24+
{%- endif -%}
25+
{%- endmacro -%}
26+
{#- System Message Construction ============================================ -#}
27+
{%- macro build_system_message(system_message) -%}
28+
{%- if system_message and system_message.content -%}
29+
{{- visible_text(system_message.content) }}
30+
{%- else -%}
31+
{%- if model_identity is not defined -%}
32+
{%- set model_identity = "You are a helpful assistant." -%}
33+
{%- endif -%}
34+
{{- model_identity }}
35+
{%- endif -%}
36+
37+
{#- Handle current_date -#}
38+
{%- if system_message and system_message.current_date -%}
39+
{{- '\n' ~ 'Current date: ' + system_message.current_date }}
40+
{%- endif -%}
41+
{#- Handle current_location -#}
42+
{%- if system_message and system_message.current_location -%}
43+
{{- '\n' ~ 'Current location: ' + system_message.current_location }}
44+
{%- endif -%}
45+
{%- endmacro -%}
46+
{#- Main Template Logic ================================================= -#}
47+
{#- Extract system message (only first message if it's system) -#}
48+
{%- set system_message = none -%}
49+
{%- set conversation_messages = messages -%}
50+
{%- if messages and messages[0].role == "system" -%}
51+
{%- set system_message = messages[0] -%}
52+
{%- set conversation_messages = messages[1:] -%}
53+
{%- endif -%}
54+
{#- Get the last user message turn, for interleved thinking -#}
55+
{%- set ns = namespace(last_user_index=-1) %}
56+
{% for m in conversation_messages %}
57+
{%- if m.role == 'user' %}
58+
{% set ns.last_user_index = loop.index0 -%}
59+
{%- endif %}
60+
{%- endfor %}
61+
{#- Render system message -#}
62+
{{- ']~!b[' ~ ']~b]system' ~ '\n' }}
63+
{{- build_system_message(system_message) }}
64+
{#- Render tools if available -#}
65+
{%- if tools -%}
66+
{{- '\n\n' ~ '# Tools' ~ '\n' ~ 'You may call one or more tools to assist with the user query.\nHere are the tools available in JSONSchema format:' ~ '\n' }}
67+
{{- '\n' ~ '<tools>' ~ '\n' }}
68+
{{- render_tool_namespace("functions", tools) }}
69+
{{- '</tools>' ~ '\n\n' }}
70+
{{- 'When making tool calls, use XML format to invoke tools and pass parameters:' ~ '\n' }}
71+
{{- '\n' ~ toolcall_begin_token }}
72+
<invoke name="tool-name-1">
73+
<parameter name="param-key-1">param-value-1</parameter>
74+
<parameter name="param-key-2">param-value-2</parameter>
75+
...
76+
</invoke>
77+
{{- '\n' ~ toolcall_end_token }}
78+
{%- endif -%}
79+
{{- '[e~[\n' }}
80+
81+
{#- Render messages -#}
82+
{%- set last_tool_call = namespace(name=none) -%}
83+
{%- for message in conversation_messages -%}
84+
{%- if message.role == 'assistant' -%}
85+
{#- Only render reasoning_content if no user message follows -#}
86+
{{- ']~b]ai' ~ '\n' }}
87+
88+
{%- set reasoning_content = '' %}
89+
{%- set content = visible_text(message.content) %}
90+
{%- if message.reasoning_content is string %}
91+
{%- set reasoning_content = message.reasoning_content %}
92+
{%- else %}
93+
{%- if '</think>' in content %}
94+
{# Unsloth template fixes - must change to for loop since llama.cpp will error out if not #}
95+
{%- set parts = content.split('</think>') %}
96+
{%- for part in parts %}
97+
{%- if loop.index0 == 0 -%}
98+
{%- set reasoning_content = part.strip('\n') %}
99+
{%- set reasoning_content = (reasoning_content.split('<think>')|last) %}
100+
{%- set reasoning_content = reasoning_content.strip('\n') -%}
101+
{%- else -%}
102+
{%- set content = part.strip('\n') %}
103+
{%- endif %}
104+
{%- endfor %}
105+
{%- endif %}
106+
{%- endif %}
107+
{%- if reasoning_content and loop.index0 > ns.last_user_index -%}
108+
{{- '<think>' ~ '\n' ~ reasoning_content ~ '\n' ~ '</think>' ~ '\n\n' }}
109+
{%- endif -%}
110+
{%- if content -%}
111+
{{- content }}
112+
{%- endif -%}
113+
{%- if message.tool_calls -%}
114+
{{- '\n' ~ toolcall_begin_token ~ '\n' }}
115+
116+
{%- for tool_call in message.tool_calls -%}
117+
{%- if tool_call.function %}
118+
{%- set tool_call = tool_call.function %}
119+
{%- endif %}
120+
{{- '<invoke name="' + tool_call.name + '">\n' }}
121+
{%- if tool_call.arguments is defined and tool_call.arguments is mapping -%}
122+
{% set _args = tool_call.arguments %}
123+
{%- for k, v in _args|items %}
124+
{{- '<parameter name="' + k + '">' }}
125+
{{- v | tojson | string if v is not string else v }}
126+
{{- '</parameter>' }}
127+
{% endfor %}{%- endif -%}
128+
{{- '</invoke>' ~ '\n' }}
129+
{%- endfor -%}
130+
131+
{{- toolcall_end_token}}
132+
{# Fix by ochafik - https://github.com/ochafik/minja/pull/7#issuecomment-3478459580 #}
133+
{%- set last_tool_call.name = message.tool_calls[-1].function.name -%}
134+
{%- else -%}
135+
{%- set last_tool_call.name = none -%}
136+
{%- endif -%}
137+
{{- '[e~[' ~ '\n' }}
138+
139+
{%- elif message.role == 'tool' -%}
140+
{%- if last_tool_call.name is none -%}
141+
{{- raise_exception("Message has tool role, but there was no previous assistant message with a tool call!") }}
142+
{%- endif -%}
143+
{%- if loop.first or (conversation_messages[loop.index0 - 1].role != 'tool') -%}
144+
{{- ']~b]tool' }}
145+
{%- endif -%}
146+
{%- if message.content is string -%}
147+
{{- '\n<response>' }}
148+
{{- message.content }}
149+
{{- '</response>' }}
150+
{%- else -%}
151+
{%- for tr in message.content -%}
152+
{{- '\n<response>' }}
153+
{{- tr.output if tr.output is defined else (tr.text if tr.type == 'text' and tr.text is defined else tr) }}
154+
{{- '\n</response>' }}
155+
{%- endfor -%}
156+
{%- endif -%}
157+
{%- if loop.last or (conversation_messages[loop.index0 + 1].role != 'tool') -%}
158+
{{- '[e~[\n' -}}
159+
{%- endif -%}
160+
161+
{%- elif message.role == 'user' -%}
162+
{{- ']~b]user' ~ '\n' }}
163+
{{- visible_text(message.content) }}
164+
{{- '[e~[' ~ '\n' }}
165+
{%- endif -%}
166+
{%- endfor -%}
167+
168+
{#- Generation prompt -#}
169+
{%- if add_generation_prompt -%}
170+
{{- ']~b]ai' ~ '\n' ~ '<think>' ~ '\n' }}
171+
{%- endif -%}
172+
{# Copyright 2025-present Unsloth. Apache 2.0 License. #}

vendor/minja/chat-template.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,14 @@ class chat_template {
198198
dummy_user_msg,
199199
make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj.dump())})),
200200
}), {}, false);
201-
auto tool_call_renders_str_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
201+
auto tool_call_renders_str_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":")
202+
|| contains(out, "'argument_needle':") || contains(out, "<parameter name=\"argument_needle\">");
202203
out = try_raw_render(json::array({
203204
dummy_user_msg,
204205
make_tool_calls_msg(json::array({make_tool_call("ipython", dummy_args_obj)})),
205206
}), {}, false);
206-
auto tool_call_renders_obj_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":") || contains(out, "'argument_needle':");
207+
auto tool_call_renders_obj_arguments = contains(out, "<parameter=argument_needle>") || contains(out, "\"argument_needle\":")
208+
|| contains(out, "'argument_needle':") || contains(out, "<parameter name=\"argument_needle\">");
207209

208210
caps_.supports_tool_calls = tool_call_renders_str_arguments || tool_call_renders_obj_arguments;
209211
caps_.requires_object_arguments = !tool_call_renders_str_arguments && tool_call_renders_obj_arguments;

0 commit comments

Comments
 (0)