Auto-detect GLM-5.3 chat templates as glm45/glm47 parsers (#38297)

This commit is contained in:
Xinyuan Tong
2026-09-11 09:02:44 -04:00
committed by GitHub
parent 358c163250
commit e8a36d339c
2 changed files with 77 additions and 4 deletions
@@ -38,6 +38,26 @@ def _patch_hf_transformers_utils(get_tokenizer, get_config=None):
return patch.dict(sys.modules, {module.__name__: module})
def _glm53_template(concat):
"""GLM-5.3 shape: always-on thinking behind a ``Reasoning Effort:`` header
(no ``enable_thinking`` toggle) and the compact GLM-4.7 tool-call format;
HF revisions differ only in the ``+`` / ``~`` concat operator."""
return (
"[gMASK]<sop>\n"
"{%- set effective_reasoning_effort = reasoning_effort if reasoning_effort is defined "
"and reasoning_effort in ['low', 'high'] else 'max' -%}\n"
"<|system|>Reasoning Effort: {{ effective_reasoning_effort | capitalize }}\n"
"{% for tc in m.tool_calls %}\n"
f"{{{{- '<tool_call>' {concat} tc.name -}}}}\n"
"{% set _args = tc.arguments %}"
"{% for k, v in _args.items() %}"
"<arg_key>{{ k }}</arg_key><arg_value>{{ v }}</arg_value>"
"{% endfor %}</tool_call>\n"
"{% endfor %}\n"
"<|assistant|>{{- '<think>' -}}"
)
class TestTemplateManagerReasoningDetection(unittest.TestCase):
def _detect(self, template, vocab):
force, config = detect_reasoning_pattern(template)
@@ -79,6 +99,23 @@ class TestTemplateManagerReasoningDetection(unittest.TestCase):
)
self.assertEqual(parser, "glm45")
def test_glm53_effort_template_resolves_glm_parsers(self):
# Without an enable_thinking toggle the GLM-4.5 rule misses, and the
# template used to fall through to deepseek-r1 + the xml_kv fallback
# (glm45 tool parser), which cannot read the compact tool-call format.
vocab = ["<tool_call>", "<arg_key>", "<arg_value>", "<|user|>", "<|endoftext|>"]
for concat in ("+", "~"):
template = _glm53_template(concat)
with self.subTest(concat=concat):
force, config, parser = self._detect(template, vocab)
self.assertEqual(parser, "glm45")
self.assertEqual(
detect_tool_call_parser(
template, _DummyTokenizer(vocab), config, force
),
"glm47",
)
def test_interns1_detects_enable_thinking_default_true(self):
template = """
{% set default_thinking_sys %}...<think>...</think>{% endset %}
@@ -716,6 +753,22 @@ class TestToolCallParserDetection(unittest.TestCase):
["<tool_call>", "<arg_key>", "<arg_value>", "<|endoftext|>"],
"glm47",
),
(
"glm47_tilde_concat_tool_call",
(
"[gMASK]<sop>\n"
"{% set enable_thinking = enable_thinking if enable_thinking is defined else true %}\n"
"{% for tc in m.tool_calls %}\n"
"{{- '<tool_call>' ~ tc.name -}}\n"
"{% set _args = tc.arguments %}"
"{% for k, v in _args.items() %}"
"<arg_key>{{ k }}</arg_key><arg_value>{{ v }}</arg_value>"
"{% endfor %}</tool_call>\n"
"{% endfor %}"
),
["<tool_call>", "<arg_key>", "<arg_value>", "<|endoftext|>"],
"glm47",
),
(
"glm45_newline_tool_call",
(
@@ -760,6 +813,9 @@ class TestToolCallParserDetection(unittest.TestCase):
rule.name: i for i, rule in enumerate(REASONING_PARSER_RULES)
}
self.assertLess(reasoning_index["deepseek_v4"], reasoning_index["deepseek_v3"])
self.assertLess(
reasoning_index["glm45"], reasoning_index["deepseek_r1_think_tags"]
)
self.assertLess(
reasoning_index["hunyuan"], reasoning_index["deepseek_r1_think_tags"]
)