[Tool Call] Stream DeepSeek-V3.2 function call parameters in JSON format. (#16091)

Co-authored-by: Huixxi <uestc.hugo@gmail.com>
This commit is contained in:
Muqi Li
2026-03-03 17:46:29 +08:00
committed by GitHub
parent 4c95953b77
commit 666caaf9ce
2 changed files with 31 additions and 22 deletions

View File

@@ -85,6 +85,7 @@ class DeepSeekV32Detector(BaseFormatDetector):
r'<DSMLinvoke\s+name="([^"]+)"\s*>(.*?)(</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:
@@ -93,9 +94,9 @@ class DeepSeekV32Detector(BaseFormatDetector):
def _parse_parameters_from_xml(
self, invoke_content: str, allow_partial: bool = False
) -> dict:
) -> str:
"""
Parse parameters from either XML-like format or JSON format to dict.
Parse parameters from either XML-like format or JSON format to str.
Supports two formats:
1. XML parameter tags: <DSMLparameter name="..." string="...">value</DSMLparameter>
@@ -103,17 +104,14 @@ class DeepSeekV32Detector(BaseFormatDetector):
"""
# First, try to parse as direct JSON (new format)
invoke_content_stripped = invoke_content.strip()
if invoke_content_stripped.startswith("{") and invoke_content_stripped.endswith(
"}"
):
try:
parameters = json.loads(invoke_content_stripped)
if isinstance(parameters, dict):
return parameters
except (json.JSONDecodeError, ValueError):
# If JSON parsing fails, fall through to XML parsing
pass
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 = {}
@@ -165,7 +163,7 @@ class DeepSeekV32Detector(BaseFormatDetector):
except json.JSONDecodeError:
parameters[param_name] = param_value.strip()
return parameters
return json.dumps(parameters, ensure_ascii=False)
def detect_and_parse(self, text: str, tools: list[Tool]) -> StreamingParseResult:
"""
@@ -202,7 +200,7 @@ class DeepSeekV32Detector(BaseFormatDetector):
# Parse parameters from XML format
func_args = self._parse_parameters_from_xml(invoke_content)
# construct match_result for parse_base_json
match_result = {"name": func_name, "parameters": func_args}
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)
@@ -288,7 +286,6 @@ class DeepSeekV32Detector(BaseFormatDetector):
current_params = self._parse_parameters_from_xml(
invoke_content, allow_partial=not is_tool_end
)
current_args_json = json.dumps(current_params, ensure_ascii=False)
# 3. Calculate and send incremental arguments
sent_len = len(self.streamed_args_for_tool[self.current_tool_id])
@@ -300,12 +297,11 @@ class DeepSeekV32Detector(BaseFormatDetector):
if is_tool_end:
# If complete, send everything remaining
argument_diff = current_args_json[sent_len:]
argument_diff = current_params[sent_len:]
elif prev_params is not None:
# If partial, send stable prefix diff
prev_args_json = json.dumps(prev_params, ensure_ascii=False)
if current_args_json != prev_args_json:
prefix = _find_common_prefix(prev_args_json, current_args_json)
if current_params != prev_params:
prefix = _find_common_prefix(current_params, prev_params)
if len(prefix) > sent_len:
argument_diff = prefix[sent_len:]
@@ -353,5 +349,5 @@ class DeepSeekV32Detector(BaseFormatDetector):
return lambda name: StructureInfo(
begin=f'<DSMLinvoke name="{name}">',
end="</DSMLinvoke>",
trigger=f"<DSMLinvoke",
trigger="<DSMLinvoke",
)