Skip to content

Commit 35ce422

Browse files
committed
Update chat_templates.py
1 parent 49b053c commit 35ce422

File tree

1 file changed

+63
-28
lines changed

1 file changed

+63
-28
lines changed

unsloth/chat_templates.py

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,11 +1380,12 @@
13801380
{%- set tool = tool.function %}
13811381
{{- "// " + tool.description + "\n" }}
13821382
{{- "type "+ tool.name + " = " }}
1383-
{%- if tool.parameters and tool.parameters.properties -%}
1384-
{{- "(_: " }}
1385-
{{- "{\n" }}
1383+
{%- if tool.parameters and tool.parameters.properties %}
1384+
{{- "(_: {\n" }}
13861385
{%- for param_name, param_spec in tool.parameters.properties.items() %}
1387-
{{- "// " + param_spec.description + "\n" }}
1386+
{%- if param_spec.description %}
1387+
{{- "// " + param_spec.description + "\n" }}
1388+
{%- endif %}
13881389
{{- param_name }}
13891390
{%- if param_name not in (tool.parameters.required or []) -%}
13901391
{{- "?" }}
@@ -1403,7 +1404,7 @@
14031404
{%- if not loop.last %}
14041405
{{- ",\n" }}
14051406
{%- else %}
1406-
{{- "\n" }}
1407+
{{- ",\n" }}
14071408
{%- endif -%}
14081409
{%- endfor %}
14091410
{{- "}) => any;\n\n" }}
@@ -1462,17 +1463,16 @@
14621463
{#- System Message Construction ============================================ #}
14631464
{%- macro build_system_message() -%}
14641465
{%- if model_identity is not defined %}
1465-
{{- "You are ChatGPT, a large language model trained by OpenAI.\n" -}}
1466-
{%- else %}
1467-
{{- model_identity }}
1466+
{%- set model_identity = "You are ChatGPT, a large language model trained by OpenAI." %}
14681467
{%- endif %}
1468+
{{- model_identity + "\n" }}
14691469
{{- "Knowledge cutoff: 2024-06\n" }}
14701470
{{- "Current date: " + strftime_now("%Y-%m-%d") + "\n\n" }}
14711471
{%- if reasoning_effort is not defined %}
14721472
{%- set reasoning_effort = "medium" %}
14731473
{%- endif %}
14741474
{{- "Reasoning: " + reasoning_effort + "\n\n" }}
1475-
{%- if builtin_tools is defined %}
1475+
{%- if builtin_tools is defined and builtin_tools is not none %}
14761476
{{- "# Tools\n\n" }}
14771477
{%- set available_builtin_tools = namespace(browser=false, python=false) %}
14781478
{%- for tool in builtin_tools %}
@@ -1485,7 +1485,7 @@
14851485
{{- render_builtin_tools(available_builtin_tools.browser, available_builtin_tools.python) }}
14861486
{%- endif -%}
14871487
{{- "# Valid channels: analysis, commentary, final. Channel must be included for every message." }}
1488-
{%- if tools is defined -%}
1488+
{%- if tools -%}
14891489
{{- "\nCalls to these tools must go to the commentary channel: 'functions'." }}
14901490
{%- endif -%}
14911491
{%- endmacro -%}
@@ -1499,7 +1499,10 @@
14991499
{{- "<|end|>" }}
15001500
15011501
{#- Extract developer message #}
1502-
{%- if messages[0].role == "developer" or messages[0].role == "system" %}
1502+
{%- if developer_instructions is defined and developer_instructions is not none %}
1503+
{%- set developer_message = developer_instructions %}
1504+
{%- set loop_messages = messages %}
1505+
{%- elif messages[0].role == "developer" or messages[0].role == "system" %}
15031506
{%- set developer_message = messages[0].content %}
15041507
{%- set loop_messages = messages[1:] %}
15051508
{%- else %}
@@ -1515,7 +1518,9 @@
15151518
{{- developer_message }}
15161519
{%- endif %}
15171520
{%- if tools -%}
1518-
{{- "\n\n" }}
1521+
{%- if developer_message %}
1522+
{{- "\n\n" }}
1523+
{%- endif %}
15191524
{{- "# Tools\n\n" }}
15201525
{{- render_tool_namespace("functions", tools) }}
15211526
{%- endif -%}
@@ -1527,50 +1532,80 @@
15271532
{%- for message in loop_messages -%}
15281533
{#- At this point only assistant/user/tool messages should remain #}
15291534
{%- if message.role == 'assistant' -%}
1535+
{#- Checks to ensure the messages are being passed in the format we expect #}
1536+
{%- if "content" in message %}
1537+
{%- if "<|channel|>analysis<|message|>" in message.content or "<|channel|>final<|message|>" in message.content %}
1538+
{{- raise_exception("You have passed a message containing <|channel|> tags in the content field. Instead of doing this, you should pass analysis messages (the string between '<|message|>' and '<|end|>') in the 'thinking' field, and final messages (the string between '<|message|>' and '<|end|>') in the 'content' field.") }}
1539+
{%- endif %}
1540+
{%- endif %}
1541+
{%- if "thinking" in message %}
1542+
{%- if "<|channel|>analysis<|message|>" in message.thinking or "<|channel|>final<|message|>" in message.thinking %}
1543+
{{- raise_exception("You have passed a message containing <|channel|> tags in the thinking field. Instead of doing this, you should pass analysis messages (the string between '<|message|>' and '<|end|>') in the 'thinking' field, and final messages (the string between '<|message|>' and '<|end|>') in the 'content' field.") }}
1544+
{%- endif %}
1545+
{%- endif %}
15301546
{%- if "tool_calls" in message %}
1547+
{#- We need very careful handling here - we want to drop the tool call analysis message if the model #}
1548+
{#- has output a later <|final|> message, but otherwise we want to retain it. This is the only case #}
1549+
{#- when we render CoT/analysis messages in inference. #}
1550+
{%- set future_final_message = namespace(found=false) %}
1551+
{%- for future_message in loop_messages[loop.index:] %}
1552+
{%- if future_message.role == 'assistant' and "tool_calls" not in future_message %}
1553+
{%- set future_final_message.found = true %}
1554+
{%- endif %}
1555+
{%- endfor %}
15311556
{#- We assume max 1 tool call per message, and so we infer the tool call name #}
15321557
{#- in "tool" messages from the most recent assistant tool call name #}
15331558
{%- set tool_call = message.tool_calls[0] %}
15341559
{%- if tool_call.function %}
15351560
{%- set tool_call = tool_call.function %}
15361561
{%- endif %}
1537-
{%- if message.content %}
1562+
{%- if message.content and message.thinking %}
1563+
{{- raise_exception("Cannot pass both content and thinking in an assistant message with tool calls! Put the analysis message in one or the other, but not both.") }}
1564+
{%- elif message.content and not future_final_message.found %}
15381565
{{- "<|start|>assistant<|channel|>analysis<|message|>" + message.content + "<|end|>" }}
1566+
{%- elif message.thinking and not future_final_message.found %}
1567+
{{- "<|start|>assistant<|channel|>analysis<|message|>" + message.thinking + "<|end|>" }}
15391568
{%- endif %}
15401569
{{- "<|start|>assistant to=" }}
1541-
{{- "functions." + tool_call.name + "<|channel|>commentary json<|message|>" }}
1542-
{{- tool_call.arguments|tojson }}
1570+
{{- "functions." + tool_call.name + "<|channel|>commentary " }}
1571+
{{- (tool_call.content_type if tool_call.content_type is defined else "json") + "<|message|>" }}
1572+
{%- if tool_call.arguments is string %}
1573+
{{- tool_call.arguments }}
1574+
{%- else %}
1575+
{{- tool_call.arguments|tojson }}
1576+
{%- endif %}
15431577
{{- "<|call|>" }}
15441578
{%- set last_tool_call.name = tool_call.name %}
1545-
{%- elif "thinking" in message and loop.last and not add_generation_prompt %}
1579+
{%- elif loop.last and not add_generation_prompt %}
15461580
{#- Only render the CoT if the final turn is an assistant turn and add_generation_prompt is false #}
15471581
{#- This is a situation that should only occur in training, never in inference. #}
1548-
{{- "<|start|>assistant<|channel|>analysis<|message|>" + message.thinking + "<|end|>" }}
1582+
{%- if "thinking" in message %}
1583+
{{- "<|start|>assistant<|channel|>analysis<|message|>" + message.thinking + "<|end|>" }}
1584+
{%- endif %}
15491585
{#- <|return|> indicates the end of generation, but <|end|> does not #}
15501586
{#- <|return|> should never be an input to the model, but we include it as the final token #}
15511587
{#- when training, so the model learns to emit it. #}
1552-
{{- "<|start|>assistant<|channel|>final<|message|>" + message.content + "<|return|>" }}
1553-
{%- set last_tool_call.name = none %}
1588+
{{- "<|start|>assistant<|channel|>final<|message|>" + message.content + "<|end|>" }}
15541589
{%- elif "thinking" in message %}
15551590
{#- CoT is dropped during all previous turns, so we never render it for inference #}
1556-
{{- "<|start|>assistant<|channel|>final<|message|>" + message.content + "<|end|>" }}
1591+
{{- "<|start|>assistant<|channel|>analysis<|message|>" + message.content + "<|end|>" }}
15571592
{%- set last_tool_call.name = none %}
1558-
{%- elif loop.last and not add_generation_prompt %}
1559-
{#- <|return|> indicates the end of generation, but <|end|> does not #}
1560-
{#- <|return|> should never be an input to the model, but we include it as the final token #}
1561-
{#- when training, so the model learns to emit it. #}
1562-
{{- "<|start|>assistant<|message|>" + message.content + "<|return|>" }}
15631593
{%- else %}
1564-
{{- "<|start|>assistant<|message|>" + message.content + "<|end|>" }}
1594+
{#- CoT is dropped during all previous turns, so we never render it for inference #}
1595+
{{- "<|start|>assistant<|channel|>final<|message|>" + message.content + "<|end|>" }}
15651596
{%- set last_tool_call.name = none %}
15661597
{%- endif %}
15671598
{%- elif message.role == 'tool' -%}
15681599
{%- if last_tool_call.name is none %}
15691600
{{- raise_exception("Message has tool role, but there was no previous assistant message with a tool call!") }}
15701601
{%- endif %}
15711602
{{- "<|start|>functions." + last_tool_call.name }}
1572-
{{- " to=assistant<|channel|>commentary<|message|>" + message.content|tojson + "<|end|>" }}
1573-
{%- else -%}
1603+
{%- if message.content is string %}
1604+
{{- " to=assistant<|channel|>commentary<|message|>" + message.content + "<|end|>" }}
1605+
{%- else %}
1606+
{{- " to=assistant<|channel|>commentary<|message|>" + message.content|tojson + "<|end|>" }}
1607+
{%- endif %}
1608+
{%- elif message.role == 'user' -%}
15741609
{{- "<|start|>user<|message|>" + message.content + "<|end|>" }}
15751610
{%- endif -%}
15761611
{%- endfor -%}

0 commit comments

Comments
 (0)