nlpguy commited on
Commit
174943c
·
verified ·
1 Parent(s): 45fd4ec

Update chat_template.jinja

Browse files
Files changed (1) hide show
  1. chat_template.jinja +490 -82
chat_template.jinja CHANGED
@@ -1,3 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {%- set image_count = namespace(value=0) %}
2
  {%- set video_count = namespace(value=0) %}
3
  {%- macro render_content(content, do_vision_count, is_system_content=false) %}
@@ -5,9 +164,41 @@
5
  {{- content }}
6
  {%- elif content is iterable and content is not mapping %}
7
  {%- for item in content %}
8
- {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  {%- if is_system_content %}
10
- {{- raise_exception('System message cannot contain images.') }}
 
 
11
  {%- endif %}
12
  {%- if do_vision_count %}
13
  {%- set image_count.value = image_count.value + 1 %}
@@ -16,9 +207,11 @@
16
  {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
  {%- endif %}
18
  {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
- {%- elif 'video' in item or item.type == 'video' %}
20
  {%- if is_system_content %}
21
- {{- raise_exception('System message cannot contain videos.') }}
 
 
22
  {%- endif %}
23
  {%- if do_vision_count %}
24
  {%- set video_count.value = video_count.value + 1 %}
@@ -27,10 +220,17 @@
27
  {{- 'Video ' ~ video_count.value ~ ': ' }}
28
  {%- endif %}
29
  {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
- {%- elif 'text' in item %}
 
 
 
 
 
31
  {{- item.text }}
32
  {%- else %}
33
- {{- raise_exception('Unexpected item type in content.') }}
 
 
34
  {%- endif %}
35
  {%- endfor %}
36
  {%- elif content is none or content is undefined %}
@@ -39,58 +239,96 @@
39
  {{- raise_exception('Unexpected content type.') }}
40
  {%- endif %}
41
  {%- endmacro %}
42
- {%- if not messages %}
43
- {{- raise_exception('No messages provided.') }}
44
- {%- endif %}
45
- {%- set reasoning_instructions = '' %}
46
- {%- if enable_thinking is undefined or enable_thinking is true %}
47
- {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
48
- {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
49
- {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}
 
 
 
 
 
 
 
 
 
50
  {%- endif %}
51
- {%- if resolved_reasoning_effort == 'xhigh' %}
52
- {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}
53
- {%- elif resolved_reasoning_effort == 'low' %}
54
- {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}
 
 
 
 
 
 
 
 
 
 
55
  {%- endif %}
56
- {%- endif %}
57
- {%- if tools and tools is iterable and tools is not mapping %}
 
 
 
 
 
58
  {{- '<|im_start|>system\n' }}
59
  {%- if reasoning_instructions %}
60
- {{- reasoning_instructions + '\n\n' }}
61
  {%- endif %}
62
- {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
63
  {%- for tool in tools %}
64
- {{- "\n" }}
65
  {{- tool | tojson }}
66
  {%- endfor %}
67
- {{- "\n</tools>" }}
68
- {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
69
- {%- if messages[0].role == 'system' %}
70
- {%- set content = render_content(messages[0].content, false, true)|trim %}
71
- {%- if content %}
72
- {{- '\n\n' + content }}
73
- {%- endif %}
 
74
  {%- endif %}
75
  {{- '<|im_end|>\n' }}
76
  {%- else %}
77
- {%- if messages[0].role == 'system' %}
78
- {%- set content = render_content(messages[0].content, false, true)|trim %}
79
- {%- if content %}
80
- {{- '<|im_start|>system\n' + (reasoning_instructions + '\n\n' if reasoning_instructions else '') + content + '<|im_end|>\n' }}
81
- {%- elif reasoning_instructions %}
82
- {{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }}
83
- {%- endif %}
84
  {%- elif reasoning_instructions %}
85
- {{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }}
 
 
 
86
  {%- endif %}
87
  {%- endif %}
88
- {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
89
- {%- for message in messages[::-1] %}
90
- {%- set index = (messages|length - 1) - loop.index0 %}
91
- {%- if ns.multi_step_tool and message.role == "user" %}
92
- {%- set content = render_content(message.content, false)|trim %}
93
- {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
 
 
 
 
 
 
 
 
 
 
 
94
  {%- set ns.multi_step_tool = false %}
95
  {%- set ns.last_query_index = index %}
96
  {%- endif %}
@@ -99,70 +337,240 @@
99
  {%- if ns.multi_step_tool %}
100
  {{- raise_exception('No user query found in messages.') }}
101
  {%- endif %}
102
- {%- for message in messages %}
103
- {%- set content = render_content(message.content, true)|trim %}
104
- {%- if message.role == "system" %}
105
- {%- if not loop.first %}
106
- {{- raise_exception('System message must be at the beginning.') }}
107
- {%- endif %}
108
- {%- elif message.role == "user" %}
109
- {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
110
- {%- elif message.role == "assistant" %}
 
 
 
 
 
111
  {%- set reasoning_content = '' %}
112
- {%- if message.reasoning_content is string %}
113
- {%- set reasoning_content = message.reasoning_content %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  {%- endif %}
115
- {%- set reasoning_content = reasoning_content|trim %}
116
- {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %}
117
- {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
 
 
 
 
 
 
 
118
  {%- else %}
119
- {{- '<|im_start|>' + message.role + '\n' + content }}
120
  {%- endif %}
121
- {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
 
 
 
 
 
 
 
 
 
122
  {%- for tool_call in message.tool_calls %}
123
- {%- if tool_call.function is defined %}
124
- {%- set tool_call = tool_call.function %}
 
 
 
125
  {%- endif %}
126
- {%- if loop.first %}
127
- {%- if content|trim %}
128
- {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  {%- else %}
130
- {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  {%- endif %}
 
 
 
 
 
132
  {%- else %}
133
- {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
134
- {%- endif %}
135
- {%- if tool_call.arguments is defined and tool_call.arguments != '' %}
136
- {%- for args_name, args_value in tool_call.arguments|items %}
137
- {{- '<parameter=' + args_name + '>\n' }}
138
- {%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %}
139
- {{- args_value }}
140
- {{- '\n</parameter>\n' }}
141
- {%- endfor %}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  {%- endif %}
143
- {{- '</function>\n</tool_call>' }}
144
  {%- endfor %}
145
  {%- endif %}
146
  {{- '<|im_end|>\n' }}
147
- {%- elif message.role == "tool" %}
148
- {%- if loop.previtem and loop.previtem.role != "tool" %}
149
  {{- '<|im_start|>user' }}
150
  {%- endif %}
 
 
 
 
 
 
 
 
 
151
  {{- '\n<tool_response>\n' }}
152
  {{- content }}
153
  {{- '\n</tool_response>' }}
154
- {%- if not loop.last and loop.nextitem.role != "tool" %}
155
  {{- '<|im_end|>\n' }}
156
- {%- elif loop.last %}
157
  {{- '<|im_end|>\n' }}
158
  {%- endif %}
 
 
 
 
 
159
  {%- else %}
160
- {{- raise_exception('Unexpected message role.') }}
 
 
161
  {%- endif %}
 
162
  {%- endfor %}
 
 
 
163
  {%- if add_generation_prompt %}
164
  {{- '<|im_start|>assistant\n' }}
165
- {%- if enable_thinking is defined and enable_thinking is false %}
166
  {{- '<think>\n\n</think>\n\n' }}
167
  {%- else %}
168
  {{- '<think>\n' }}
 
1
+ {%- set template_version = "qwen3.8-safe-v2" %}
2
+ {#- -------------------------------------------------------------------------
3
+ Whitespace contract: every tag opener in this file carries a leading minus,
4
+ so it strips the whitespace and newlines that precede it. All emitted text
5
+ comes from string literals, which makes the rendered prompt byte-exact and
6
+ free of incidental newlines. Comment tags need the minus just as much as
7
+ block tags do; a plain comment tag breaks the strip chain and leaks the
8
+ surrounding indentation into the prompt.
9
+ ------------------------------------------------------------------------- -#}
10
+ {#- The message list has to exist before anything else can inspect it. -#}
11
+ {%- if not messages %}
12
+ {{- raise_exception('No messages provided.') }}
13
+ {%- endif %}
14
+ {#- -------------------------------------------------------------------------
15
+ Configuration
16
+ ------------------------------------------------------------------------- -#}
17
+ {%- set _requested_tool_format =
18
+ tool_call_format if tool_call_format is defined else 'auto'
19
+ %}
20
+ {%- if _requested_tool_format not in ('auto', 'xml', 'json') %}
21
+ {{- raise_exception(
22
+ 'Unexpected tool_call_format ' ~ _requested_tool_format ~
23
+ '. Supported values are auto (default), xml, and json.'
24
+ ) }}
25
+ {%- endif %}
26
+ {%- set add_vision_id = add_vision_id if add_vision_id is defined else false %}
27
+ {%- set enable_thinking = enable_thinking if enable_thinking is defined else true %}
28
+ {%- set auto_disable_thinking_with_tools =
29
+ auto_disable_thinking_with_tools
30
+ if auto_disable_thinking_with_tools is defined
31
+ else false
32
+ %}
33
+ {%- if preserve_reasoning is defined
34
+ and preserve_reasoning is not none
35
+ and preserve_thinking is defined
36
+ and preserve_thinking is not none
37
+ and preserve_reasoning != preserve_thinking %}
38
+ {{- raise_exception(
39
+ 'preserve_reasoning and preserve_thinking were both provided with conflicting values.'
40
+ ) }}
41
+ {%- endif %}
42
+ {%- if preserve_reasoning is defined and preserve_reasoning is not none %}
43
+ {%- set _preserve_thinking = preserve_reasoning %}
44
+ {%- elif preserve_thinking is defined and preserve_thinking is not none %}
45
+ {%- set _preserve_thinking = preserve_thinking %}
46
+ {%- else %}
47
+ {%- set _preserve_thinking = true %}
48
+ {%- endif %}
49
+ {%- set max_tool_arg_chars =
50
+ max_tool_arg_chars if max_tool_arg_chars is defined else 0
51
+ %}
52
+ {%- set max_tool_response_chars =
53
+ max_tool_response_chars if max_tool_response_chars is defined else 0
54
+ %}
55
+ {%- if max_tool_arg_chars is not number or max_tool_arg_chars < 0 %}
56
+ {{- raise_exception('max_tool_arg_chars must be a non-negative number.') }}
57
+ {%- endif %}
58
+ {%- if max_tool_response_chars is not number or max_tool_response_chars < 0 %}
59
+ {{- raise_exception('max_tool_response_chars must be a non-negative number.') }}
60
+ {%- endif %}
61
+ {%- if tools is defined and tools is not none and tools %}
62
+ {%- if tools is string or tools is mapping or tools is not iterable %}
63
+ {{- raise_exception('tools must be an iterable list of tool definitions.') }}
64
+ {%- endif %}
65
+ {%- set _has_tools = true %}
66
+ {%- else %}
67
+ {%- set _has_tools = false %}
68
+ {%- endif %}
69
+ {#- -------------------------------------------------------------------------
70
+ Resolve the tool-call wire format.
71
+
72
+ OpenAI-compatible servers hand assistant tool calls back with
73
+ `function.arguments` as a JSON *string*, not a mapping. The XML parameter
74
+ form needs the individual key/value pairs, and a chat template cannot
75
+ decode a JSON string: the standard chat-template environment provides
76
+ `tojson` but no inverse, and no codepoint-to-character primitive, so a
77
+ hand-rolled scanner could not decode \uXXXX escapes correctly. Guessing
78
+ would silently corrupt tool arguments, which is worse than any error.
79
+
80
+ So when the caller does not pin a format, render the whole prompt in the
81
+ JSON tool-call form if any tool call in the history carries string
82
+ arguments. That is lossless, needs no decoding, and keeps the system-prompt
83
+ instructions and the rendered history in one consistent format. Histories
84
+ whose arguments are all mappings still render as XML, byte-identically to
85
+ tool_call_format='xml'.
86
+
87
+ A caller that explicitly pins 'xml' still gets a hard error for string
88
+ arguments, because that request cannot be honoured faithfully.
89
+ ------------------------------------------------------------------------- -#}
90
+ {%- set format_scan = namespace(has_json_string_arguments=false) %}
91
+ {%- for message in messages %}
92
+ {%- if message.role == 'assistant'
93
+ and message.tool_calls is defined
94
+ and message.tool_calls is not none
95
+ and message.tool_calls
96
+ and message.tool_calls is not string
97
+ and message.tool_calls is not mapping
98
+ and message.tool_calls is iterable %}
99
+ {%- for tool_call in message.tool_calls %}
100
+ {%- if tool_call.function is defined
101
+ and tool_call.function is not none %}
102
+ {%- set _tc = tool_call.function %}
103
+ {%- else %}
104
+ {%- set _tc = tool_call %}
105
+ {%- endif %}
106
+ {%- if _tc.arguments is defined
107
+ and _tc.arguments is not none
108
+ and _tc.arguments is string
109
+ and _tc.arguments | trim
110
+ and _tc.arguments | trim != '{}' %}
111
+ {%- set format_scan.has_json_string_arguments = true %}
112
+ {%- endif %}
113
+ {%- endfor %}
114
+ {%- endif %}
115
+ {%- endfor %}
116
+ {%- if _requested_tool_format == 'auto' %}
117
+ {%- if format_scan.has_json_string_arguments %}
118
+ {%- set _tool_format = 'json' %}
119
+ {%- else %}
120
+ {%- set _tool_format = 'xml' %}
121
+ {%- endif %}
122
+ {%- else %}
123
+ {%- set _tool_format = _requested_tool_format %}
124
+ {%- endif %}
125
+ {%- set _thinking_enabled = enable_thinking %}
126
+ {%- if auto_disable_thinking_with_tools and _has_tools %}
127
+ {%- set _thinking_enabled = false %}
128
+ {%- endif %}
129
+ {#- Validate reasoning effort even when thinking is disabled. -#}
130
+ {%- set _effort_raw =
131
+ reasoning_effort if reasoning_effort is defined else 'xhigh'
132
+ %}
133
+ {%- if _effort_raw == 'high' or _effort_raw == 'xhigh' %}
134
+ {%- set _reasoning_effort = 'xhigh' %}
135
+ {%- elif _effort_raw == 'medium' %}
136
+ {%- set _reasoning_effort = 'medium' %}
137
+ {%- elif _effort_raw == 'low' %}
138
+ {%- set _reasoning_effort = 'low' %}
139
+ {%- else %}
140
+ {{- raise_exception(
141
+ 'Unexpected reasoning effort ' ~ _effort_raw ~
142
+ '. Supported values are high/xhigh, medium, and low.'
143
+ ) }}
144
+ {%- endif %}
145
+ {%- set reasoning_instructions = '' %}
146
+ {%- if _thinking_enabled %}
147
+ {%- if _reasoning_effort == 'xhigh' %}
148
+ {%- set reasoning_instructions =
149
+ 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.'
150
+ %}
151
+ {%- elif _reasoning_effort == 'low' %}
152
+ {%- set reasoning_instructions =
153
+ 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.'
154
+ %}
155
+ {%- endif %}
156
+ {%- endif %}
157
+ {#- -------------------------------------------------------------------------
158
+ Content rendering
159
+ ------------------------------------------------------------------------- -#}
160
  {%- set image_count = namespace(value=0) %}
161
  {%- set video_count = namespace(value=0) %}
162
  {%- macro render_content(content, do_vision_count, is_system_content=false) %}
 
164
  {{- content }}
165
  {%- elif content is iterable and content is not mapping %}
166
  {%- for item in content %}
167
+ {%- if item is not mapping %}
168
+ {{- raise_exception(
169
+ 'Content-list items must be mappings with a supported text, image, or video type.'
170
+ ) }}
171
+ {%- endif %}
172
+ {%- set _item_type = item.type if 'type' in item else none %}
173
+ {%- if _item_type is not none
174
+ and _item_type not in ('text', 'image', 'image_url', 'video') %}
175
+ {{- raise_exception(
176
+ 'Unexpected content item type ' ~ _item_type ~ '.'
177
+ ) }}
178
+ {%- endif %}
179
+ {%- set _is_image =
180
+ _item_type == 'image'
181
+ or _item_type == 'image_url'
182
+ or 'image' in item
183
+ or 'image_url' in item
184
+ %}
185
+ {%- set _is_video =
186
+ _item_type == 'video'
187
+ or 'video' in item
188
+ %}
189
+ {%- if _is_image and _is_video %}
190
+ {{- raise_exception(
191
+ 'A content item cannot contain both image and video data.'
192
+ ) }}
193
+ {%- elif _item_type == 'text' and (_is_image or _is_video) %}
194
+ {{- raise_exception(
195
+ 'A text content item cannot also contain image or video data.'
196
+ ) }}
197
+ {%- elif _is_image %}
198
  {%- if is_system_content %}
199
+ {{- raise_exception(
200
+ 'System and developer messages cannot contain images.'
201
+ ) }}
202
  {%- endif %}
203
  {%- if do_vision_count %}
204
  {%- set image_count.value = image_count.value + 1 %}
 
207
  {{- 'Picture ' ~ image_count.value ~ ': ' }}
208
  {%- endif %}
209
  {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
210
+ {%- elif _is_video %}
211
  {%- if is_system_content %}
212
+ {{- raise_exception(
213
+ 'System and developer messages cannot contain videos.'
214
+ ) }}
215
  {%- endif %}
216
  {%- if do_vision_count %}
217
  {%- set video_count.value = video_count.value + 1 %}
 
220
  {{- 'Video ' ~ video_count.value ~ ': ' }}
221
  {%- endif %}
222
  {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
223
+ {%- elif _item_type == 'text' or 'text' in item %}
224
+ {%- if 'text' not in item or item.text is not string %}
225
+ {{- raise_exception(
226
+ 'Text content items must contain a string text field.'
227
+ ) }}
228
+ {%- endif %}
229
  {{- item.text }}
230
  {%- else %}
231
+ {{- raise_exception(
232
+ 'Unexpected item in content list. Expected text, image, or video content.'
233
+ ) }}
234
  {%- endif %}
235
  {%- endfor %}
236
  {%- elif content is none or content is undefined %}
 
239
  {{- raise_exception('Unexpected content type.') }}
240
  {%- endif %}
241
  {%- endmacro %}
242
+ {#- -------------------------------------------------------------------------
243
+ Validate and merge leading system/developer messages
244
+ ------------------------------------------------------------------------- -#}
245
+ {%- set head = namespace(count=0, seen_non_system=false) %}
246
+ {%- for message in messages %}
247
+ {%- set _is_system =
248
+ message.role == 'system' or message.role == 'developer'
249
+ %}
250
+ {%- if _is_system %}
251
+ {%- if head.seen_non_system %}
252
+ {{- raise_exception(
253
+ 'System and developer messages must appear before all user, assistant, and tool messages.'
254
+ ) }}
255
+ {%- endif %}
256
+ {%- set head.count = head.count + 1 %}
257
+ {%- else %}
258
+ {%- set head.seen_non_system = true %}
259
  {%- endif %}
260
+ {%- endfor %}
261
+ {%- set system_state = namespace(content='') %}
262
+ {%- for message in messages[:head.count] %}
263
+ {%- set _part =
264
+ render_content(message.content, false, true) | trim
265
+ %}
266
+ {%- if _part %}
267
+ {%- if system_state.content %}
268
+ {%- set system_state.content =
269
+ system_state.content ~ '\n\n' ~ _part
270
+ %}
271
+ {%- else %}
272
+ {%- set system_state.content = _part %}
273
+ {%- endif %}
274
  {%- endif %}
275
+ {%- endfor %}
276
+ {%- set _system_content = system_state.content %}
277
+ {%- set _msgs = messages[head.count:] %}
278
+ {#- -------------------------------------------------------------------------
279
+ System prompt and tool instructions
280
+ ------------------------------------------------------------------------- -#}
281
+ {%- if _has_tools %}
282
  {{- '<|im_start|>system\n' }}
283
  {%- if reasoning_instructions %}
284
+ {{- reasoning_instructions ~ '\n\n' }}
285
  {%- endif %}
286
+ {{- '# Tools\n\nYou have access to the following functions:\n\n<tools>' }}
287
  {%- for tool in tools %}
288
+ {{- '\n' }}
289
  {{- tool | tojson }}
290
  {%- endfor %}
291
+ {{- '\n</tools>' }}
292
+ {%- if _tool_format == 'json' %}
293
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n{"name": "example_function_name", "arguments": {"example_parameter_1": "value_1", "example_parameter_2": "This is the value for the second parameter"}}\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: a single JSON object with "name" and "arguments" keys must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
294
+ {%- else %}
295
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
296
+ {%- endif %}
297
+ {%- if _system_content %}
298
+ {{- '\n\n' ~ _system_content }}
299
  {%- endif %}
300
  {{- '<|im_end|>\n' }}
301
  {%- else %}
302
+ {%- if _system_content %}
303
+ {{- '<|im_start|>system\n'
304
+ ~ (reasoning_instructions ~ '\n\n' if reasoning_instructions else '')
305
+ ~ _system_content
306
+ ~ '<|im_end|>\n'
307
+ }}
 
308
  {%- elif reasoning_instructions %}
309
+ {{- '<|im_start|>system\n'
310
+ ~ reasoning_instructions
311
+ ~ '<|im_end|>\n'
312
+ }}
313
  {%- endif %}
314
  {%- endif %}
315
+ {#- -------------------------------------------------------------------------
316
+ Find the last real user query
317
+ ------------------------------------------------------------------------- -#}
318
+ {%- set ns = namespace(
319
+ multi_step_tool=true,
320
+ last_query_index=(_msgs | length) - 1
321
+ ) %}
322
+ {%- for message in _msgs[::-1] %}
323
+ {%- set index = (_msgs | length - 1) - loop.index0 %}
324
+ {%- if ns.multi_step_tool and message.role == 'user' %}
325
+ {%- set _rendered_user =
326
+ render_content(message.content, false) | trim
327
+ %}
328
+ {%- if not (
329
+ _rendered_user.startswith('<tool_response>')
330
+ and _rendered_user.endswith('</tool_response>')
331
+ ) %}
332
  {%- set ns.multi_step_tool = false %}
333
  {%- set ns.last_query_index = index %}
334
  {%- endif %}
 
337
  {%- if ns.multi_step_tool %}
338
  {{- raise_exception('No user query found in messages.') }}
339
  {%- endif %}
340
+ {#- -------------------------------------------------------------------------
341
+ Conversation rendering
342
+ ------------------------------------------------------------------------- -#}
343
+ {%- set history_state = namespace(previous_role='') %}
344
+ {%- for message in _msgs %}
345
+ {%- set content =
346
+ render_content(message.content, true) | trim
347
+ %}
348
+ {%- if message.role == 'user' %}
349
+ {{- '<|im_start|>user\n'
350
+ ~ content
351
+ ~ '<|im_end|>\n'
352
+ }}
353
+ {%- elif message.role == 'assistant' %}
354
  {%- set reasoning_content = '' %}
355
+ {%- if message.reasoning_content is defined
356
+ and message.reasoning_content is not none %}
357
+ {%- if message.reasoning_content is string %}
358
+ {%- set reasoning_content = message.reasoning_content %}
359
+ {%- else %}
360
+ {%- set reasoning_content =
361
+ message.reasoning_content | string
362
+ %}
363
+ {%- endif %}
364
+ {%- elif message.thinking is defined
365
+ and message.thinking is not none %}
366
+ {%- if message.thinking is string %}
367
+ {%- set reasoning_content = message.thinking %}
368
+ {%- else %}
369
+ {%- set reasoning_content =
370
+ message.thinking | string
371
+ %}
372
+ {%- endif %}
373
  {%- endif %}
374
+ {%- set reasoning_content = reasoning_content | trim %}
375
+ {#- Preserve the original invariant: when thinking is preserved,
376
+ always reconstruct the thinking block, even when it is empty. -#}
377
+ {%- if _preserve_thinking
378
+ or loop.index0 > ns.last_query_index %}
379
+ {{- '<|im_start|>assistant\n<think>\n'
380
+ ~ reasoning_content
381
+ ~ '\n</think>\n\n'
382
+ ~ content
383
+ }}
384
  {%- else %}
385
+ {{- '<|im_start|>assistant\n' ~ content }}
386
  {%- endif %}
387
+ {%- if message.tool_calls is defined
388
+ and message.tool_calls is not none
389
+ and message.tool_calls %}
390
+ {%- if message.tool_calls is string
391
+ or message.tool_calls is mapping
392
+ or message.tool_calls is not iterable %}
393
+ {{- raise_exception(
394
+ 'assistant.tool_calls must be an iterable list of tool calls.'
395
+ ) }}
396
+ {%- endif %}
397
  {%- for tool_call in message.tool_calls %}
398
+ {%- if tool_call.function is defined
399
+ and tool_call.function is not none %}
400
+ {%- set tc = tool_call.function %}
401
+ {%- else %}
402
+ {%- set tc = tool_call %}
403
  {%- endif %}
404
+ {%- if tc.name is not defined
405
+ or tc.name is none
406
+ or tc.name is not string
407
+ or not tc.name %}
408
+ {{- raise_exception(
409
+ 'Every tool call must have a non-empty string name.'
410
+ ) }}
411
+ {%- endif %}
412
+ {%- set tc_name = tc.name %}
413
+ {%- if _tool_format == 'json' %}
414
+ {%- if loop.first %}
415
+ {%- if content | trim %}
416
+ {{- '\n\n<tool_call>\n' }}
417
+ {%- else %}
418
+ {{- '<tool_call>\n' }}
419
+ {%- endif %}
420
  {%- else %}
421
+ {{- '\n<tool_call>\n' }}
422
+ {%- endif %}
423
+ {%- set _json_arguments = '{}' %}
424
+ {%- if tc.arguments is defined
425
+ and tc.arguments is not none %}
426
+ {%- if tc.arguments is mapping %}
427
+ {%- set _json_arguments =
428
+ tc.arguments | tojson
429
+ %}
430
+ {%- elif tc.arguments is string %}
431
+ {%- set _raw_arguments =
432
+ tc.arguments | trim
433
+ %}
434
+ {%- if _raw_arguments %}
435
+ {%- if not (
436
+ _raw_arguments.startswith('{')
437
+ and _raw_arguments.endswith('}')
438
+ ) %}
439
+ {{- raise_exception(
440
+ 'JSON tool-call arguments must be a JSON object string.'
441
+ ) }}
442
+ {%- endif %}
443
+ {%- set _json_arguments =
444
+ _raw_arguments
445
+ %}
446
+ {%- endif %}
447
+ {%- else %}
448
+ {{- raise_exception(
449
+ 'JSON tool-call arguments must be a mapping or a JSON object string.'
450
+ ) }}
451
+ {%- endif %}
452
  {%- endif %}
453
+ {{- '{"name": ' }}
454
+ {{- tc_name | tojson }}
455
+ {{- ', "arguments": ' }}
456
+ {{- _json_arguments }}
457
+ {{- '}\n</tool_call>' }}
458
  {%- else %}
459
+ {%- if loop.first %}
460
+ {%- if content | trim %}
461
+ {{- '\n\n<tool_call>\n<function='
462
+ ~ tc_name
463
+ ~ '>\n'
464
+ }}
465
+ {%- else %}
466
+ {{- '<tool_call>\n<function='
467
+ ~ tc_name
468
+ ~ '>\n'
469
+ }}
470
+ {%- endif %}
471
+ {%- else %}
472
+ {{- '\n<tool_call>\n<function='
473
+ ~ tc_name
474
+ ~ '>\n'
475
+ }}
476
+ {%- endif %}
477
+ {%- if tc.arguments is defined
478
+ and tc.arguments is not none %}
479
+ {%- if tc.arguments is mapping %}
480
+ {%- for args_name, args_value
481
+ in tc.arguments | items %}
482
+ {%- set _argument_name =
483
+ args_name | string
484
+ %}
485
+ {{- '<parameter='
486
+ ~ _argument_name
487
+ ~ '>\n'
488
+ }}
489
+ {#- Preserve strings verbatim. Serialize every
490
+ other type as JSON so booleans and null stay
491
+ true/false/null instead of True/False/None. -#}
492
+ {%- if args_value is string %}
493
+ {%- set _argument_value =
494
+ args_value
495
+ %}
496
+ {%- else %}
497
+ {%- set _argument_value =
498
+ args_value | tojson
499
+ %}
500
+ {%- endif %}
501
+ {%- if max_tool_arg_chars > 0
502
+ and _argument_value | length
503
+ > max_tool_arg_chars %}
504
+ {{- _argument_value[:max_tool_arg_chars] }}
505
+ {{- '\n[TRUNCATED — original length '
506
+ ~ (_argument_value | length | string)
507
+ ~ ' chars]'
508
+ }}
509
+ {%- else %}
510
+ {{- _argument_value }}
511
+ {%- endif %}
512
+ {{- '\n</parameter>\n' }}
513
+ {%- endfor %}
514
+ {%- elif tc.arguments is string %}
515
+ {%- set _raw_arguments =
516
+ tc.arguments | trim
517
+ %}
518
+ {%- if _raw_arguments
519
+ and _raw_arguments != '{}' %}
520
+ {{- raise_exception(
521
+ 'tool_call_format="xml" requires tool-call arguments to be mappings, but received a JSON string. Parse the JSON string into a mapping before applying the template, or leave tool_call_format unset so the template can select the JSON tool-call format automatically.'
522
+ ) }}
523
+ {%- endif %}
524
+ {%- else %}
525
+ {{- raise_exception(
526
+ 'XML tool-call arguments must be a mapping. Parse JSON-string arguments before applying the template.'
527
+ ) }}
528
+ {%- endif %}
529
+ {%- endif %}
530
+ {{- '</function>\n</tool_call>' }}
531
  {%- endif %}
 
532
  {%- endfor %}
533
  {%- endif %}
534
  {{- '<|im_end|>\n' }}
535
+ {%- elif message.role == 'tool' %}
536
+ {%- if history_state.previous_role != 'tool' %}
537
  {{- '<|im_start|>user' }}
538
  {%- endif %}
539
+ {%- if max_tool_response_chars > 0
540
+ and content | length > max_tool_response_chars %}
541
+ {%- set content =
542
+ content[:max_tool_response_chars]
543
+ ~ '\n[TRUNCATED — original length '
544
+ ~ (content | length | string)
545
+ ~ ' chars]'
546
+ %}
547
+ {%- endif %}
548
  {{- '\n<tool_response>\n' }}
549
  {{- content }}
550
  {{- '\n</tool_response>' }}
551
+ {%- if loop.last %}
552
  {{- '<|im_end|>\n' }}
553
+ {%- elif _msgs[loop.index0 + 1].role != 'tool' %}
554
  {{- '<|im_end|>\n' }}
555
  {%- endif %}
556
+ {%- elif message.role == 'system'
557
+ or message.role == 'developer' %}
558
+ {{- raise_exception(
559
+ 'System and developer messages must appear only at the beginning of the conversation.'
560
+ ) }}
561
  {%- else %}
562
+ {{- raise_exception(
563
+ 'Unexpected message role ' ~ message.role ~ '.'
564
+ ) }}
565
  {%- endif %}
566
+ {%- set history_state.previous_role = message.role %}
567
  {%- endfor %}
568
+ {#- -------------------------------------------------------------------------
569
+ Generation prompt
570
+ ------------------------------------------------------------------------- -#}
571
  {%- if add_generation_prompt %}
572
  {{- '<|im_start|>assistant\n' }}
573
+ {%- if not _thinking_enabled %}
574
  {{- '<think>\n\n</think>\n\n' }}
575
  {%- else %}
576
  {{- '<think>\n' }}