verbose/chat_template.jinja
8.8 KB · 185 lines · plaintext Raw
1 {%- macro tool_to_json(tool) -%}
2 {%- set ns_tool = namespace(first=true) -%}
3 {{- '{' -}}
4 {%- for k, v in tool.items() -%}
5 {%- if k != 'defer_loading' and k != 'strict' -%}
6 {%- if not ns_tool.first -%}{{- ', ' -}}{%- endif -%}
7 {%- set ns_tool.first = false -%}
8 {{- '"' ~ k ~ '": ' ~ (v | tojson(ensure_ascii=False)) -}}
9 {%- endif -%}
10 {%- endfor -%}
11 {{- '}' -}}
12 {%- endmacro -%}
13 {%- set enable_thinking = enable_thinking if enable_thinking is defined else True %}
14 {%- set low_effort = low_effort if low_effort is defined else False %}
15 {%- if reasoning_effort is defined and reasoning_effort is not none %}
16 {%- set low_effort = reasoning_effort == "low" %}
17 {%- endif %}
18 {%- set truncate_history_thinking = truncate_history_thinking if truncate_history_thinking is defined else True %}
19
20 {%- set ns = namespace(last_user_idx = -1) %}
21 {%- set loop_messages = messages %}
22 {%- for m in loop_messages %}
23 {%- if m["role"] == "user" %}
24 {%- set ns.last_user_idx = loop.index0 %}
25 {%- endif %}
26 {%- endfor %}
27
28 {%- if messages[0]["role"] == "system" %}
29 {%- set system_message = messages[0]["content"] %}
30 {%- set loop_messages = messages[1:] %}
31 {%- else %}
32 {%- set system_message = "" %}
33 {%- set loop_messages = messages %}
34 {%- endif %}
35 {%- if not tools is defined %}
36 {%- set tools = [] %}
37 {%- endif %}
38 {# Recompute last_user_idx relative to loop_messages after handling system #}
39 {%- set ns = namespace(last_user_idx = -1) %}
40 {%- for m in loop_messages %}
41 {%- if m["role"] == "user" %}
42 {%- set ns.last_user_idx = loop.index0 %}
43 {%- endif %}
44 {%- endfor %}
45 {%- if system_message is defined %}
46 {{- "<|im_start|>system\n" + system_message }}
47 {%- else %}
48 {%- if tools is iterable and tools | length > 0 %}
49 {{- "<|im_start|>system\n" }}
50 {%- endif %}
51 {%- endif %}
52 {%- if tools is iterable and tools | length > 0 %}
53 {%- if system_message is defined and system_message | length > 0 %}
54 {{- "\n\n" }}
55 {%- endif %}
56 {{- "# Tools\n\nYou have access to the following functions:\n\n" }}
57 {{- "<tools>" }}
58 {%- for tool in tools %}
59 {%- if tool.function is defined %}
60 {%- set tool = tool.function %}
61 {%- endif %}
62 {%- if tool.defer_loading is not defined or not tool.defer_loading %}
63 {{- '\n' ~ tool_to_json(tool) }}
64 {%- endif %}
65 {%- endfor %}
66 {{- "\n</tools>" }}
67
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 {%- endif %}
70
71
72 {%- if system_message is defined %}
73 {{- '<|im_end|>\n' }}
74 {%- else %}
75 {%- if tools is iterable and tools | length > 0 %}
76 {{- '<|im_end|>\n' }}
77 {%- endif %}
78 {%- endif %}
79
80 {%- for message in loop_messages %}
81 {%- if message.role == "assistant" %}
82 {# Add reasoning content in to content field for unified processing below. #}
83 {%- if message.reasoning_content is defined and message.reasoning_content is string and message.reasoning_content | trim | length > 0 %}
84 {%- set content = "<think>\n" ~ message.reasoning_content ~ "\n</think>\n" ~ (message.content | default('', true)) %}
85 {%- else %}
86 {%- set content = message.content | default('', true) %}
87 {%- if content is string -%}
88 {# Allow downstream logic to to take care of broken thought, only handle coherent reasoning here. #}
89 {%- if '<think>' not in content and '</think>' not in content -%}
90 {%- set content = "<think></think>" ~ content -%}
91 {%- endif -%}
92 {%- else -%}
93 {%- set content = content -%}
94 {%- endif -%}
95 {%- endif %}
96 {%- if message.tool_calls is defined and message.tool_calls is iterable and message.tool_calls | length > 0 %}
97 {# Assistant message has tool calls. #}
98 {{- '<|im_start|>assistant\n' }}
99 {%- set include_content = not (truncate_history_thinking and loop.index0 < ns.last_user_idx) %}
100 {%- if content is string and content | trim | length > 0 %}
101 {%- if include_content %}
102 {{- (content | trim) ~ '\n' -}}
103 {%- else %}
104 {%- set c = (content | string) %}
105 {%- if '</think>' in c %}
106 {# Keep only content after the last closing think. Also generation prompt causes this. #}
107 {%- set c = c.split('</think>')[-1] %}
108 {%- elif '<think>' in c %}
109 {# If <think> was opened but never closed, drop the trailing think segment #}
110 {%- set c = c.split('<think>')[0] %}
111 {%- endif %}
112 {%- set c = "<think></think>" ~ c | trim %}
113 {%- if c | length > 0 %}
114 {{- c ~ '\n' -}}
115 {%- endif %}
116 {%- endif %}
117 {%- else %}
118 {{- "<think></think>" -}}
119 {%- endif %}
120 {%- for tool_call in message.tool_calls %}
121 {%- if tool_call.function is defined %}
122 {%- set tool_call = tool_call.function %}
123 {%- endif %}
124 {{- '<tool_call>\n<function=' ~ tool_call.name ~ '>\n' -}}
125 {%- if tool_call.arguments is defined %}
126 {%- for args_name, args_value in tool_call.arguments|items %}
127 {{- '<parameter=' ~ args_name ~ '>\n' -}}
128 {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
129 {{- args_value ~ '\n</parameter>\n' -}}
130 {%- endfor %}
131 {%- endif %}
132 {{- '</function>\n</tool_call>\n' -}}
133 {%- endfor %}
134 {{- '<|im_end|>\n' }}
135 {%- else %}
136 {# Assistant message doesn't have tool calls. #}
137 {%- if not (truncate_history_thinking and loop.index0 < ns.last_user_idx) %}
138 {{- '<|im_start|>assistant\n' ~ (content | default('', true) | string | trim) ~ '<|im_end|>\n' }}
139 {%- else %}
140 {%- set c = (content | default('', true) | string) %}
141 {%- if '<think>' in c and '</think>' in c %}
142 {%- set c = "<think></think>" ~ c.split('</think>')[-1] %}
143 {%- endif %}
144 {%- set c = c | trim %}
145 {%- if c | length > 0 %}
146 {{- '<|im_start|>assistant\n' ~ c ~ '<|im_end|>\n' }}
147 {%- else %}
148 {{- '<|im_start|>assistant\n<|im_end|>\n' }}
149 {%- endif %}
150 {%- endif %}
151 {%- endif %}
152 {%- elif message.role == "user" or message.role == "system" %}
153 {{- '<|im_start|>' + message.role + '\n' }}
154 {%- set content = message.content | string %}
155 {%- if message.role == "user" and loop.index0 == ns.last_user_idx and low_effort %}
156 {{- content + '\n\n{reasoning effort: low}' }}
157 {%- else %}
158 {{- content }}
159 {%- endif %}
160 {{- '<|im_end|>\n' }}
161 {%- elif message.role == "tool" %}
162 {%- if loop.previtem and loop.previtem.role != "tool" %}
163 {{- '<|im_start|>user\n' }}
164 {%- endif %}
165 {{- '<tool_response>\n' }}
166 {{- message.content }}
167 {{- '\n</tool_response>\n' }}
168 {%- if not loop.last and loop.nextitem.role != "tool" %}
169 {{- '<|im_end|>\n' }}
170 {%- elif loop.last %}
171 {{- '<|im_end|>\n' }}
172 {%- endif %}
173 {%- else %}
174 {{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>\n' }}
175 {%- endif %}
176 {%- endfor %}
177
178 {%- if add_generation_prompt %}
179 {%- if enable_thinking %}
180 {{- '<|im_start|>assistant\n<think>\n' }}
181 {%- else %}
182 {{- '<|im_start|>assistant\n<think></think>' }}
183 {%- endif %}
184 {%- endif %}
185