openhands commited on
Commit
58d8279
·
1 Parent(s): 637d9b5

Update README.md, add v1.1.5 template variants

Browse files
README.md CHANGED
@@ -15,7 +15,8 @@ tags:
15
  - template-fix
16
  - bugfix
17
  ---
18
- ### 23.05: v1.1.2 has known tool calling degradation, im working on it. Stay with v1.1 for now
 
19
 
20
  TL:DR Download your fixed and finally working Qwen3.5 and Qwen3.6 jinja chat-template here. V1.1.2 with new tool call error bug fixes and additional edge case enhancements is now released.
21
 
@@ -25,6 +26,9 @@ Compatible with Qwen3, Qwen3.5 and Qwen3.6 in any OpenWeight size yet released!
25
  #### Update: V1.1.2 is out in the wild now!
26
  Additional fixes to tool calling have been done, verified correct developer role handling and other improvements. I would consider this a first stable version.
27
 
 
 
 
28
 
29
  # Welcome to the Template Rebuild Project
30
 
 
15
  - template-fix
16
  - bugfix
17
  ---
18
+ ### 23.05 22:00: v1.1.5 is released. Agentic and Tool use works 100%. Try it out!
19
+ ### If you use mainly agentic coding you can also test the "adiditional-systemprompt" variant out. It adds minimalistic Systempromt Enhancements to 1.1.5 that should work in every environment and actively enhancing correct coding creation and behaviour.
20
 
21
  TL:DR Download your fixed and finally working Qwen3.5 and Qwen3.6 jinja chat-template here. V1.1.2 with new tool call error bug fixes and additional edge case enhancements is now released.
22
 
 
26
  #### Update: V1.1.2 is out in the wild now!
27
  Additional fixes to tool calling have been done, verified correct developer role handling and other improvements. I would consider this a first stable version.
28
 
29
+ #### OpenCode:
30
+ If using OpenCode do notice they have an open bug for showing thinking content as plain text. You can look it up on Github. Its open since 4 months. Meanwhile you can use the llama.cpp Server switch to hide thinking completely(but still let the model think) using the additonal parameter:
31
+ --reasoning-format deepseek
32
 
33
  # Welcome to the Template Rebuild Project
34
 
