Auto-detect GLM-5.3 chat templates as glm45/glm47 parsers (#38297)
This commit is contained in:
@@ -336,9 +336,26 @@ def _is_glm45(ctx):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_glm53(ctx):
|
||||||
|
# GLM-5.3 keeps the GLM-4.5 prompt and tool-call format but replaces the
|
||||||
|
# enable_thinking toggle with an always-on "Reasoning Effort:" header.
|
||||||
|
return (
|
||||||
|
ctx.has_text("[gMASK]<sop>")
|
||||||
|
and ctx.has_text("Reasoning Effort:")
|
||||||
|
and not ctx.has_text("enable_thinking")
|
||||||
|
and ctx.has_text("<tool_call>")
|
||||||
|
and ctx.has_text("<arg_key>")
|
||||||
|
and ctx.has_text("<arg_value>")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_glm_family(ctx):
|
||||||
|
return _is_glm45(ctx) or _is_glm53(ctx)
|
||||||
|
|
||||||
|
|
||||||
def _is_glm47(ctx):
|
def _is_glm47(ctx):
|
||||||
return _is_glm45(ctx) and ctx.has_pattern(
|
return _is_glm_family(ctx) and ctx.has_pattern(
|
||||||
r"\{\{[-\s]*['\"]<tool_call>['\"]\s*\+\s*tc\.name"
|
r"\{\{[-\s]*['\"]<tool_call>['\"]\s*[+~]\s*tc\.name"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -486,7 +503,7 @@ REASONING_PARSER_RULES = (
|
|||||||
DetectionRule(name="gpt_oss", value="gpt-oss", predicate=_is_gpt_oss),
|
DetectionRule(name="gpt_oss", value="gpt-oss", predicate=_is_gpt_oss),
|
||||||
DetectionRule(name="kimi_k2", value="kimi_k2", predicate=_is_kimi_k2),
|
DetectionRule(name="kimi_k2", value="kimi_k2", predicate=_is_kimi_k2),
|
||||||
DetectionRule(name="nemotron_3", value="nemotron_3", predicate=_is_nemotron_3),
|
DetectionRule(name="nemotron_3", value="nemotron_3", predicate=_is_nemotron_3),
|
||||||
DetectionRule(name="glm45", value="glm45", predicate=_is_glm45),
|
DetectionRule(name="glm45", value="glm45", predicate=_is_glm_family),
|
||||||
DetectionRule(name="hunyuan", value="hunyuan", predicate=_is_hunyuan),
|
DetectionRule(name="hunyuan", value="hunyuan", predicate=_is_hunyuan),
|
||||||
DetectionRule(name="poolside_v1", value="poolside_v1", predicate=_is_poolside_v1),
|
DetectionRule(name="poolside_v1", value="poolside_v1", predicate=_is_poolside_v1),
|
||||||
DetectionRule(name="mimo", value="mimo", predicate=_is_mimo),
|
DetectionRule(name="mimo", value="mimo", predicate=_is_mimo),
|
||||||
@@ -526,7 +543,7 @@ TOOL_CALL_PARSER_RULES = (
|
|||||||
DetectionRule(name="deepseek_v31", value="deepseekv31", predicate=_is_deepseek_v31),
|
DetectionRule(name="deepseek_v31", value="deepseekv31", predicate=_is_deepseek_v31),
|
||||||
DetectionRule(name="lfm2", value="lfm2", predicate=_is_lfm2),
|
DetectionRule(name="lfm2", value="lfm2", predicate=_is_lfm2),
|
||||||
DetectionRule(name="glm47", value="glm47", predicate=_is_glm47),
|
DetectionRule(name="glm47", value="glm47", predicate=_is_glm47),
|
||||||
DetectionRule(name="glm45", value="glm45", predicate=_is_glm45),
|
DetectionRule(name="glm45", value="glm45", predicate=_is_glm_family),
|
||||||
DetectionRule(name="minicpm5", value="minicpm5", predicate=_is_minicpm5),
|
DetectionRule(name="minicpm5", value="minicpm5", predicate=_is_minicpm5),
|
||||||
DetectionRule(name="hunyuan", value="hunyuan", predicate=_is_hunyuan),
|
DetectionRule(name="hunyuan", value="hunyuan", predicate=_is_hunyuan),
|
||||||
DetectionRule(name="poolside_v1", value="poolside_v1", predicate=_is_poolside_v1),
|
DetectionRule(name="poolside_v1", value="poolside_v1", predicate=_is_poolside_v1),
|
||||||
|
|||||||
@@ -38,6 +38,26 @@ def _patch_hf_transformers_utils(get_tokenizer, get_config=None):
|
|||||||
return patch.dict(sys.modules, {module.__name__: module})
|
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):
|
class TestTemplateManagerReasoningDetection(unittest.TestCase):
|
||||||
def _detect(self, template, vocab):
|
def _detect(self, template, vocab):
|
||||||
force, config = detect_reasoning_pattern(template)
|
force, config = detect_reasoning_pattern(template)
|
||||||
@@ -79,6 +99,23 @@ class TestTemplateManagerReasoningDetection(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(parser, "glm45")
|
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):
|
def test_interns1_detects_enable_thinking_default_true(self):
|
||||||
template = """
|
template = """
|
||||||
{% set default_thinking_sys %}...<think>...</think>{% endset %}
|
{% set default_thinking_sys %}...<think>...</think>{% endset %}
|
||||||
@@ -716,6 +753,22 @@ class TestToolCallParserDetection(unittest.TestCase):
|
|||||||
["<tool_call>", "<arg_key>", "<arg_value>", "<|endoftext|>"],
|
["<tool_call>", "<arg_key>", "<arg_value>", "<|endoftext|>"],
|
||||||
"glm47",
|
"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",
|
"glm45_newline_tool_call",
|
||||||
(
|
(
|
||||||
@@ -760,6 +813,9 @@ class TestToolCallParserDetection(unittest.TestCase):
|
|||||||
rule.name: i for i, rule in enumerate(REASONING_PARSER_RULES)
|
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["deepseek_v4"], reasoning_index["deepseek_v3"])
|
||||||
|
self.assertLess(
|
||||||
|
reasoning_index["glm45"], reasoning_index["deepseek_r1_think_tags"]
|
||||||
|
)
|
||||||
self.assertLess(
|
self.assertLess(
|
||||||
reasoning_index["hunyuan"], reasoning_index["deepseek_r1_think_tags"]
|
reasoning_index["hunyuan"], reasoning_index["deepseek_r1_think_tags"]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user