[auto-detect] match Ring-2.6/Ling XML kv tool-call format via vocab signature (#25366)
This commit is contained in:
@@ -199,6 +199,16 @@ def _is_glm45(ctx):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_xml_kv_tool_call(ctx):
|
||||||
|
# Structural signature for the GLM-4.5 / GLM-4.6 style tool-call format
|
||||||
|
# (`<tool_call>name<arg_key>k</arg_key>\n<arg_value>v</arg_value>...</tool_call>`).
|
||||||
|
# Matches any model whose tokenizer carries `<arg_key>` and `<arg_value>` as
|
||||||
|
# added tokens — e.g., inclusionAI/Ring-2.6, which borrows GLM's tool-call
|
||||||
|
# format but doesn't share the `[gMASK]<sop>` / `enable_thinking` family
|
||||||
|
# signature checked by `_is_glm45`.
|
||||||
|
return ctx.has_vocab("<arg_key>") and ctx.has_vocab("<arg_value>")
|
||||||
|
|
||||||
|
|
||||||
def _is_mimo(ctx):
|
def _is_mimo(ctx):
|
||||||
return ctx.reasoning_config == ReasoningToggleConfig(
|
return ctx.reasoning_config == ReasoningToggleConfig(
|
||||||
toggle_param="enable_thinking", default_enabled=False
|
toggle_param="enable_thinking", default_enabled=False
|
||||||
@@ -268,6 +278,9 @@ TOOL_CALL_PARSER_RULES = (
|
|||||||
DetectionRule(name="interns1", value="interns1", predicate=_is_interns1),
|
DetectionRule(name="interns1", value="interns1", predicate=_is_interns1),
|
||||||
DetectionRule(name="mistral", value="mistral", predicate=_is_mistral),
|
DetectionRule(name="mistral", value="mistral", predicate=_is_mistral),
|
||||||
DetectionRule(name="glm45", value="glm45", predicate=_is_glm45),
|
DetectionRule(name="glm45", value="glm45", predicate=_is_glm45),
|
||||||
|
DetectionRule(
|
||||||
|
name="xml_kv_tool_call", value="glm45", predicate=_is_xml_kv_tool_call
|
||||||
|
),
|
||||||
DetectionRule(name="mimo", value="mimo", predicate=_is_mimo),
|
DetectionRule(name="mimo", value="mimo", predicate=_is_mimo),
|
||||||
DetectionRule(name="qwen", value="qwen", predicate=_is_qwen3),
|
DetectionRule(name="qwen", value="qwen", predicate=_is_qwen3),
|
||||||
DetectionRule(name="deepseek_v3", value="deepseekv3", predicate=_is_deepseek_v3),
|
DetectionRule(name="deepseek_v3", value="deepseekv3", predicate=_is_deepseek_v3),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import unittest
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from sglang.srt.managers.template_detection import (
|
from sglang.srt.managers.template_detection import (
|
||||||
|
TOOL_CALL_PARSER_RULES,
|
||||||
ReasoningToggleConfig,
|
ReasoningToggleConfig,
|
||||||
detect_reasoning_parser,
|
detect_reasoning_parser,
|
||||||
detect_reasoning_pattern,
|
detect_reasoning_pattern,
|
||||||
@@ -265,6 +266,12 @@ class TestToolCallParserDetection(unittest.TestCase):
|
|||||||
["<|tool_calls_section_begin|>"],
|
["<|tool_calls_section_begin|>"],
|
||||||
"kimi_k2",
|
"kimi_k2",
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"xml_kv_tool_call_via_vocab",
|
||||||
|
"{% set reasoning_effort = reasoning_effort | default('high', true) %}\n<think>",
|
||||||
|
["<tool_call>", "<arg_key>", "<arg_value>", "<|endoftext|>"],
|
||||||
|
"glm45",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
for name, template, vocab, expected in cases:
|
for name, template, vocab, expected in cases:
|
||||||
with self.subTest(name=name):
|
with self.subTest(name=name):
|
||||||
@@ -274,6 +281,23 @@ class TestToolCallParserDetection(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_glm45_rule_precedes_xml_kv_fallback(self):
|
||||||
|
# The specific GLM-4.5 family check must run before the generic
|
||||||
|
# xml_kv_tool_call fallback. Both currently map to "glm45", so the
|
||||||
|
# value-based test above can't catch a swap — assert positions directly.
|
||||||
|
rule_index = {rule.name: i for i, rule in enumerate(TOOL_CALL_PARSER_RULES)}
|
||||||
|
self.assertLess(rule_index["glm45"], rule_index["xml_kv_tool_call"])
|
||||||
|
|
||||||
|
def test_xml_kv_requires_both_arg_tokens(self):
|
||||||
|
template = "Hello {{ user }}"
|
||||||
|
force, config = detect_reasoning_pattern(template)
|
||||||
|
for vocab in (["<arg_key>"], ["<arg_value>"], []):
|
||||||
|
with self.subTest(vocab=vocab):
|
||||||
|
result = detect_tool_call_parser(
|
||||||
|
template, _DummyTokenizer(vocab), config, force
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_none_template_returns_none(self):
|
def test_none_template_returns_none(self):
|
||||||
self.assertIsNone(detect_tool_call_parser(None, None))
|
self.assertIsNone(detect_tool_call_parser(None, None))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user