qwen3-5_6-template_v1.1.jinja → qwen3_5-6-template_v1.1.5.jinja RENAMED
@@ -1,11 +1,41 @@
1
- {#- ===== SECTION 1: MACRO render_content =====
2
- Handles string, list (image/video/text items), or None/undefined.
3
- count_vision=true: increments ns.image_count / ns.video_count.
4
- -#}
5
- {%- macro render_content(content, count_vision=false) -%}
6
- {%- if content is string -%}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  {{- content -}}
8
- {%- elif content is iterable and content is not mapping -%}
9
  {%- for item in content -%}
10
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
11
  {%- if count_vision -%}{%- set ns.image_count = ns.image_count + 1 -%}{%- endif -%}
@@ -13,6 +43,11 @@
13
  {{- 'Picture ' ~ ns.image_count ~ ': ' -}}
14
  {%- endif -%}
15
  {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
 
 
 
 
 
16
  {%- elif item.type == 'video' or 'video' in item -%}
17
  {%- if count_vision -%}{%- set ns.video_count = ns.video_count + 1 -%}{%- endif -%}
18
  {%- if add_vision_id is defined and add_vision_id -%}
@@ -21,25 +56,46 @@
21
  {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
22
  {%- elif item.type == 'text' or 'text' in item -%}
23
  {{- item.text -}}
 
 
24
  {%- endif -%}
25
  {%- endfor -%}
 
 
26
  {%- endif -%}
27
  {%- endmacro -%}
28
 
29
- {#- ===== SECTION 2: NAMESPACE INITIALISATION =====
30
- Single ns object for all mutable state.
31
- enable_thinking default=true (BUG-003 fix)
32
- preserve_thinking default=true: when false, suppresses think-block output in
33
- generation prompt and overrides enable_thinking to false.
34
- Passed via --chat-template-kwargs {"preserve_thinking":false}.
35
- -#}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  {%- set ns = namespace(
37
  enable_thinking=true,
 
38
  image_count=0,
39
- video_count=0
 
 
40
  ) -%}
41
 
42
- {#- Resolve enable_thinking kwarg -#}
43
  {%- if enable_thinking is defined -%}
44
  {%- if enable_thinking -%}
45
  {%- set ns.enable_thinking = true -%}
@@ -48,21 +104,16 @@
48
  {%- endif -%}
49
  {%- endif -%}
50
 
51
- {#- Resolve preserve_thinking kwarg.
52
- preserve_thinking=false => force non-thinking mode (same as enable_thinking=false).
53
- preserve_thinking=true => default, no override (thinking controlled by enable_thinking).
54
- When not defined => default, no override.
55
- -#}
56
- {%- if preserve_thinking is defined and not preserve_thinking -%}
57
- {%- set ns.enable_thinking = false -%}
58
  {%- endif -%}
59
 
60
- {#- ===== SECTION 3: PRE-SCAN =====
61
- Track last /no_think or /think flag in user messages.
62
- Also scan system messages for <|think_off|> / <|think_on|> markers
63
- (allows apps to control thinking mode via system prompt injection).
64
- The model follows the last flag encountered in multi-turn conversations.
65
- -#}
66
  {%- for i in range(messages | length) -%}
67
  {%- set _msg = messages[i] -%}
68
  {%- if _msg.role == 'user' -%}
@@ -82,14 +133,16 @@
82
  {%- endif -%}
83
  {%- endfor -%}
84
 
85
- {#- ===== SECTION 4: COLLECT SYSTEM CONTENT =====
86
- Merge all system/developer messages with \n\n separator (BUG-004 fix).
87
- <|think_off|> / <|think_on|> markers are stripped from output.
88
- -#}
 
 
89
  {%- set ns_sys = namespace(content='') -%}
90
  {%- for msg in messages -%}
91
  {%- if msg.role == 'system' or msg.role == 'developer' -%}
92
- {%- set _c = render_content(msg.content | default('')) | trim -%}
93
  {%- set _c = _c | replace('<|think_off|>', '') | replace('<|think_on|>', '') | trim -%}
94
  {%- if _c -%}
95
  {%- if ns_sys.content == '' -%}
@@ -101,10 +154,7 @@
101
  {%- endif -%}
102
  {%- endfor -%}
103
 
104
- {#- ===== SECTION 5: BUILD TOOLS LIST =====
105
- Normalise each tool to {"type":"function","function":{...}} format.
106
- Serialisation happens later at output time (avoids Markup + str escaping bugs).
107
- -#}
108
  {%- set _has_tools = tools is defined and tools -%}
109
  {%- if _has_tools -%}
110
  {%- set ns_tb = namespace(list=[]) -%}
@@ -117,12 +167,7 @@
117
  {%- endfor -%}
118
  {%- endif -%}
119
 
120
- {#- ===== SECTION 6: OUTPUT SYSTEM TURN =====
121
- Each fragment output via its own {{ }} block so tojson Markup objects are
122
- never Python-concatenated with plain strings (would trigger HTML-escaping).
123
- User system content appears BEFORE the tools block (correct ordering).
124
- No default system prompt injected.
125
- -#}
126
  {%- if ns_sys.content or _has_tools -%}
127
  {{- '<|im_start|>system\n' -}}
128
  {%- if ns_sys.content -%}
@@ -135,97 +180,75 @@
135
  {{- tool | tojson -}}
136
  {%- if not loop.last -%}{{- '\n' -}}{%- endif -%}
137
  {%- endfor -%}
138
- {{- '\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>' -}}
 
139
  {%- endif -%}
140
  {{- '<|im_end|>\n' -}}
141
  {%- endif -%}
142
 
143
- {#- ===== SECTION 7: MAIN MESSAGE LOOP ===== -#}
144
  {%- for message in messages -%}
145
 
146
- {#- 7a: System / Developer — already rendered above, skip -#}
147
  {%- if message.role == 'system' or message.role == 'developer' -%}
148
 
149
- {#- 7b: User messages -#}
150
  {%- elif message.role == 'user' -%}
151
- {%- set _uc = render_content(message.content | default(''), true) -%}
152
  {{- '<|im_start|>user\n' + _uc + '<|im_end|>\n' -}}
153
 
154
- {#- 7c: Assistant messages -#}
155
  {%- elif message.role == 'assistant' -%}
156
- {#- Safely extract content as string — guard against absent key (BUG-002 fix).
157
- Also support message.reasoning_content as an explicit think-block source
158
- (used by some frameworks that store thinking separately from content). -#}
159
  {%- if message.content is defined and message.content is string -%}
160
  {%- set _ac = message.content -%}
161
- {%- elif message.content is defined and message.content is iterable and message.content is not mapping -%}
162
- {%- set _ac = render_content(message.content) -%}
163
  {%- else -%}
164
  {%- set _ac = '' -%}
165
  {%- endif -%}
166
 
167
- {#- Reconstruct content from reasoning_content + content when the framework
168
- stores thinking separately (e.g. OpenAI-style reasoning_content field).
169
- Only apply when no think-block already present in _ac. -#}
170
  {%- if message.reasoning_content is defined and message.reasoning_content is string
171
  and message.reasoning_content | trim
172
  and '<think>' not in _ac -%}
173
  {%- set _ac = '<think>\n' + message.reasoning_content | trim + '\n</think>\n\n' + _ac -%}
174
  {%- endif -%}
175
 
176
- {#- Collect tool_calls if present -#}
177
- {%- set _tc = message.tool_calls if message.tool_calls is defined and message.tool_calls else [] -%}
178
 
179
- {#- Strip <tool_call> prefix from content when tool_calls also present
180
- (some frameworks duplicate the data in both fields) -#}
181
  {%- if _tc and '<tool_call>' in _ac -%}
182
  {%- set _ac = _ac.split('<tool_call>')[0] | trim -%}
183
  {%- endif -%}
184
 
185
- {#- Determine if this is the last-in-history assistant turn.
186
- When add_generation_prompt=False and this is the last message, think blocks
187
- must be preserved (and non-thinking prefill applied if needed).
188
- All other turns have their think blocks stripped. -#}
189
- {%- set _is_last_hist = loop.last and not (add_generation_prompt | default(false)) -%}
 
 
 
190
 
191
- {#- Think-block handling (BUG-001 fix + last-turn preservation):
192
- - Tool-call turns : never strip (think block is part of the tool-call format)
193
- - Last-history turn : preserve; inject non-thinking prefill when absent
194
- - Historical turns : strip using fuzzy end-tag matching to handle
195
- </think>, </thinking>, </ think>, </think > variants -#}
196
- {%- if not _tc -%}
197
- {%- if _is_last_hist -%}
198
- {%- if '<think>' not in _ac and not ns.enable_thinking -%}
199
- {%- set _ac = '<think>\n\n</think>\n\n' + _ac -%}
200
- {%- endif -%}
201
- {%- else -%}
202
- {#- Fuzzy end-tag detection for historical turn stripping -#}
203
- {%- set _think_end = '' -%}
204
- {%- if '</think>' in _ac -%}
205
- {%- set _think_end = '</think>' -%}
206
- {%- elif '</thinking>' in _ac -%}
207
- {%- set _think_end = '</thinking>' -%}
208
- {%- elif '</ think>' in _ac -%}
209
- {%- set _think_end = '</ think>' -%}
210
- {%- elif '</think >' in _ac -%}
211
- {%- set _think_end = '</think >' -%}
212
- {%- endif -%}
213
- {%- if _think_end -%}
214
- {%- set _ac = _ac.split(_think_end)[-1].lstrip('\n') -%}
215
- {%- endif -%}
216
  {%- endif -%}
 
 
217
  {%- endif -%}
218
 
219
- {#- Emit the assistant turn -#}
220
  {{- '<|im_start|>assistant\n' -}}
221
  {%- if _ac -%}
222
  {{- _ac -}}
223
  {%- if _tc -%}{{- '\n' -}}{%- endif -%}
224
  {%- endif -%}
225
 
226
- {#- Render tool calls in Hermes format (BUG-006 fix: arguments as-is or tojson).
227
- Each value output via its own {{ }} block — never concatenated with plain strings
228
- in Python, which would trigger Markup HTML-escaping (BUG-003/markup fix). -#}
229
  {%- if _tc -%}
230
  {%- for tc in _tc -%}
231
  {{- '<tool_call>\n' -}}
@@ -245,15 +268,29 @@
245
  {%- endif -%}
246
  {{- '<|im_end|>\n' -}}
247
 
248
- {#- 7d: Tool results — group consecutive tool messages into one user turn -#}
249
  {%- elif message.role == 'tool' -%}
250
  {%- set _prev_role = messages[loop.index0 - 1].role if loop.index0 > 0 else '' -%}
251
  {%- set _next_role = messages[loop.index0 + 1].role if not loop.last else '' -%}
 
 
 
 
252
  {%- if _prev_role != 'tool' -%}
253
  {{- '<|im_start|>user\n' -}}
254
  {%- endif -%}
255
  {{- '<tool_response>\n' -}}
256
- {{- message.content | default('') -}}
 
 
 
 
 
 
 
 
 
 
257
  {%- if _next_role == 'tool' -%}
258
  {{- '\n</tool_response>\n' -}}
259
  {%- else -%}
@@ -261,25 +298,13 @@
261
  {{- '<|im_end|>\n' -}}
262
  {%- endif -%}
263
 
264
- {#- 7e: Unknown role -#}
265
  {%- else -%}
266
  {{- raise_exception('Unexpected message role: ' + message.role) -}}
267
  {%- endif -%}
268
 
269
  {%- endfor -%}
270
 
271
- {#- ===== SECTION 8: GENERATION PROMPT =====
272
- enable_thinking=True → open <think>\n prefill so llama.cpp reasoning-budget
273
- and other inference engines can hook into the think-stream.
274
- The model continues generating inside the open block.
275
- enable_thinking=False → exact non-thinking prefill: <think>\n\n</think>\n\n
276
- (19-char closed block, BUG-005 fix)
277
-
278
- NOTE: The <think>\n opener is EPHEMERAL — it lives only in the generation
279
- prompt, never in chat history. Historical think-block stripping (BUG-001)
280
- is handled in Section 7c and is entirely unaffected by this change.
281
- No context poisoning risk.
282
- -#}
283
  {%- if add_generation_prompt -%}
284
  {{- '<|im_start|>assistant\n' -}}
285
  {%- if ns.enable_thinking -%}
@@ -288,3 +313,5 @@
288
  {{- '<think>\n\n</think>\n\n' -}}
289
  {%- endif -%}
290
  {%- endif -%}
 
 
 
1
+ ```jinja
2
+ {#- ============================================================================
3
+ Qwen Chat Template v1.1 (Stable Agentic Version + JSON Escaping Fix)
4
+
5
+ FIXES APPLIED IN v1.1:
6
+ 1. Added strict JSON escaping instructions to Section 7 to prevent the LLM
7
+ from generating raw newlines and quotes inside JSON tool arguments.
8
+
9
+ FIXES APPLIED IN v1.0:
10
+ 1. Kept v0.9 Audio support and strict iterable type checks.
11
+ 2. REMOVED URL text injection before vision/audio pads.
12
+ 3. REMOVED Tool Call ID injection (violates Qwen's native JSON schema).
13
+ 4. RE-APPLIED Context Breakout Prevention (tojson on tool responses).
14
+ 5. RE-APPLIED Think-Mode Tool Call Prevention (Instruction in Section 7).
15
+ ============================================================================ -#}
16
+
17
+ {%- macro raise_exception(message) -%}
18
+ {{- '\n[ERROR: ' ~ message ~ ']' -}}
19
+ {%- endmacro -%}
20
+
21
+ {#- ===== SECTION 1A: MACRO render_content ===== -#}
22
+ {%- macro render_content(content, count_vision=false, is_system_content=false) -%}
23
+ {%- if is_system_content and content is iterable and content is not mapping and content is not string and content is not none -%}
24
+ {%- for item in content -%}
25
+ {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
26
+ {{- raise_exception('System message cannot contain images.') -}}
27
+ {%- endif -%}
28
+ {%- if item.type == 'video' or 'video' in item -%}
29
+ {{- raise_exception('System message cannot contain videos.') -}}
30
+ {%- endif -%}
31
+ {%- endfor -%}
32
+ {%- endif -%}
33
+
34
+ {%- if content is none or content is defined == false -%}
35
+ {{- '' -}}
36
+ {%- elif content is string -%}
37
  {{- content -}}
38
+ {%- elif content is not mapping and content is iterable and content is not string -%}
39
  {%- for item in content -%}
40
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
41
  {%- if count_vision -%}{%- set ns.image_count = ns.image_count + 1 -%}{%- endif -%}
 
43
  {{- 'Picture ' ~ ns.image_count ~ ': ' -}}
44
  {%- endif -%}
45
  {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
46
+ {%- elif item.type == 'audio' or 'audio' in item or 'audio_url' in item -%}
47
+ {%- if add_vision_id is defined and add_vision_id -%}
48
+ {{- 'Audio ' ~ ns.image_count ~ ': ' -}}
49
+ {%- endif -%}
50
+ {{- '<|audio_pad|><|audio_end|>' -}}
51
  {%- elif item.type == 'video' or 'video' in item -%}
52
  {%- if count_vision -%}{%- set ns.video_count = ns.video_count + 1 -%}{%- endif -%}
53
  {%- if add_vision_id is defined and add_vision_id -%}
 
56
  {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
57
  {%- elif item.type == 'text' or 'text' in item -%}
58
  {{- item.text -}}
59
+ {%- else -%}
60
+ {{- raise_exception('Unexpected content type in message content.') -}}
61
  {%- endif -%}
62
  {%- endfor -%}
63
+ {%- elif content is not none and content is defined -%}
64
+ {{- raise_exception('Unexpected content type.') -}}
65
  {%- endif -%}
66
  {%- endmacro -%}
67
 
68
+ {#- ===== SECTION 1B: MACRO detect_tool_error ===== -#}
69
+ {%- macro detect_tool_error(content) -%}
70
+ {%- set content = content if content is string else '' -%}
71
+ {%- set content_lower = content | lower -%}
72
+ {%- set content_length = content | length -%}
73
+
74
+ {%- if content_length < 500
75
+ and '$ ' not in content
76
+ and 'took ' not in content_lower
77
+ and ('"error":' in content_lower or 'error:' in content_lower
78
+ or 'exception:' in content_lower or 'traceback' in content_lower
79
+ or 'command not found' in content_lower or 'invalid syntax' in content_lower
80
+ or 'failed to' in content_lower or 'permission denied' in content_lower) -%}
81
+ {%- set ns.last_tool_failed = true -%}
82
+ {%- set ns.consecutive_failures = ns.consecutive_failures + 1 -%}
83
+ {%- else -%}
84
+ {%- set ns.last_tool_failed = false -%}
85
+ {%- set ns.consecutive_failures = 0 -%}
86
+ {%- endif -%}
87
+ {%- endmacro -%}
88
+
89
+ {#- ===== SECTION 2: NAMESPACE INITIALISATION ===== -#}
90
  {%- set ns = namespace(
91
  enable_thinking=true,
92
+ preserve_thinking=true,
93
  image_count=0,
94
+ video_count=0,
95
+ consecutive_failures=0,
96
+ last_tool_failed=false
97
  ) -%}
98
 
 
99
  {%- if enable_thinking is defined -%}
100
  {%- if enable_thinking -%}
101
  {%- set ns.enable_thinking = true -%}
 
104
  {%- endif -%}
105
  {%- endif -%}
106
 
107
+ {%- if preserve_thinking is defined -%}
108
+ {%- if not preserve_thinking -%}
109
+ {%- set ns.enable_thinking = false -%}
110
+ {%- set ns.preserve_thinking = false -%}
111
+ {%- else -%}
112
+ {%- set ns.preserve_thinking = true -%}
113
+ {%- endif -%}
114
  {%- endif -%}
115
 
116
+ {#- ===== SECTION 3: PRE-SCAN ===== -#}
 
 
 
 
 
117
  {%- for i in range(messages | length) -%}
118
  {%- set _msg = messages[i] -%}
119
  {%- if _msg.role == 'user' -%}
 
133
  {%- endif -%}
134
  {%- endfor -%}
135
 
136
+ {#- ===== SECTION 4: VALIDATE MESSAGES ===== -#}
137
+ {%- if not messages -%}
138
+ {{- raise_exception('No messages provided.') -}}
139
+ {%- endif -%}
140
+
141
+ {#- ===== SECTION 5: COLLECT SYSTEM CONTENT ===== -#}
142
  {%- set ns_sys = namespace(content='') -%}
143
  {%- for msg in messages -%}
144
  {%- if msg.role == 'system' or msg.role == 'developer' -%}
145
+ {%- set _c = render_content(msg.content | default(''), false, true) | trim -%}
146
  {%- set _c = _c | replace('<|think_off|>', '') | replace('<|think_on|>', '') | trim -%}
147
  {%- if _c -%}
148
  {%- if ns_sys.content == '' -%}
 
154
  {%- endif -%}
155
  {%- endfor -%}
156
 
157
+ {#- ===== SECTION 6: BUILD TOOLS LIST ===== -#}
 
 
 
158
  {%- set _has_tools = tools is defined and tools -%}
159
  {%- if _has_tools -%}
160
  {%- set ns_tb = namespace(list=[]) -%}
 
167
  {%- endfor -%}
168
  {%- endif -%}
169
 
170
+ {#- ===== SECTION 7: OUTPUT SYSTEM TURN ===== -#}
 
 
 
 
 
171
  {%- if ns_sys.content or _has_tools -%}
172
  {{- '<|im_start|>system\n' -}}
173
  {%- if ns_sys.content -%}
 
180
  {{- tool | tojson -}}
181
  {%- if not loop.last -%}{{- '\n' -}}{%- endif -%}
182
  {%- endfor -%}
183
+ {#- FIX v1.1: Added JSON strict escaping warnings to critical instructions -#}
184
+ {{- '\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>\n\nCRITICAL INSTRUCTIONS:\n1. You MUST NOT output <tool_call> tags while inside <think> tags. Complete your reasoning and output </think> FIRST, then initiate your tool calls.\n2. When passing multi-line code or text containing quotes into a JSON argument, you MUST strictly escape all newlines as \\n and quotes as \\". Failure to escape will invalidate the JSON and crash the system.' -}}
185
  {%- endif -%}
186
  {{- '<|im_end|>\n' -}}
187
  {%- endif -%}
188
 
189
+ {#- ===== SECTION 8: MAIN MESSAGE LOOP ===== -#}
190
  {%- for message in messages -%}
191
 
 
192
  {%- if message.role == 'system' or message.role == 'developer' -%}
193
 
 
194
  {%- elif message.role == 'user' -%}
195
+ {%- set _uc = render_content(message.content | default(''), true, false) -%}
196
  {{- '<|im_start|>user\n' + _uc + '<|im_end|>\n' -}}
197
 
 
198
  {%- elif message.role == 'assistant' -%}
 
 
 
199
  {%- if message.content is defined and message.content is string -%}
200
  {%- set _ac = message.content -%}
201
+ {%- elif message.content is defined and message.content is iterable and message.content is not mapping and message.content is not string -%}
202
+ {%- set _ac = render_content(message.content, false, false) -%}
203
  {%- else -%}
204
  {%- set _ac = '' -%}
205
  {%- endif -%}
206
 
 
 
 
207
  {%- if message.reasoning_content is defined and message.reasoning_content is string
208
  and message.reasoning_content | trim
209
  and '<think>' not in _ac -%}
210
  {%- set _ac = '<think>\n' + message.reasoning_content | trim + '\n</think>\n\n' + _ac -%}
211
  {%- endif -%}
212
 
213
+ {%- set _tc = message.tool_calls if message.tool_calls is defined and message.tool_calls is iterable and message.tool_calls is not string else [] -%}
 
214
 
 
 
215
  {%- if _tc and '<tool_call>' in _ac -%}
216
  {%- set _ac = _ac.split('<tool_call>')[0] | trim -%}
217
  {%- endif -%}
218
 
219
+ {%- set _show_think = false -%}
220
+ {%- if _tc -%}
221
+ {%- set _show_think = true -%}
222
+ {%- elif ns.preserve_thinking -%}
223
+ {%- set _show_think = true -%}
224
+ {%- elif loop.last -%}
225
+ {%- set _show_think = ns.enable_thinking -%}
226
+ {%- endif -%}
227
 
228
+ {%- if not _show_think -%}
229
+ {%- set _think_end = '' -%}
230
+ {%- if '</think>' in _ac -%}
231
+ {%- set _think_end = '</think>' -%}
232
+ {%- elif '</thinking>' in _ac -%}
233
+ {%- set _think_end = '</thinking>' -%}
234
+ {%- elif '</ think>' in _ac -%}
235
+ {%- set _think_end = '</ think>' -%}
236
+ {%- elif '</think >' in _ac -%}
237
+ {%- set _think_end = '</think >' -%}
238
+ {%- endif -%}
239
+ {%- if _think_end -%}
240
+ {%- set _ac = _ac.split(_think_end)[-1].lstrip('\n') -%}
 
 
 
 
 
 
 
 
 
 
 
 
241
  {%- endif -%}
242
+ {%- elif not _tc and loop.last and '<think>' not in _ac and not ns.enable_thinking -%}
243
+ {%- set _ac = '<think>\n\n</think>\n\n' + _ac -%}
244
  {%- endif -%}
245
 
 
246
  {{- '<|im_start|>assistant\n' -}}
247
  {%- if _ac -%}
248
  {{- _ac -}}
249
  {%- if _tc -%}{{- '\n' -}}{%- endif -%}
250
  {%- endif -%}
251
 
 
 
 
252
  {%- if _tc -%}
253
  {%- for tc in _tc -%}
254
  {{- '<tool_call>\n' -}}
 
268
  {%- endif -%}
269
  {{- '<|im_end|>\n' -}}
270
 
271
+ {#- 8d: Tool results -#}
272
  {%- elif message.role == 'tool' -%}
273
  {%- set _prev_role = messages[loop.index0 - 1].role if loop.index0 > 0 else '' -%}
274
  {%- set _next_role = messages[loop.index0 + 1].role if not loop.last else '' -%}
275
+
276
+ {%- set _tool_content = message.content | default('') -%}
277
+ {{- detect_tool_error(_tool_content) -}}
278
+
279
  {%- if _prev_role != 'tool' -%}
280
  {{- '<|im_start|>user\n' -}}
281
  {%- endif -%}
282
  {{- '<tool_response>\n' -}}
283
+
284
+ {{- '{"result": ' ~ _tool_content | string | tojson ~ '}' -}}
285
+
286
+ {%- if ns.last_tool_failed -%}
287
+ {%- if ns.consecutive_failures >= 2 -%}
288
+ {{- '\n\n[SYSTEM WARNING: ' ~ ns.consecutive_failures ~ ' consecutive tool errors detected. Your previous approach is incorrect.]' -}}
289
+ {%- else -%}
290
+ {{- '\n\n[SYSTEM WARNING: The previous tool call returned an error. Diagnose the failure and retry with corrected arguments.]' -}}
291
+ {%- endif -%}
292
+ {%- endif -%}
293
+
294
  {%- if _next_role == 'tool' -%}
295
  {{- '\n</tool_response>\n' -}}
296
  {%- else -%}
 
298
  {{- '<|im_end|>\n' -}}
299
  {%- endif -%}
300
 
 
301
  {%- else -%}
302
  {{- raise_exception('Unexpected message role: ' + message.role) -}}
303
  {%- endif -%}
304
 
305
  {%- endfor -%}
306
 
307
+ {#- ===== SECTION 9: GENERATION PROMPT ===== -#}
 
 
 
 
 
 
 
 
 
 
 
308
  {%- if add_generation_prompt -%}
309
  {{- '<|im_start|>assistant\n' -}}
310
  {%- if ns.enable_thinking -%}
 
313
  {{- '<think>\n\n</think>\n\n' -}}
314
  {%- endif -%}
315
  {%- endif -%}
316
+
317
+ ```
qwen3_5-6-template_v1.1.2.jinja → v1.1.5-variant1_added_agentic-coding-prompt-enhancements.jinja RENAMED
@@ -1,51 +1,30 @@
 
1
  {#- ============================================================================
2
- Qwen Chat Template v0.8
3
- Based on v0.7 with additional compatibility fixes for llama.cpp
4
 
5
- FIXES APPLIED IN v0.8:
6
- 1. Type guard in detect_tool_error macro (prevents errors on non-string input)
7
- 2. Type check for tool_calls (ensures list type before iteration)
8
- 3. Replaced emoji in tool error warnings with text-only (tokenization safety)
9
-
10
- FIXES APPLIED IN v0.7:
11
- 1. Tool call error handling with consecutive_failures tracking
12
- 2. System message media validation (raises exception for images/videos)
13
- 3. Empty messages validation (raises exception if no messages)
14
- 4. Unknown content type handling (raises exception for unexpected types)
15
- 5. Think-block display logic (preserve_thinking controls ALL assistant messages, not just generation prompt)
16
-
17
- Sections:
18
- - 1A: MACRO render_content (with media validation for system content)
19
- - 1B: MACRO detect_tool_error (error detection for tool responses)
20
- - 2: NAMESPACE INITIALISATION (with error tracking)
21
- - 3: PRE-SCAN (thinking mode detection)
22
- - 4: VALIDATE MESSAGES (empty messages check)
23
- - 5: COLLECT SYSTEM CONTENT (with media validation for system messages)
24
- - 6: BUILD TOOLS LIST
25
- - 7: OUTPUT SYSTEM TURN
26
- - 8: MAIN MESSAGE LOOP (with error detection in tool responses)
27
- - 9: GENERATION PROMPT (fixed preserve_thinking logic)
28
  ============================================================================ -#}
29
 
30
- {#- ===== HELPER: raise_exception macro =====
31
- Jinja2 doesn't have a built-in raise_exception.
32
- This macro outputs an error marker in the rendered output.
33
- Callers should check output for "ERROR:" pattern to detect validation failures.
34
- -#}
35
  {%- macro raise_exception(message) -%}
36
  {{- '\n[ERROR: ' ~ message ~ ']' -}}
37
  {%- endmacro -%}
38
 
39
- {#- ===== SECTION 1A: MACRO render_content =====
40
- Handles string, list (image/video/text items), or None/undefined.
41
- count_vision=true: increments ns.image_count / ns.video_count.
42
- is_system_content=false: Set true when rendering system/developer content
43
- to enable media validation (raises exception).
44
- count_vision=true: increments vision counters.
45
- -#}
46
  {%- macro render_content(content, count_vision=false, is_system_content=false) -%}
47
- {#- VALIDATION: System messages cannot contain images or videos (from v18) -#}
48
- {#- FIX: also exclude strings and handle None - llama.cpp treats strings as non-iterable in for loops -#}
49
  {%- if is_system_content and content is iterable and content is not mapping and content is not string and content is not none -%}
50
  {%- for item in content -%}
51
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
@@ -57,22 +36,23 @@
57
  {%- endfor -%}
58
  {%- endif -%}
59
 
60
- {#- Main content rendering -#}
61
- {#- Handle None/undefined content -#}
62
  {%- if content is none or content is defined == false -%}
63
  {{- '' -}}
64
  {%- elif content is string -%}
65
  {{- content -}}
66
- {#- FIX: also exclude strings - llama.cpp treats strings as non-iterable in for loops -#}
67
- {%- elif content is iterable and content is not mapping and content is not string -%}
68
  {%- for item in content -%}
69
- {#- Handle different item types -#}
70
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
71
  {%- if count_vision -%}{%- set ns.image_count = ns.image_count + 1 -%}{%- endif -%}
72
  {%- if add_vision_id is defined and add_vision_id -%}
73
  {{- 'Picture ' ~ ns.image_count ~ ': ' -}}
74
  {%- endif -%}
75
  {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
 
 
 
 
 
76
  {%- elif item.type == 'video' or 'video' in item -%}
77
  {%- if count_vision -%}{%- set ns.video_count = ns.video_count + 1 -%}{%- endif -%}
78
  {%- if add_vision_id is defined and add_vision_id -%}
@@ -81,34 +61,21 @@
81
  {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
82
  {%- elif item.type == 'text' or 'text' in item -%}
83
  {{- item.text -}}
84
- {#- ERROR: Unknown content type - raise explicit exception (from v18) -#}
85
  {%- else -%}
86
  {{- raise_exception('Unexpected content type in message content.') -}}
87
  {%- endif -%}
88
  {%- endfor -%}
89
- {#- ERROR: Unknown content type - raise explicit exception (from v18) -#}
90
  {%- elif content is not none and content is defined -%}
91
  {{- raise_exception('Unexpected content type.') -}}
92
  {%- endif -%}
93
  {%- endmacro -%}
94
 
95
- {#- ===== SECTION 1B: MACRO detect_tool_error (NEW in v0.7) =====
96
- Detects if a tool response contains error indicators.
97
- Uses heuristics from v18:
98
- - Checks for error keywords (error, exception, traceback, failed to)
99
- - Ignores responses with '$ ' (shell output prefix) or 'took ' (timing info)
100
- - Ignores responses > 500 chars (likely valid output, not error)
101
-
102
- Returns: ns.last_tool_failed (true/false)
103
- Side effect: Updates ns.consecutive_failures counter
104
- -#}
105
  {%- macro detect_tool_error(content) -%}
106
- {#- Type guard: ensure content is string (llama.cpp compatibility) -#}
107
  {%- set content = content if content is string else '' -%}
108
  {%- set content_lower = content | lower -%}
109
  {%- set content_length = content | length -%}
110
 
111
- {#- Error detection heuristics: short response + no shell prefix + has error keywords -#}
112
  {%- if content_length < 500
113
  and '$ ' not in content
114
  and 'took ' not in content_lower
@@ -116,28 +83,15 @@
116
  or 'exception:' in content_lower or 'traceback' in content_lower
117
  or 'command not found' in content_lower or 'invalid syntax' in content_lower
118
  or 'failed to' in content_lower or 'permission denied' in content_lower) -%}
119
- {#- Error detected - update failure tracking -#}
120
  {%- set ns.last_tool_failed = true -%}
121
  {%- set ns.consecutive_failures = ns.consecutive_failures + 1 -%}
122
  {%- else -%}
123
- {#- No error - reset failure tracking -#}
124
  {%- set ns.last_tool_failed = false -%}
125
  {%- set ns.consecutive_failures = 0 -%}
126
  {%- endif -%}
127
  {%- endmacro -%}
128
 
129
- {#- ===== SECTION 2: NAMESPACE INITIALISATION =====
130
- Single ns object for all mutable state.
131
-
132
- enable_thinking: default=true (controls think-block in generation prompt)
133
- preserve_thinking: default=true (controls think-block display in conversation history)
134
- image_count: Vision counter for images
135
- video_count: Vision counter for videos
136
-
137
- NEW in v0.7:
138
- - consecutive_failures: Tracks consecutive tool call failures (from v18)
139
- - last_tool_failed: Boolean flag for current tool response (from v18)
140
- -#}
141
  {%- set ns = namespace(
142
  enable_thinking=true,
143
  preserve_thinking=true,
@@ -147,7 +101,6 @@
147
  last_tool_failed=false
148
  ) -%}
149
 
150
- {#- Resolve enable_thinking kwarg -#}
151
  {%- if enable_thinking is defined -%}
152
  {%- if enable_thinking -%}
153
  {%- set ns.enable_thinking = true -%}
@@ -156,11 +109,6 @@
156
  {%- endif -%}
157
  {%- endif -%}
158
 
159
- {#- Resolve preserve_thinking kwarg (FIXED in v0.7: now also affects conversation history, not just generation prompt).
160
- preserve_thinking=false => force non-thinking mode (same as enable_thinking=false).
161
- preserve_thinking=true => default, no override (thinking controlled by enable_thinking).
162
- When not defined => default, no override.
163
- -#}
164
  {%- if preserve_thinking is defined -%}
165
  {%- if not preserve_thinking -%}
166
  {%- set ns.enable_thinking = false -%}
@@ -170,12 +118,7 @@
170
  {%- endif -%}
171
  {%- endif -%}
172
 
173
- {#- ===== SECTION 3: PRE-SCAN =====
174
- Track last /no_think or /think flag in user messages.
175
- Also scan system messages for <|think_off|> / <|think_on|> markers
176
- (allows apps to control thinking mode via system prompt injection).
177
- The model follows the last flag encountered in multi-turn conversations.
178
- -#}
179
  {%- for i in range(messages | length) -%}
180
  {%- set _msg = messages[i] -%}
181
  {%- if _msg.role == 'user' -%}
@@ -195,25 +138,15 @@
195
  {%- endif -%}
196
  {%- endfor -%}
197
 
198
- {#- ===== SECTION 4: VALIDATE MESSAGES (NEW in v0.7) =====
199
- Validate that messages is provided and not empty.
200
- From v18: raises exception if no messages provided.
201
- -#}
202
  {%- if not messages -%}
203
  {{- raise_exception('No messages provided.') -}}
204
  {%- endif -%}
205
 
206
- {#- ===== SECTION 5: COLLECT SYSTEM CONTENT =====
207
- Merge all system/developer messages with \n\n separator.
208
- <|think_off|> / <|think_on|> markers are stripped from output.
209
-
210
- FIXED in v0.7: Pass is_system_content=true to render_content to trigger
211
- media validation (raises exception if system contains images/videos).
212
- -#}
213
  {%- set ns_sys = namespace(content='') -%}
214
  {%- for msg in messages -%}
215
  {%- if msg.role == 'system' or msg.role == 'developer' -%}
216
- {#- Pass is_system_content=true for media validation -#}
217
  {%- set _c = render_content(msg.content | default(''), false, true) | trim -%}
218
  {%- set _c = _c | replace('<|think_off|>', '') | replace('<|think_on|>', '') | trim -%}
219
  {%- if _c -%}
@@ -226,10 +159,7 @@
226
  {%- endif -%}
227
  {%- endfor -%}
228
 
229
- {#- ===== SECTION 6: BUILD TOOLS LIST =====
230
- Normalise each tool to {"type":"function","function":{...}} format.
231
- Serialisation happens later at output time (avoids Markup + str escaping bugs).
232
- -#}
233
  {%- set _has_tools = tools is defined and tools -%}
234
  {%- if _has_tools -%}
235
  {%- set ns_tb = namespace(list=[]) -%}
@@ -242,12 +172,7 @@
242
  {%- endfor -%}
243
  {%- endif -%}
244
 
245
- {#- ===== SECTION 7: OUTPUT SYSTEM TURN =====
246
- Each fragment output via its own {{ }} block so tojson Markup objects are
247
- never Python-concatenated with plain strings (would trigger HTML-escaping).
248
- User system content appears BEFORE the tools block (correct ordering).
249
- No default system prompt injected.
250
- -#}
251
  {%- if ns_sys.content or _has_tools -%}
252
  {{- '<|im_start|>system\n' -}}
253
  {%- if ns_sys.content -%}
@@ -260,88 +185,52 @@
260
  {{- tool | tojson -}}
261
  {%- if not loop.last -%}{{- '\n' -}}{%- endif -%}
262
  {%- endfor -%}
263
- {{- '\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>' -}}
 
264
  {%- endif -%}
265
  {{- '<|im_end|>\n' -}}
266
  {%- endif -%}
267
 
268
- {#- ===== SECTION 8: MAIN MESSAGE LOOP =====
269
- FIXED in v0.7:
270
- - Tool responses now have error detection via detect_tool_error macro
271
- - Warning messages injected for failed tool calls
272
- - consecutive_failures tracking for escalating warnings
273
- -#}
274
  {%- for message in messages -%}
275
 
276
- {#- 8a: System / Developer — already rendered above, skip -#}
277
  {%- if message.role == 'system' or message.role == 'developer' -%}
278
 
279
- {#- 8b: User messages -#}
280
  {%- elif message.role == 'user' -%}
281
  {%- set _uc = render_content(message.content | default(''), true, false) -%}
282
  {{- '<|im_start|>user\n' + _uc + '<|im_end|>\n' -}}
283
 
284
- {#- 8c: Assistant messages -#}
285
  {%- elif message.role == 'assistant' -%}
286
- {#- Safely extract content as string — guard against absent key.
287
- Also support message.reasoning_content as an explicit think-block source
288
- (used by some frameworks that store thinking separately from content). -#}
289
  {%- if message.content is defined and message.content is string -%}
290
  {%- set _ac = message.content -%}
291
- {#- FIX: also exclude strings - llama.cpp treats strings as non-iterable in for loops -#}
292
  {%- elif message.content is defined and message.content is iterable and message.content is not mapping and message.content is not string -%}
293
  {%- set _ac = render_content(message.content, false, false) -%}
294
  {%- else -%}
295
  {%- set _ac = '' -%}
296
  {%- endif -%}
297
 
298
- {#- Reconstruct content from reasoning_content + content when the framework
299
- stores thinking separately (e.g. OpenAI-style reasoning_content field).
300
- Only apply when no think-block already present in _ac. -#}
301
  {%- if message.reasoning_content is defined and message.reasoning_content is string
302
  and message.reasoning_content | trim
303
  and '<think>' not in _ac -%}
304
  {%- set _ac = '<think>\n' + message.reasoning_content | trim + '\n</think>\n\n' + _ac -%}
305
  {%- endif -%}
306
 
307
- {#- Collect tool_calls if present -#}
308
- {#- Type check: ensure tool_calls is a list, not string (llama.cpp compatibility) -#}
309
  {%- set _tc = message.tool_calls if message.tool_calls is defined and message.tool_calls is iterable and message.tool_calls is not string else [] -%}
310
 
311
- {#- Strip <tool_call> prefix from content when tool_calls also present
312
- (some frameworks duplicate the data in both fields) -#}
313
  {%- if _tc and '<tool_call>' in _ac -%}
314
  {%- set _ac = _ac.split('<tool_call>')[0] | trim -%}
315
  {%- endif -%}
316
 
317
- {#- FIXED in v0.7: Think-block handling with preserve_thinking support
318
-
319
- New logic (from v18): preserve_thinking controls think-block display on ALL
320
- assistant messages, not just generation prompt:
321
-
322
- - Tool-call turns : never strip (think block is part of the tool-call format)
323
- - preserve_thinking : if true, show think blocks on ALL messages
324
- - Last-history turn : if preserve_thinking false, apply last-turn handling
325
- - Historical turns : if preserve_thinking false, strip think blocks
326
-
327
- The old behavior (strip unless add_generation_prompt) is now controlled
328
- by preserve_thinking parameter.
329
- -#}
330
  {%- set _show_think = false -%}
331
  {%- if _tc -%}
332
- {#- Tool calls: always show think block -#}
333
  {%- set _show_think = true -%}
334
  {%- elif ns.preserve_thinking -%}
335
- {#- preserve_thinking=true: show think blocks on all messages -#}
336
  {%- set _show_think = true -%}
337
  {%- elif loop.last -%}
338
- {#- Last message without preserve_thinking: show if thinking enabled -#}
339
  {%- set _show_think = ns.enable_thinking -%}
340
  {%- endif -%}
341
 
342
- {#- Apply think-block stripping based on _show_think flag -#}
343
  {%- if not _show_think -%}
344
- {#- Fuzzy end-tag detection for stripping -#}
345
  {%- set _think_end = '' -%}
346
  {%- if '</think>' in _ac -%}
347
  {%- set _think_end = '</think>' -%}
@@ -356,20 +245,15 @@
356
  {%- set _ac = _ac.split(_think_end)[-1].lstrip('\n') -%}
357
  {%- endif -%}
358
  {%- elif not _tc and loop.last and '<think>' not in _ac and not ns.enable_thinking -%}
359
- {#- Last turn, non-thinking: inject empty think block if missing -#}
360
  {%- set _ac = '<think>\n\n</think>\n\n' + _ac -%}
361
  {%- endif -%}
362
 
363
- {#- Emit the assistant turn -#}
364
  {{- '<|im_start|>assistant\n' -}}
365
  {%- if _ac -%}
366
  {{- _ac -}}
367
  {%- if _tc -%}{{- '\n' -}}{%- endif -%}
368
  {%- endif -%}
369
 
370
- {#- Render tool calls in Hermes format.
371
- Each value output via its own {{ }} block — never concatenated with plain strings
372
- in Python, which would trigger Markup HTML-escaping. -#}
373
  {%- if _tc -%}
374
  {%- for tc in _tc -%}
375
  {{- '<tool_call>\n' -}}
@@ -389,12 +273,11 @@
389
  {%- endif -%}
390
  {{- '<|im_end|>\n' -}}
391
 
392
- {#- 8d: Tool results — with error detection (NEW in v0.7) -#}
393
  {%- elif message.role == 'tool' -%}
394
  {%- set _prev_role = messages[loop.index0 - 1].role if loop.index0 > 0 else '' -%}
395
  {%- set _next_role = messages[loop.index0 + 1].role if not loop.last else '' -%}
396
 
397
- {#- NEW in v0.7: Detect errors in tool response -#}
398
  {%- set _tool_content = message.content | default('') -%}
399
  {{- detect_tool_error(_tool_content) -}}
400
 
@@ -402,10 +285,9 @@
402
  {{- '<|im_start|>user\n' -}}
403
  {%- endif -%}
404
  {{- '<tool_response>\n' -}}
405
- {{- _tool_content -}}
406
 
407
- {#- NEW in v0.7: Inject warning if tool error detected -#}
408
- {#- v0.8: Replaced emoji with text-only for tokenization safety -#}
409
  {%- if ns.last_tool_failed -%}
410
  {%- if ns.consecutive_failures >= 2 -%}
411
  {{- '\n\n[SYSTEM WARNING: ' ~ ns.consecutive_failures ~ ' consecutive tool errors detected. Your previous approach is incorrect.]' -}}
@@ -421,26 +303,13 @@
421
  {{- '<|im_end|>\n' -}}
422
  {%- endif -%}
423
 
424
- {#- 8e: Unknown role - explicit error (from v18) -#}
425
  {%- else -%}
426
  {{- raise_exception('Unexpected message role: ' + message.role) -}}
427
  {%- endif -%}
428
 
429
  {%- endfor -%}
430
 
431
- {#- ===== SECTION 9: GENERATION PROMPT =====
432
- FIXED in v0.7: preserve_thinking now affects conversation history (Section 8),
433
- so generation prompt logic is simplified.
434
-
435
- enable_thinking=True → open <think>\n prefill so llama.cpp reasoning-budget
436
- and other inference engines can hook into the think-stream.
437
- The model continues generating inside the open block.
438
- enable_thinking=False → exact non-thinking prefill: </think>\n\n
439
-
440
- NOTE: The <think>\n opener is EPHEMERAL — it lives only in the generation
441
- prompt, never in chat history. Historical think-block stripping is handled
442
- in Section 8 based on preserve_thinking setting.
443
- -#}
444
  {%- if add_generation_prompt -%}
445
  {{- '<|im_start|>assistant\n' -}}
446
  {%- if ns.enable_thinking -%}
@@ -448,4 +317,6 @@
448
  {%- else -%}
449
  {{- '<think>\n\n</think>\n\n' -}}
450
  {%- endif -%}
451
- {%- endif -%}
 
 
 
1
+ ```jinja
2
  {#- ============================================================================
3
+ Qwen Chat Template v1.2 (Ultimate Agentic Version)
 
4
 
5
+ FIXES APPLIED IN v1.2:
6
+ 1. Added comprehensive Agentic rules to Section 7 (NO LAZINESS, BREAK LOOPS,
7
+ STRICT TOOLS, VERIFY) to prevent common agentic loops and lazy coding
8
+ behaviors in frameworks like Hermes and OpenHands.
9
+
10
+ FIXES APPLIED IN v1.1:
11
+ 1. Added strict JSON escaping instructions to prevent Context Breakouts during
12
+ outbound code generation.
13
+
14
+ FIXES APPLIED IN v1.0:
15
+ 1. Kept v0.9 Audio support and strict iterable type checks.
16
+ 2. REMOVED URL text injection before vision/audio pads.
17
+ 3. REMOVED Tool Call ID injection (violates Qwen's native JSON schema).
18
+ 4. RE-APPLIED Context Breakout Prevention (tojson on tool responses).
19
+ 5. RE-APPLIED Think-Mode Tool Call Prevention (Instruction in Section 7).
 
 
 
 
 
 
 
 
20
  ============================================================================ -#}
21
 
 
 
 
 
 
22
  {%- macro raise_exception(message) -%}
23
  {{- '\n[ERROR: ' ~ message ~ ']' -}}
24
  {%- endmacro -%}
25
 
26
+ {#- ===== SECTION 1A: MACRO render_content ===== -#}
 
 
 
 
 
 
27
  {%- macro render_content(content, count_vision=false, is_system_content=false) -%}
 
 
28
  {%- if is_system_content and content is iterable and content is not mapping and content is not string and content is not none -%}
29
  {%- for item in content -%}
30
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
 
36
  {%- endfor -%}
37
  {%- endif -%}
38
 
 
 
39
  {%- if content is none or content is defined == false -%}
40
  {{- '' -}}
41
  {%- elif content is string -%}
42
  {{- content -}}
43
+ {%- elif content is not mapping and content is iterable and content is not string -%}
 
44
  {%- for item in content -%}
 
45
  {%- if item.type == 'image' or 'image' in item or 'image_url' in item -%}
46
  {%- if count_vision -%}{%- set ns.image_count = ns.image_count + 1 -%}{%- endif -%}
47
  {%- if add_vision_id is defined and add_vision_id -%}
48
  {{- 'Picture ' ~ ns.image_count ~ ': ' -}}
49
  {%- endif -%}
50
  {{- '<|vision_start|><|image_pad|><|vision_end|>' -}}
51
+ {%- elif item.type == 'audio' or 'audio' in item or 'audio_url' in item -%}
52
+ {%- if add_vision_id is defined and add_vision_id -%}
53
+ {{- 'Audio ' ~ ns.image_count ~ ': ' -}}
54
+ {%- endif -%}
55
+ {{- '<|audio_pad|><|audio_end|>' -}}
56
  {%- elif item.type == 'video' or 'video' in item -%}
57
  {%- if count_vision -%}{%- set ns.video_count = ns.video_count + 1 -%}{%- endif -%}
58
  {%- if add_vision_id is defined and add_vision_id -%}
 
61
  {{- '<|vision_start|><|video_pad|><|vision_end|>' -}}
62
  {%- elif item.type == 'text' or 'text' in item -%}
63
  {{- item.text -}}
 
64
  {%- else -%}
65
  {{- raise_exception('Unexpected content type in message content.') -}}
66
  {%- endif -%}
67
  {%- endfor -%}
 
68
  {%- elif content is not none and content is defined -%}
69
  {{- raise_exception('Unexpected content type.') -}}
70
  {%- endif -%}
71
  {%- endmacro -%}
72
 
73
+ {#- ===== SECTION 1B: MACRO detect_tool_error ===== -#}
 
 
 
 
 
 
 
 
 
74
  {%- macro detect_tool_error(content) -%}
 
75
  {%- set content = content if content is string else '' -%}
76
  {%- set content_lower = content | lower -%}
77
  {%- set content_length = content | length -%}
78
 
 
79
  {%- if content_length < 500
80
  and '$ ' not in content
81
  and 'took ' not in content_lower
 
83
  or 'exception:' in content_lower or 'traceback' in content_lower
84
  or 'command not found' in content_lower or 'invalid syntax' in content_lower
85
  or 'failed to' in content_lower or 'permission denied' in content_lower) -%}
 
86
  {%- set ns.last_tool_failed = true -%}
87
  {%- set ns.consecutive_failures = ns.consecutive_failures + 1 -%}
88
  {%- else -%}
 
89
  {%- set ns.last_tool_failed = false -%}
90
  {%- set ns.consecutive_failures = 0 -%}
91
  {%- endif -%}
92
  {%- endmacro -%}
93
 
94
+ {#- ===== SECTION 2: NAMESPACE INITIALISATION ===== -#}
 
 
 
 
 
 
 
 
 
 
 
95
  {%- set ns = namespace(
96
  enable_thinking=true,
97
  preserve_thinking=true,
 
101
  last_tool_failed=false
102
  ) -%}
103
 
 
104
  {%- if enable_thinking is defined -%}
105
  {%- if enable_thinking -%}
106
  {%- set ns.enable_thinking = true -%}
 
109
  {%- endif -%}
110
  {%- endif -%}
111
 
 
 
 
 
 
112
  {%- if preserve_thinking is defined -%}
113
  {%- if not preserve_thinking -%}
114
  {%- set ns.enable_thinking = false -%}
 
118
  {%- endif -%}
119
  {%- endif -%}
120
 
121
+ {#- ===== SECTION 3: PRE-SCAN ===== -#}
 
 
 
 
 
122
  {%- for i in range(messages | length) -%}
123
  {%- set _msg = messages[i] -%}
124
  {%- if _msg.role == 'user' -%}
 
138
  {%- endif -%}
139
  {%- endfor -%}
140
 
141
+ {#- ===== SECTION 4: VALIDATE MESSAGES ===== -#}
 
 
 
142
  {%- if not messages -%}
143
  {{- raise_exception('No messages provided.') -}}
144
  {%- endif -%}
145
 
146
+ {#- ===== SECTION 5: COLLECT SYSTEM CONTENT ===== -#}
 
 
 
 
 
 
147
  {%- set ns_sys = namespace(content='') -%}
148
  {%- for msg in messages -%}
149
  {%- if msg.role == 'system' or msg.role == 'developer' -%}
 
150
  {%- set _c = render_content(msg.content | default(''), false, true) | trim -%}
151
  {%- set _c = _c | replace('<|think_off|>', '') | replace('<|think_on|>', '') | trim -%}
152
  {%- if _c -%}
 
159
  {%- endif -%}
160
  {%- endfor -%}
161
 
162
+ {#- ===== SECTION 6: BUILD TOOLS LIST ===== -#}
 
 
 
163
  {%- set _has_tools = tools is defined and tools -%}
164
  {%- if _has_tools -%}
165
  {%- set ns_tb = namespace(list=[]) -%}
 
172
  {%- endfor -%}
173
  {%- endif -%}
174
 
175
+ {#- ===== SECTION 7: OUTPUT SYSTEM TURN ===== -#}
 
 
 
 
 
176
  {%- if ns_sys.content or _has_tools -%}
177
  {{- '<|im_start|>system\n' -}}
178
  {%- if ns_sys.content -%}
 
185
  {{- tool | tojson -}}
186
  {%- if not loop.last -%}{{- '\n' -}}{%- endif -%}
187
  {%- endfor -%}
188
+ {#- FIX v1.2: Added comprehensive agentic rules to the critical instructions -#}
189
+ {{- '\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{"name": <function-name>, "arguments": <args-json-object>}\n</tool_call>\n\nCRITICAL INSTRUCTIONS:\n1. You MUST NOT output <tool_call> tags while inside <think> tags. Complete your reasoning and output </think> FIRST, then initiate your tool calls.\n2. When passing multi-line code or text containing quotes into a JSON argument, you MUST strictly escape all newlines as \\n and quotes as \\". Failure to escape will invalidate the JSON and crash the system.\n3. NO LAZINESS: Never use placeholders like "// existing code" or "/* rest of function */". You must output the complete, runnable code block required by the tool.\n4. BREAK LOOPS: If a tool call fails with an error, DO NOT repeat the exact same call. Analyze the error, change your approach, use alternative tools, or ask the user for clarification.\n5. STRICT TOOLS: Only use the exact function names listed in the <tools> block. Do not invent new tools or use colon-separated namespaces unless explicitly provided.\n6. VERIFY: After making file changes, always run tests or use terminal commands to verify your code compiles and works before responding to the user.' -}}
190
  {%- endif -%}
191
  {{- '<|im_end|>\n' -}}
192
  {%- endif -%}
193
 
194
+ {#- ===== SECTION 8: MAIN MESSAGE LOOP ===== -#}
 
 
 
 
 
195
  {%- for message in messages -%}
196
 
 
197
  {%- if message.role == 'system' or message.role == 'developer' -%}
198
 
 
199
  {%- elif message.role == 'user' -%}
200
  {%- set _uc = render_content(message.content | default(''), true, false) -%}
201
  {{- '<|im_start|>user\n' + _uc + '<|im_end|>\n' -}}
202
 
 
203
  {%- elif message.role == 'assistant' -%}
 
 
 
204
  {%- if message.content is defined and message.content is string -%}
205
  {%- set _ac = message.content -%}
 
206
  {%- elif message.content is defined and message.content is iterable and message.content is not mapping and message.content is not string -%}
207
  {%- set _ac = render_content(message.content, false, false) -%}
208
  {%- else -%}
209
  {%- set _ac = '' -%}
210
  {%- endif -%}
211
 
 
 
 
212
  {%- if message.reasoning_content is defined and message.reasoning_content is string
213
  and message.reasoning_content | trim
214
  and '<think>' not in _ac -%}
215
  {%- set _ac = '<think>\n' + message.reasoning_content | trim + '\n</think>\n\n' + _ac -%}
216
  {%- endif -%}
217
 
 
 
218
  {%- set _tc = message.tool_calls if message.tool_calls is defined and message.tool_calls is iterable and message.tool_calls is not string else [] -%}
219
 
 
 
220
  {%- if _tc and '<tool_call>' in _ac -%}
221
  {%- set _ac = _ac.split('<tool_call>')[0] | trim -%}
222
  {%- endif -%}
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  {%- set _show_think = false -%}
225
  {%- if _tc -%}
 
226
  {%- set _show_think = true -%}
227
  {%- elif ns.preserve_thinking -%}
 
228
  {%- set _show_think = true -%}
229
  {%- elif loop.last -%}
 
230
  {%- set _show_think = ns.enable_thinking -%}
231
  {%- endif -%}
232
 
 
233
  {%- if not _show_think -%}
 
234
  {%- set _think_end = '' -%}
235
  {%- if '</think>' in _ac -%}
236
  {%- set _think_end = '</think>' -%}
 
245
  {%- set _ac = _ac.split(_think_end)[-1].lstrip('\n') -%}
246
  {%- endif -%}
247
  {%- elif not _tc and loop.last and '<think>' not in _ac and not ns.enable_thinking -%}
 
248
  {%- set _ac = '<think>\n\n</think>\n\n' + _ac -%}
249
  {%- endif -%}
250
 
 
251
  {{- '<|im_start|>assistant\n' -}}
252
  {%- if _ac -%}
253
  {{- _ac -}}
254
  {%- if _tc -%}{{- '\n' -}}{%- endif -%}
255
  {%- endif -%}
256
 
 
 
 
257
  {%- if _tc -%}
258
  {%- for tc in _tc -%}
259
  {{- '<tool_call>\n' -}}
 
273
  {%- endif -%}
274
  {{- '<|im_end|>\n' -}}
275
 
276
+ {#- 8d: Tool results -#}
277
  {%- elif message.role == 'tool' -%}
278
  {%- set _prev_role = messages[loop.index0 - 1].role if loop.index0 > 0 else '' -%}
279
  {%- set _next_role = messages[loop.index0 + 1].role if not loop.last else '' -%}
280
 
 
281
  {%- set _tool_content = message.content | default('') -%}
282
  {{- detect_tool_error(_tool_content) -}}
283
 
 
285
  {{- '<|im_start|>user\n' -}}
286
  {%- endif -%}
287
  {{- '<tool_response>\n' -}}
 
288
 
289
+ {{- '{"result": ' ~ _tool_content | string | tojson ~ '}' -}}
290
+
291
  {%- if ns.last_tool_failed -%}
292
  {%- if ns.consecutive_failures >= 2 -%}
293
  {{- '\n\n[SYSTEM WARNING: ' ~ ns.consecutive_failures ~ ' consecutive tool errors detected. Your previous approach is incorrect.]' -}}
 
303
  {{- '<|im_end|>\n' -}}
304
  {%- endif -%}
305
 
 
306
  {%- else -%}
307
  {{- raise_exception('Unexpected message role: ' + message.role) -}}
308
  {%- endif -%}
309
 
310
  {%- endfor -%}
311
 
312
+ {#- ===== SECTION 9: GENERATION PROMPT ===== -#}
 
 
 
 
 
 
 
 
 
 
 
 
313
  {%- if add_generation_prompt -%}
314
  {{- '<|im_start|>assistant\n' -}}
315
  {%- if ns.enable_thinking -%}
 
317
  {%- else -%}
318
  {{- '<think>\n\n</think>\n\n' -}}
319
  {%- endif -%}
320
+ {%- endif -%}
321
+
322
+ ```