From 5a5ecc8fc0ed3c0199b0ac39ea57b65d944096fb Mon Sep 17 00:00:00 2001 From: Prathamesh Jadhav <55660103+lollinng@users.noreply.github.com> Date: Thu, 11 Jun 2026 19:38:34 +0530 Subject: [PATCH] Strip trailing whitespace in construct_chat_template (fixes #992) (#6008) * Strip trailing whitespace in construct_chat_template (fixes #992) construct_chat_template() only lstrip()s the template, so a template ending in a newline (e.g. the default Llama-3 template + a trailing '\n') keeps the trailing whitespace. That breaks the rfind-based repeated-example detection and, on current main, also the regex fallback, which then raises "Could not recover a two-example structure from chat_template". Surrounding whitespace is not significant to the template here (the left side is already stripped), so strip() both ends. Verified that the valid no-newline template still parses via the primary path and that the meaningful trailing token (e.g. <|eot_id|>) is preserved. Co-Authored-By: Claude Opus 4.8 (1M context) * Fix trailing whitespace handling via parse retry for PR #6008 --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Daniel Han --- unsloth/chat_templates.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/unsloth/chat_templates.py b/unsloth/chat_templates.py index b96f8cb7a0..fe078c5025 100644 --- a/unsloth/chat_templates.py +++ b/unsloth/chat_templates.py @@ -2341,13 +2341,15 @@ extra_eos_tokens = None, You must use {INPUT}, {OUTPUT} twice, and {SYSTEM} is optional. """ - # Strip only the left + # Strip only the left: trailing whitespace can be part of the repeated example + # (e.g. "{OUTPUT}\n"). Accidental trailing whitespace (#992) is retried on failure. chat_template = chat_template.lstrip() assert(tokenizer is not None) if extra_eos_tokens is None: extra_eos_tokens = [] elif type(extra_eos_tokens) is str: extra_eos_tokens = [extra_eos_tokens,] + original_extra_eos_tokens = list(extra_eos_tokens) vocab = tokenizer.get_vocab() for extra_eos in extra_eos_tokens: @@ -2454,6 +2456,20 @@ extra_eos_tokens = None, f"{left_changed}" ) except: + # Accidental trailing whitespace (#992) desyncs the two-example detection, + # so retry once without it. Templates that parse as-is are never altered. + rstripped_chat_template = chat_template.rstrip() + if rstripped_chat_template != chat_template: + try: + return construct_chat_template( + tokenizer = tokenizer, + chat_template = rstripped_chat_template, + default_system_message = default_system_message, + extra_eos_tokens = original_extra_eos_tokens, + ) + except Exception: + pass + output_pos = chat_template.find("{OUTPUT}") input_pos = chat_template.find("{INPUT}") if output_pos == -1 or input_pos == -1: