LuffyTheFox

Untitled

Apr 17th, 2026 (edited)
1,961
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.31 KB | None | 0 0
  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 -%}
  12. {%- if add_vision_id is defined and add_vision_id -%}
  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 -%}
  19. {{- 'Video ' ~ ns.video_count ~ ': ' -}}
  20. {%- endif -%}
  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 -%}
  46. {%- else -%}
  47. {%- set ns.enable_thinking = false -%}
  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' -%}
  69. {%- set _u = _msg.content if _msg.content is string else '' -%}
  70. {%- if _u.rstrip().endswith('/no_think') -%}
  71. {%- set ns.enable_thinking = false -%}
  72. {%- elif _u.rstrip().endswith('/think') -%}
  73. {%- set ns.enable_thinking = true -%}
  74. {%- endif -%}
  75. {%- elif _msg.role == 'system' or _msg.role == 'developer' -%}
  76. {%- set _s = _msg.content if _msg.content is string else '' -%}
  77. {%- if '<|think_off|>' in _s -%}
  78. {%- set ns.enable_thinking = false -%}
  79. {%- elif '<|think_on|>' in _s -%}
  80. {%- set ns.enable_thinking = true -%}
  81. {%- endif -%}
  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 == '' -%}
  96. {%- set ns_sys.content = _c -%}
  97. {%- else -%}
  98. {%- set ns_sys.content = ns_sys.content + '\n\n' + _c -%}
  99. {%- endif -%}
  100. {%- endif -%}
  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=[]) -%}
  111. {%- for tool in tools -%}
  112. {%- if tool.function is defined -%}
  113. {%- set ns_tb.list = ns_tb.list + [tool] -%}
  114. {%- else -%}
  115. {%- set ns_tb.list = ns_tb.list + [{"type": "function", "function": tool}] -%}
  116. {%- endif -%}
  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 -%}
  129. {{- ns_sys.content -}}
  130. {%- if _has_tools -%}{{- '\n\n' -}}{%- endif -%}
  131. {%- endif -%}
  132. {%- if _has_tools -%}
  133. {{- '# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>\n' -}}
  134. {%- for tool in ns_tb.list -%}
  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' -}}
  232. {{- '{"name": ' -}}{{- tc.function.name | tojson -}}
  233. {%- if tc.function.arguments is string -%}
  234. {{- ', "arguments": ' + tc.function.arguments -}}
  235. {%- else -%}
  236. {{- ', "arguments": ' -}}{{- tc.function.arguments | tojson -}}
  237. {%- endif -%}
  238. {{- '}' -}}
  239. {%- if not loop.last -%}
  240. {{- '\n</tool_call>\n' -}}
  241. {%- else -%}
  242. {{- '\n</tool_call>' -}}
  243. {%- endif -%}
  244. {%- endfor -%}
  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 -%}
  260. {{- '\n</tool_response>' -}}
  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 -%}
  286. {{- '<think>\n' -}}
  287. {%- else -%}
  288. {{- '<think>\n\n</think>\n\n' -}}
  289. {%- endif -%}
  290. {%- endif -%}
Advertisement
Add Comment
Please, Sign In to add comment