chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,372 @@
import json
import logging
import re
from partial_json_parser.core.options import Allow
from sglang.srt.entrypoints.openai.protocol import Tool
from sglang.srt.function_call.base_format_detector import BaseFormatDetector
from sglang.srt.function_call.core_types import (
StreamingParseResult,
StructureInfo,
ToolCallItem,
_GetInfoFunc,
)
from sglang.srt.function_call.utils import _find_common_prefix, _partial_json_loads
logger = logging.getLogger(__name__)
class DeepSeekV32Detector(BaseFormatDetector):
"""
Detector for DeepSeek V3.2 model function call format.
The DeepSeek V3.2 format uses XML-like DSML tags to delimit function calls.
Supports two parameter formats:
Format 1 - XML Parameter Tags:
```
<DSMLfunction_calls>
<DSMLinvoke name="function_name">
<DSMLparameter name="param_name" string="true">value</DSMLparameter>
...
</DSMLinvoke>
</DSMLfunction_calls>
```
Format 2 - Direct JSON:
```
<DSMLfunction_calls>
<DSMLinvoke name="function_name">
{
"param_name": "value"
}
</DSMLinvoke>
</DSMLfunction_calls>
```
Examples:
```
<DSMLfunction_calls>
<DSMLinvoke name="get_favorite_tourist_spot">
<DSMLparameter name="city" string="true">San Francisco</DSMLparameter>
</DSMLinvoke>
</DSMLfunction_calls>
<DSMLfunction_calls>
<DSMLinvoke name="get_favorite_tourist_spot">
{ "city": "San Francisco" }
</DSMLinvoke>
</DSMLfunction_calls>
```
Key Components:
- Tool Calls Section: Wrapped between `<DSMLfunction_calls>` and `</DSMLfunction_calls>`
- Individual Tool Call: Wrapped between `<DSMLinvoke name="...">` and `</DSMLinvoke>`
- Parameters: Either XML tags or direct JSON format
- Supports multiple tool calls
Reference: DeepSeek V3.2 format specification
"""
def __init__(self):
super().__init__()
self.bot_token = "<DSMLfunction_calls>"
self.eot_token = "</DSMLfunction_calls>"
self.invoke_end_token = "</DSMLinvoke>"
self.parameter_regex = r'<DSMLparameter\s+name="([^"]+)"\s+string="([^"]+)"\s*>(.*?)</DSMLparameter>'
self.partial_parameter_regex = (
r'<DSMLparameter\s+name="([^"]+)"\s+string="([^"]+)"\s*>(.*)$'
)
self.function_calls_regex = (
r"<DSMLfunction_calls>(.*?)</DSMLfunction_calls>"
)
# Long-form `<DSMLinvoke name="x">...</DSMLinvoke>` and the
# self-closing `<DSMLinvoke name="x"/>` shape V4 emits for zero-arg
# tools. The `end` group is empty when the closer hasn't streamed in.
self.invoke_regex = (
r'<DSMLinvoke\s+name="(?P<name>[^"]+)"\s*'
r"(?:(?P<self_close>/>)"
r"|>(?P<body>.*?)(?P<end>(?:</DSMLinvoke>|$)))"
)
self.prefix_parameter_end_call = ["</", "DSML", "parameter"]
self.prefix_invoke_end_call = ["</", "DSML", "inv", "oke"]
self.current_tool_id = -1
def has_tool_call(self, text: str) -> bool:
"""Check if the text contains a deepseek v32 format tool call."""
return self.bot_token in text or "<DSMLinvoke" in text
@staticmethod
def _unpack_invoke_match(m: "re.Match[str]") -> tuple[str, str, bool]:
"""Returns (name, body, is_complete) for an invoke_regex match.
Self-closing invokes have empty body and are always complete.
Long-form bodies are always strings (possibly empty); they're
incomplete when matched against `$` because the closing tag
hasn't streamed in yet.
"""
name = m.group("name").strip()
if m.group("self_close"):
return name, "", True
return name, m.group("body"), bool(m.group("end"))
def _parse_parameters_from_xml(
self, invoke_content: str, allow_partial: bool = False
) -> str:
"""
Parse parameters from either XML-like format or JSON format to str.
Supports two formats:
1. XML parameter tags: <DSMLparameter name="..." string="...">value</DSMLparameter>
2. Direct JSON: { "key": "value" }
"""
# First, try to parse as direct JSON (new format)
invoke_content_stripped = invoke_content.strip()
if invoke_content_stripped.startswith("{"):
if allow_partial:
# Remove incomplete invoke end call prefix in case they are captured by param
for token in reversed(self.prefix_invoke_end_call):
invoke_content_stripped = invoke_content_stripped.rstrip(token)
return invoke_content_stripped
elif invoke_content_stripped.endswith("}"):
return invoke_content_stripped
# Fall back to XML parameter tag parsing (original format)
parameters = {}
# Find all complete parameter matches
param_matches = list(
re.finditer(self.parameter_regex, invoke_content, re.DOTALL)
)
last_match_end = 0
for match in param_matches:
param_name = match.group(1)
param_type = match.group(2)
param_value = match.group(3)
last_match_end = match.end()
# Convert value based on type
if param_type == "true": # string type
parameters[param_name] = param_value.strip()
else:
# Try to parse as JSON for other types
try:
parameters[param_name] = json.loads(param_value.strip())
except (json.JSONDecodeError, ValueError):
parameters[param_name] = param_value.strip()
# If allowed, try to parse a partial parameter at the end
if allow_partial:
remaining_content = invoke_content[last_match_end:]
# Remove incomplete parameter_end_call prefix in case they are captured by param
for token in reversed(self.prefix_parameter_end_call):
remaining_content = remaining_content.rstrip(token)
# Match start of a parameter tag + value (potentially incomplete)
# Regex: <tag name="..." string="...">VALUE... (no end tag)
partial_match = re.search(
self.partial_parameter_regex, remaining_content, re.DOTALL
)
if partial_match and (param_value := partial_match.group(3)):
param_name = partial_match.group(1)
if partial_match.group(2) == "true":
parameters[param_name] = param_value.strip()
else:
try:
parameters[param_name] = _partial_json_loads(
param_value, Allow.ALL
)[0]
except json.JSONDecodeError:
parameters[param_name] = param_value.strip()
return json.dumps(parameters, ensure_ascii=False)
def detect_and_parse(self, text: str, tools: list[Tool]) -> StreamingParseResult:
"""
One-time parsing: Detects and parses tool calls in the provided text.
:param text: The complete text to parse.
:param tools: List of available tools.
:return: ParseResult indicating success or failure, consumed text, leftover text, and parsed calls.
"""
idx = text.find(self.bot_token)
normal_text = text[:idx].removesuffix("\n\n") if idx != -1 else text
if self.bot_token not in text:
return StreamingParseResult(normal_text=normal_text, calls=[])
calls = []
try:
# Extract content between function_calls tags
function_calls_match = re.search(
self.function_calls_regex,
text,
re.DOTALL,
)
if not function_calls_match:
return StreamingParseResult(normal_text=normal_text, calls=[])
function_calls_content = function_calls_match.group(1)
# Find all invoke blocks
for invoke_match in re.finditer(
self.invoke_regex, function_calls_content, re.DOTALL
):
func_name, invoke_content, _ = self._unpack_invoke_match(invoke_match)
func_args = self._parse_parameters_from_xml(invoke_content)
# construct match_result for parse_base_json
match_result = {"name": func_name, "parameters": json.loads(func_args)}
calls.extend(self.parse_base_json(match_result, tools))
return StreamingParseResult(normal_text=normal_text, calls=calls)
except Exception as e:
logger.error(f"Error in detect_and_parse: {e}")
# return the normal text if parsing fails
return StreamingParseResult(normal_text=text)
def parse_streaming_increment(
self, new_text: str, tools: list[Tool]
) -> StreamingParseResult:
"""
Streaming incremental parsing tool calls for DeepSeekV32 format.
Supports multiple consecutive invoke blocks and argument streaming.
"""
self._buffer += new_text
current_text = self._buffer
# Check if buffer contains any DSML markers or ends with potential tag prefix
# This handles partial/streaming DSML content
dsml_markers = ["DSML", "<", "</"]
potentially_dsml = any(marker in current_text for marker in dsml_markers)
# Also check if text ends with start of a tag (to handle "<" arriving separately)
dsml_prefixes = ["<", "<", "</", "</"]
ends_with_prefix = any(
current_text.rstrip().endswith(prefix) for prefix in dsml_prefixes
)
if (
not self.has_tool_call(current_text)
and not potentially_dsml
and not ends_with_prefix
):
self._buffer = ""
for e_token in [self.eot_token, self.invoke_end_token]:
if e_token in current_text:
current_text = current_text.replace(e_token, "")
return StreamingParseResult(normal_text=current_text)
all_calls: list[ToolCallItem] = []
try:
# Loop to handle multiple consecutive invoke blocks
while True:
# Try to match an invoke block (may be partial)
invoke_match = re.search(
pattern=self.invoke_regex,
string=current_text,
flags=re.DOTALL,
)
if not invoke_match:
break
func_name, invoke_content, is_tool_end = self._unpack_invoke_match(
invoke_match
)
# Initialize state if this is the first tool call
if self.current_tool_id == -1:
self.current_tool_id = 0
self.prev_tool_call_arr = []
self.streamed_args_for_tool = [""]
# Ensure arrays are large enough for current tool
while len(self.prev_tool_call_arr) <= self.current_tool_id:
self.prev_tool_call_arr.append({})
while len(self.streamed_args_for_tool) <= self.current_tool_id:
self.streamed_args_for_tool.append("")
# 1. Send tool name if not sent yet
if not self.current_tool_name_sent:
all_calls.append(
ToolCallItem(
tool_index=self.current_tool_id,
name=func_name,
parameters="",
)
)
self.current_tool_name_sent = True
# 2. Parse current parameters (partial or complete)
current_params = self._parse_parameters_from_xml(
invoke_content, allow_partial=not is_tool_end
)
# 3. Calculate and send incremental arguments
sent_len = len(self.streamed_args_for_tool[self.current_tool_id])
prev_params = self.prev_tool_call_arr[self.current_tool_id].get(
"arguments"
)
argument_diff = None
if is_tool_end:
# If complete, send everything remaining
argument_diff = current_params[sent_len:]
elif prev_params is not None:
# If partial, send stable prefix diff
if current_params != prev_params:
prefix = _find_common_prefix(current_params, prev_params)
if len(prefix) > sent_len:
argument_diff = prefix[sent_len:]
if argument_diff:
all_calls.append(
ToolCallItem(
tool_index=self.current_tool_id,
name=None,
parameters=argument_diff,
)
)
self.streamed_args_for_tool[self.current_tool_id] += argument_diff
# Update the stored arguments
self.prev_tool_call_arr[self.current_tool_id] = {
"name": func_name,
"arguments": current_params,
}
# Check if tool call is complete (has closing tag)
if is_tool_end:
# Remove the completed tool call from buffer
self._buffer = current_text[invoke_match.end() :]
current_text = self._buffer # Update for next iteration
# Move to next tool call
self.current_tool_id += 1
self.current_tool_name_sent = False
# Continue loop to check for more invoke blocks
continue
else:
# Tool call not complete yet, don't return anything
# Wait for more chunks until we see </DSMLinvoke>
break
# No more invoke blocks found
return StreamingParseResult(normal_text="", calls=all_calls)
except Exception as e:
logger.error(f"Error in parse_streaming_increment: {e}")
return StreamingParseResult(normal_text=current_text)
def structure_info(self) -> _GetInfoFunc:
return lambda name: StructureInfo(
begin=f'<DSMLinvoke name="{name}">',
end="</DSMLinvoke>",
trigger="<DSMLinvoke",
)
def get_structural_tag_name(self) -> str:
return "deepseek_v3_2"