Fix chat template: render assistant tool_calls

#12
Files changed (1) hide show
  1. chat_template.jinja +92 -31
chat_template.jinja CHANGED
@@ -1,16 +1,61 @@
1
  {{- bos_token -}}
2
- {%- set keep_past_thinking = keep_past_thinking | default(false) -%}
3
- {%- set ns = namespace(system_prompt="") -%}
4
- {%- if messages[0]["role"] == "system" -%}
5
- {%- set sys_content = messages[0]["content"] -%}
6
- {%- if sys_content is not string -%}
7
- {%- for item in sys_content -%}
8
- {%- if item["type"] == "text" -%}
9
- {%- set ns.system_prompt = ns.system_prompt + item["text"] -%}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  {%- endif -%}
11
  {%- endfor -%}
12
- {%- else -%}
13
- {%- set ns.system_prompt = sys_content -%}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  {%- endif -%}
15
  {%- set messages = messages[1:] -%}
16
  {%- endif -%}
@@ -30,35 +75,51 @@
30
  {%- if ns.system_prompt -%}
31
  {{- "<|im_start|>system\n" + ns.system_prompt + "<|im_end|>\n" -}}
32
  {%- endif -%}
33
- {%- set ns.last_assistant_index = -1 -%}
34
  {%- for message in messages -%}
35
  {%- if message["role"] == "assistant" -%}
36
  {%- set ns.last_assistant_index = loop.index0 -%}
37
  {%- endif -%}
38
  {%- endfor -%}
39
  {%- for message in messages -%}
40
- {{- "<|im_start|>" + message["role"] + "\n" -}}
41
- {%- set content = message["content"] -%}
42
- {%- if content is not string -%}
43
- {%- set ns.content = "" -%}
44
- {%- for item in content -%}
45
- {%- if item["type"] == "image" -%}
46
- {%- set ns.content = ns.content + "<image>" -%}
47
- {%- elif item["type"] == "text" -%}
48
- {%- set ns.content = ns.content + item["text"] -%}
49
- {%- else -%}
50
- {%- set ns.content = ns.content + item | tojson -%}
51
- {%- endif -%}
52
- {%- endfor -%}
53
- {%- set content = ns.content -%}
54
- {%- endif -%}
55
- {%- if message["role"] == "assistant" and not keep_past_thinking and loop.index0 != ns.last_assistant_index -%}
56
- {%- if "</think>" in content -%}
57
  {%- set content = content.split("</think>")[-1] | trim -%}
58
  {%- endif -%}
59
- {%- endif -%}
60
- {{- content + "<|im_end|>\n" -}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  {%- endfor -%}
62
  {%- if add_generation_prompt -%}
63
  {{- "<|im_start|>assistant\n" -}}
64
- {%- endif -%}
 
1
  {{- bos_token -}}
2
+ {%- set preserve_thinking = preserve_thinking | default(keep_past_thinking | default(false)) -%}
3
+
4
+ {%- macro format_arg_value(arg_value) -%}
5
+ {%- if arg_value is string -%}
6
+ {{- "'" + (arg_value | replace("\\", "\\\\") | replace("'", "\\'") | replace("\n", "\\n") | replace("\r", "\\r")) + "'" -}}
7
+ {%- elif arg_value is mapping or arg_value is iterable -%}
8
+ {{- arg_value | tojson -}}
9
+ {%- else -%}
10
+ {{- arg_value | string -}}
11
+ {%- endif -%}
12
+ {%- endmacro -%}
13
+
14
+ {%- macro parse_content(content) -%}
15
+ {%- if content is string -%}
16
+ {{- content -}}
17
+ {%- elif content is mapping -%}
18
+ {{- content | tojson -}}
19
+ {%- elif content is iterable -%}
20
+ {%- set _ns = namespace(result="") -%}
21
+ {%- for item in content -%}
22
+ {%- if item is string -%}
23
+ {%- set _ns.result = _ns.result + item -%}
24
+ {%- elif item is mapping and item.get("type") == "image" -%}
25
+ {%- set _ns.result = _ns.result + "<image>" -%}
26
+ {%- elif item is mapping and item.get("type") == "text" -%}
27
+ {%- set _ns.result = _ns.result + ((item.get("text") or "") | string) -%}
28
+ {%- else -%}
29
+ {%- set _ns.result = _ns.result + (item | tojson) -%}
30
  {%- endif -%}
31
  {%- endfor -%}
32
+ {{- _ns.result -}}
33
+ {%- endif -%}
34
+ {%- endmacro -%}
35
+
36
+ {%- macro render_tool_calls(tool_calls) -%}
37
+ {%- set tool_calls_ns = namespace(tool_calls=[]) -%}
38
+ {%- for tool_call in tool_calls -%}
39
+ {%- set func = tool_call["function"] if "function" in tool_call else tool_call -%}
40
+ {%- set func_name = func["name"] -%}
41
+ {%- set func_args = func.get("arguments") -%}
42
+ {%- set args_ns = namespace(arg_strings=[]) -%}
43
+ {%- if func_args is mapping -%}
44
+ {%- for arg_name, arg_value in func_args.items() -%}
45
+ {%- set args_ns.arg_strings = args_ns.arg_strings + [arg_name + "=" + format_arg_value(arg_value)] -%}
46
+ {%- endfor -%}
47
+ {%- elif func_args is string and (func_args | trim) not in ["", "{}", "null"] -%}
48
+ {{- raise_exception("Tool call arguments must be a mapping, got a JSON-encoded string: parse arguments with json.loads() before applying the chat template") -}}
49
+ {%- endif -%}
50
+ {%- set tool_calls_ns.tool_calls = tool_calls_ns.tool_calls + [func_name + "(" + (args_ns.arg_strings | join(", ")) + ")"] -%}
51
+ {%- endfor -%}
52
+ {{- "<|tool_call_start|>[" + (tool_calls_ns.tool_calls | join(", ")) + "]<|tool_call_end|>" -}}
53
+ {%- endmacro -%}
54
+
55
+ {%- set ns = namespace(system_prompt="", last_assistant_index=-1) -%}
56
+ {%- if messages and messages[0]["role"] == "system" -%}
57
+ {%- if messages[0].get("content") -%}
58
+ {%- set ns.system_prompt = parse_content(messages[0]["content"]) -%}
59
  {%- endif -%}
60
  {%- set messages = messages[1:] -%}
61
  {%- endif -%}
 
75
  {%- if ns.system_prompt -%}
76
  {{- "<|im_start|>system\n" + ns.system_prompt + "<|im_end|>\n" -}}
77
  {%- endif -%}
 
78
  {%- for message in messages -%}
79
  {%- if message["role"] == "assistant" -%}
80
  {%- set ns.last_assistant_index = loop.index0 -%}
81
  {%- endif -%}
82
  {%- endfor -%}
83
  {%- for message in messages -%}
84
+ {{- "<|im_start|>" + message.role + "\n" -}}
85
+ {%- if message.role == "assistant" -%}
86
+ {%- generation -%}
87
+ {%- set keep_thinking = preserve_thinking or loop.index0 == ns.last_assistant_index -%}
88
+ {%- set thinking = message.thinking or message.reasoning or message.reasoning_content -%}
89
+ {%- set thinking = thinking if thinking is string else "" -%}
90
+ {%- if thinking and keep_thinking -%}
91
+ {{- "<think>" + thinking + "</think>" -}}
92
+ {%- endif -%}
93
+ {%- set _cfm_tag = "CONTINUE_FINAL_MESSAGE_TAG " -%}
94
+ {%- set _has_cfm = false -%}
95
+ {%- set content = "" -%}
96
+ {%- if message.get("content") -%}
97
+ {%- set content = parse_content(message.content) -%}
98
+ {%- endif -%}
99
+ {%- if not keep_thinking and "</think>" in content -%}
 
100
  {%- set content = content.split("</think>")[-1] | trim -%}
101
  {%- endif -%}
102
+ {%- if content.endswith(_cfm_tag) -%}
103
+ {%- set _has_cfm = true -%}
104
+ {%- set _trunc_len = (content | length) - (_cfm_tag | length) -%}
105
+ {%- set content = content[:_trunc_len] -%}
106
+ {%- endif -%}
107
+ {{- content -}}
108
+ {%- if message.tool_calls -%}
109
+ {{- render_tool_calls(message.tool_calls) -}}
110
+ {%- endif -%}
111
+ {%- if _has_cfm -%}
112
+ {{- _cfm_tag -}}
113
+ {%- endif -%}
114
+ {{- "<|im_end|>\n" -}}
115
+ {%- endgeneration -%}
116
+ {%- else %}
117
+ {%- if message.get("content") -%}
118
+ {{- parse_content(message["content"]) -}}
119
+ {%- endif -%}
120
+ {{- "<|im_end|>\n" -}}
121
+ {%- endif %}
122
  {%- endfor -%}
123
  {%- if add_generation_prompt -%}
124
  {{- "<|im_start|>assistant\n" -}}
125
+ {%- endif -%}