Fix chat template: render assistant tool_calls

#33
Files changed (1) hide show
  1. chat_template.jinja +37 -29
chat_template.jinja CHANGED
@@ -3,8 +3,8 @@
3
 
4
  {%- macro format_arg_value(arg_value) -%}
5
  {%- if arg_value is string -%}
6
- {{- "'" + arg_value + "'" -}}
7
- {%- elif arg_value is mapping -%}
8
  {{- arg_value | tojson -}}
9
  {%- else -%}
10
  {{- arg_value | string -}}
@@ -14,15 +14,19 @@
14
  {%- macro parse_content(content) -%}
15
  {%- if content is string -%}
16
  {{- content -}}
17
- {%- else -%}
 
 
18
  {%- set _ns = namespace(result="") -%}
19
  {%- for item in content -%}
20
- {%- if item["type"] == "image" -%}
 
 
21
  {%- set _ns.result = _ns.result + "<image>" -%}
22
- {%- elif item["type"] == "text" -%}
23
- {%- set _ns.result = _ns.result + item["text"] -%}
24
  {%- else -%}
25
- {%- set _ns.result = _ns.result + item | tojson -%}
26
  {%- endif -%}
27
  {%- endfor -%}
28
  {{- _ns.result -}}
@@ -32,19 +36,24 @@
32
  {%- macro render_tool_calls(tool_calls) -%}
33
  {%- set tool_calls_ns = namespace(tool_calls=[]) -%}
34
  {%- for tool_call in tool_calls -%}
35
- {%- set func_name = tool_call["function"]["name"] -%}
36
- {%- set func_args = tool_call["function"]["arguments"] -%}
 
37
  {%- set args_ns = namespace(arg_strings=[]) -%}
38
- {%- for arg_name, arg_value in func_args.items() -%}
39
- {%- set args_ns.arg_strings = args_ns.arg_strings + [arg_name + "=" + format_arg_value(arg_value)] -%}
40
- {%- endfor -%}
 
 
 
 
41
  {%- set tool_calls_ns.tool_calls = tool_calls_ns.tool_calls + [func_name + "(" + (args_ns.arg_strings | join(", ")) + ")"] -%}
42
  {%- endfor -%}
43
  {{- "<|tool_call_start|>[" + (tool_calls_ns.tool_calls | join(", ")) + "]<|tool_call_end|>" -}}
44
  {%- endmacro -%}
45
 
46
  {%- set ns = namespace(system_prompt="", last_user_index=-1) -%}
47
- {%- if messages[0]["role"] == "system" -%}
48
  {%- if messages[0].get("content") -%}
49
  {%- set ns.system_prompt = parse_content(messages[0]["content"]) -%}
50
  {%- endif -%}
@@ -75,29 +84,28 @@
75
  {{- "<|im_start|>" + message.role + "\n" -}}
76
  {%- if message.role == "assistant" -%}
77
  {%- generation -%}
 
78
  {%- set thinking = message.thinking or message.reasoning or message.reasoning_content -%}
79
  {%- set thinking = thinking if thinking is string else "" -%}
80
- {%- if thinking and (preserve_thinking or loop.index0 > ns.last_user_index) -%}
81
  {{- "<think>" + thinking + "</think>" -}}
82
  {%- endif -%}
83
  {%- set _cfm_tag = "CONTINUE_FINAL_MESSAGE_TAG " -%}
84
  {%- set _has_cfm = false -%}
85
- {%- if message.content is defined -%}
 
86
  {%- set content = parse_content(message.content) -%}
87
- {%- if not (preserve_thinking or loop.index0 > ns.last_user_index) -%}
88
- {%- if "</think>" in content -%}
89
- {%- set content = content.split("</think>")[-1] | trim -%}
90
- {%- endif -%}
91
- {%- endif -%}
92
- {%- if message.tool_calls is defined and content.endswith(_cfm_tag) -%}
93
- {%- set _has_cfm = true -%}
94
- {%- set _trunc_len = (content | length) - (_cfm_tag | length) -%}
95
- {{- content[:_trunc_len] -}}
96
- {%- else -%}
97
- {{- content -}}
98
- {%- endif -%}
99
  {%- endif -%}
100
- {%- if message.tool_calls is defined -%}
 
 
 
 
 
 
 
 
 
101
  {{- render_tool_calls(message.tool_calls) -}}
102
  {%- endif -%}
103
  {%- if _has_cfm -%}
@@ -114,4 +122,4 @@
114
  {%- endfor -%}
115
  {%- if add_generation_prompt -%}
116
  {{- "<|im_start|>assistant\n" -}}
117
- {%- endif -%}
 
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 -}}
 
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 -}}
 
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_user_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 -%}
 
84
  {{- "<|im_start|>" + message.role + "\n" -}}
85
  {%- if message.role == "assistant" -%}
86
  {%- generation -%}
87
+ {%- set keep_thinking = preserve_thinking or loop.index0 > ns.last_user_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 -%}
 
122
  {%- endfor -%}
123
  {%- if add_generation_prompt -%}
124
  {{- "<|im_start|>assistant\n" -}}
125
+ {%- endif -%}