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) <noreply@anthropic.com>

* Fix trailing whitespace handling via parse retry for PR #6008

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
Prathamesh Jadhav 2026-06-11 19:38:34 +05:30 committed by GitHub
commit 5a5ecc8fc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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: