[Tool Call][DSV32] Streamline function call parameters (#14750)

Signed-off-by: Muqi Li <muqi1029@gmail.com>
This commit is contained in:
Muqi Li
2025-12-27 03:35:30 +08:00
committed by GitHub
parent 43e1bbc0d5
commit 01bd0d3e8b
2 changed files with 60 additions and 29 deletions

View File

@@ -2,6 +2,8 @@ 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 (
@@ -10,7 +12,7 @@ from sglang.srt.function_call.core_types import (
ToolCallItem,
_GetInfoFunc,
)
from sglang.srt.function_call.utils import _find_common_prefix
from sglang.srt.function_call.utils import _find_common_prefix, _partial_json_loads
logger = logging.getLogger(__name__)
@@ -82,11 +84,12 @@ class DeepSeekV32Detector(BaseFormatDetector):
self.invoke_regex = (
r'<DSMLinvoke\s+name="([^"]+)"\s*>(.*?)(</DSMLinvoke>|$)'
)
self.prefix_parameter_end_call = ["</", "DSML", "parameter"]
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
return self.bot_token in text or "<DSMLinvoke" in text
def _parse_parameters_from_xml(
self, invoke_content: str, allow_partial: bool = False
@@ -139,16 +142,25 @@ class DeepSeekV32Detector(BaseFormatDetector):
# 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:
if partial_match and (param_value := partial_match.group(3)):
param_name = partial_match.group(1)
param_value = partial_match.group(3)
parameters[param_name] = param_value
if partial_match.group(2) == "true":
parameters[param_name] = param_value.strip()
else:
parameters[param_name] = _partial_json_loads(
param_value, Allow.ALL
)[0]
return parameters
@@ -206,13 +218,6 @@ class DeepSeekV32Detector(BaseFormatDetector):
self._buffer += new_text
current_text = self._buffer
# Check if we have a tool call or any DSML-related content
# Key insight: DSML tags contain distinctive markers like "DSML"
# If we see these markers anywhere, we should keep buffering
has_tool_call = (
self.bot_token in current_text or "<DSMLinvoke" in current_text
)
# Check if buffer contains any DSML markers or ends with potential tag prefix
# This handles partial/streaming DSML content
dsml_markers = ["DSML", "<", "</"]
@@ -224,7 +229,11 @@ class DeepSeekV32Detector(BaseFormatDetector):
current_text.rstrip().endswith(prefix) for prefix in dsml_prefixes
)
if not has_tool_call and not potentially_dsml and not ends_with_prefix:
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:
@@ -241,7 +250,6 @@ class DeepSeekV32Detector(BaseFormatDetector):
string=current_text,
flags=re.DOTALL,
)
if not invoke_match:
break
@@ -342,5 +350,5 @@ class DeepSeekV32Detector(BaseFormatDetector):
return lambda name: StructureInfo(
begin=f'<DSMLinvoke name="{name}">',
end="</DSMLinvoke>",
trigger=f'<DSMLinvoke name="{name}">',
trigger=f"<DSMLinvoke",
)