[Model] Support Ling-3.0-flash (BailingMoeV3) (#33561)

Signed-off-by: JustinTong <justintong0323@gmail.com>
Signed-off-by: Xinyuan Tong <xinyuantong.cs@gmail.com>
Co-authored-by: luoyuan.luo <luoyuan.luo@antgroup.com>
Co-authored-by: 得泽 <zhangkaihong.zkh@antgroup.com>
Co-authored-by: 翎悦 <vito.yy@antgroup.com>
Co-authored-by: 羽癫 <yudian.zy@antgroup.com>
Co-authored-by: tiwei.btw <tiwei.btw@antgroup.com>
Co-authored-by: Liangsheng Yin <hnyls2002@gmail.com>
Co-authored-by: 文赋 <zibin.zb@antgroup.com>
Co-authored-by: JustinTong <justintong0323@gmail.com>
This commit is contained in:
Xinyuan Tong
2026-08-26 17:27:23 -07:00
committed by GitHub
co-authored by luoyuan.luo 得泽 翎悦 羽癫 tiwei.btw Liangsheng Yin 文赋 JustinTong
parent 8739d56a31
commit 20621aa14b
76 changed files with 5184 additions and 315 deletions
@@ -28,6 +28,7 @@ from sglang.srt.function_call.inkling_detector import InklingDetector
from sglang.srt.function_call.json_array_parser import JsonArrayParser
from sglang.srt.function_call.kimik2_detector import KimiK2Detector
from sglang.srt.function_call.lfm2_detector import Lfm2Detector
from sglang.srt.function_call.ling3_detector import Ling3Detector
from sglang.srt.function_call.llama32_detector import Llama32Detector
from sglang.srt.function_call.mistral_detector import MistralDetector
from sglang.srt.function_call.pythonic_detector import PythonicDetector
@@ -2944,6 +2945,84 @@ class TestGlm4MoeDetector(unittest.TestCase):
)
self.assertEqual(result.normal_text, "")
def test_streaming_tool_call(self):
chunks = [
"<tool_call>get_weather\n",
"<arg_key>city</arg_key>\n<arg_value>Beijing</arg_value>\n",
"<arg_key>date</arg_key>\n<arg_value>2024-06-27</arg_value>\n",
"</tool_call>",
]
tool_calls = []
for chunk in chunks:
result = self.detector.parse_streaming_increment(chunk, self.tools)
for tool_call_chunk in result.calls:
if (
hasattr(tool_call_chunk, "tool_index")
and tool_call_chunk.tool_index is not None
):
while len(tool_calls) <= tool_call_chunk.tool_index:
tool_calls.append({"name": "", "parameters": ""})
tc = tool_calls[tool_call_chunk.tool_index]
if tool_call_chunk.name:
tc["name"] = tool_call_chunk.name
if tool_call_chunk.parameters:
tc["parameters"] += tool_call_chunk.parameters
self.assertEqual(len(tool_calls), 1)
self.assertEqual(tool_calls[0]["name"], "get_weather")
self.assertEqual(
tool_calls[0]["parameters"], '{"city": "Beijing", "date": "2024-06-27"}'
)
def test_streaming_tool_call_without_arguments(self):
chunks = [
"<tool_call>get_weather\n",
"</tool_call>",
]
tool_calls = []
for chunk in chunks:
result = self.detector.parse_streaming_increment(chunk, self.tools)
for tool_call_chunk in result.calls:
if (
hasattr(tool_call_chunk, "tool_index")
and tool_call_chunk.tool_index is not None
):
while len(tool_calls) <= tool_call_chunk.tool_index:
tool_calls.append({"name": "", "parameters": ""})
tc = tool_calls[tool_call_chunk.tool_index]
if tool_call_chunk.name:
tc["name"] = tool_call_chunk.name
if tool_call_chunk.parameters:
tc["parameters"] += tool_call_chunk.parameters
self.assertEqual(len(tool_calls), 1)
self.assertEqual(tool_calls[0]["name"], "get_weather")
self.assertEqual(tool_calls[0]["parameters"], "{}")
self.assertEqual(self.detector.streamed_args_for_tool[0], "{}")
def test_streaming_tool_call_without_arguments_single_chunk(self):
"""Test no-argument tool call when name and end token arrive together."""
chunks = ["<tool_call>get_weather\n</tool_call>"]
tool_calls = []
for chunk in chunks:
result = self.detector.parse_streaming_increment(chunk, self.tools)
for tool_call_chunk in result.calls:
if (
hasattr(tool_call_chunk, "tool_index")
and tool_call_chunk.tool_index is not None
):
while len(tool_calls) <= tool_call_chunk.tool_index:
tool_calls.append({"name": "", "parameters": ""})
tc = tool_calls[tool_call_chunk.tool_index]
if tool_call_chunk.name:
tc["name"] = tool_call_chunk.name
if tool_call_chunk.parameters:
tc["parameters"] += tool_call_chunk.parameters
self.assertEqual(len(tool_calls), 1)
self.assertEqual(tool_calls[0]["name"], "get_weather")
self.assertEqual(tool_calls[0]["parameters"], "{}")
self.assertEqual(self.detector.streamed_args_for_tool[0], "{}")
def test_streaming_multiple_tool_calls(self):
"""Test streaming incremental parsing of multiple tool calls."""
chunks = [
@@ -3602,6 +3681,119 @@ class TestGlm47MoeDetector(unittest.TestCase):
_glm47_native_structural_tag_available.cache_clear()
class TestLing3Detector(unittest.TestCase):
def setUp(self):
self.tools = [
Tool(
type="function",
function=Function(
name="get_weather",
description="Get weather information",
parameters={
"type": "object",
"properties": {
"city": {"type": "string"},
"date": {"type": "string"},
},
},
),
),
Tool(
type="function",
function=Function(
name="get_date",
description="Get current date",
parameters={"type": "object", "properties": {}},
),
),
]
self.detector = Ling3Detector()
def _collect_streaming_tool_calls(self, chunks):
tool_calls = []
for chunk in chunks:
result = self.detector.parse_streaming_increment(chunk, self.tools)
for tool_call_chunk in result.calls:
while len(tool_calls) <= tool_call_chunk.tool_index:
tool_calls.append({"name": "", "parameters": ""})
tc = tool_calls[tool_call_chunk.tool_index]
if tool_call_chunk.name:
tc["name"] = tool_call_chunk.name
if tool_call_chunk.parameters:
tc["parameters"] += tool_call_chunk.parameters
return tool_calls
def test_detect_and_parse_newline_and_compact_tool_call(self):
cases = {
"newline": (
"<tool_call>get_weather\n"
"<arg_key>city</arg_key><arg_value>Beijing</arg_value>"
"<arg_key>date</arg_key><arg_value>2024-06-27</arg_value>"
"</tool_call>",
'{"city": "Beijing", "date": "2024-06-27"}',
),
"compact": (
"<tool_call>get_weather"
"<arg_key>city</arg_key><arg_value>Shanghai</arg_value>"
"<arg_key>date</arg_key><arg_value>2024-06-28</arg_value>"
"</tool_call>",
'{"city": "Shanghai", "date": "2024-06-28"}',
),
}
for layout, (text, expected) in cases.items():
with self.subTest(layout=layout):
result = self.detector.detect_and_parse(text, self.tools)
self.assertEqual(len(result.calls), 1)
self.assertEqual(result.calls[0].name, "get_weather")
self.assertEqual(result.calls[0].parameters, expected)
def test_detect_and_parse_empty_args(self):
result = self.detector.detect_and_parse(
"<tool_call>get_date</tool_call>", self.tools
)
self.assertEqual(len(result.calls), 1)
self.assertEqual(result.calls[0].name, "get_date")
self.assertEqual(json.loads(result.calls[0].parameters), {})
def test_streaming_empty_args_emits_single_empty_object(self):
tool_calls = self._collect_streaming_tool_calls(
["<tool_call>get_date", "</tool_call>"]
)
self.assertEqual(len(tool_calls), 1)
self.assertEqual(tool_calls[0]["name"], "get_date")
self.assertEqual(tool_calls[0]["parameters"], "{}")
self.assertEqual(self.detector.streamed_args_for_tool[0], "{}")
def test_streaming_newline_and_compact_tool_call(self):
cases = {
"newline": (
[
"<tool_call>get_weather\n",
"<arg_key>city</arg_key><arg_value>Beijing</arg_value>",
"<arg_key>date</arg_key><arg_value>2024-06-27</arg_value>",
"</tool_call>",
],
'{"city": "Beijing", "date": "2024-06-27"}',
),
"compact": (
[
"<tool_call>get_weather",
"<arg_key>city</arg_key><arg_value>Shanghai</arg_value>",
"<arg_key>date</arg_key><arg_value>2024-06-28</arg_value>",
"</tool_call>",
],
'{"city": "Shanghai", "date": "2024-06-28"}',
),
}
for layout, (chunks, expected) in cases.items():
with self.subTest(layout=layout):
self.setUp()
tool_calls = self._collect_streaming_tool_calls(chunks)
self.assertEqual(len(tool_calls), 1)
self.assertEqual(tool_calls[0]["name"], "get_weather")
self.assertEqual(tool_calls[0]["parameters"], expected)
class TestJsonArrayParser(unittest.TestCase):
def setUp(self):
# Create sample tools for testing