DeepSeek V3 parser multi-tool call handling
Cluster 989 · environments. Data as of
Nothing open on GitHub. Fix merged in #1365 on 15 Mar 2026. Check it covers your case.
The short answer
- Known?
- Yes. 5 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #1365. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
resolved, deciding PR #1300. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and fixes address a bug in the DeepSeek V3 parser where multiple parallel tool calls are dropped, likely due to a greedy regex. The members consistently describe the same parsing defect and proposed corrections.
Open issues0
Open PRs0
Fixes on record
-
#1300 fix: DeepSeek V3 parser drops multi-tool calls
files:
environments/tool_call_parsers/deepseek_v3_parser.py,tests/test_tool_call_parsers.py -
#1365 fix: support multiple parallel tool calls in DeepSeek V3 parser (#989)
files:
environments/tool_call_parsers/deepseek_v3_parser.pyShow the diff
-
environments/tool_call_parsers/deepseek_v3_parser.py@@ -10,12 +10,13 @@ <|tool▁call▁end|> <|tool▁calls▁end|> -Based on VLLM's DeepSeekV3ToolParser.extract_tool_calls() +Fixes Issue #989: Support for multiple simultaneous tool calls. """ import re import uuid -from typing import List, Optional +import logging +from typing import List, Optional, Tuple from openai.types.chat.chat_completion_message_tool_call import ( ChatCompletionMessageToolCall, @@ -24,6 +25,7 @@ from environments.tool_call_parsers import ParseResult, ToolCallParser, register_parser +logger = logging.getLogger(__name__) @register_parser("deepseek_v3") class DeepSeekV3ToolCallParser(ToolCallParser): @@ -32,45 +34,56 @@ class DeepSeekV3ToolCallParser(ToolCallParser): Uses special unicode tokens with fullwidth angle brackets and block elements. Extracts type, function name, and JSON arguments from the structured format. + Ensures all tool calls are captured when the model executes multiple actions. """ START_TOKEN = "[redacted:secret-assignment]" - # Regex captures: type, function_name, function_arguments + # Updated PATTERN: Using \s* instead of literal \n for increased robustness + # against variations in model formatting (Issue #989). PATTERN = re.compile( - r"<|tool▁call▁begin|>(?P<type>.*?)<|tool▁sep|>(?P<function_name>.*?)\n```json\n(?P<function_arguments>.*?)\n```<|tool▁call▁end|>", + r"<|tool▁call▁begin|>(?P<type>.*?)<|tool▁sep|>(?P<function_name>.*?)\s*```json\s*(?P<function_arguments>.*?)\s*```\s*<|tool▁call▁end|>", re.DOTALL, ) def parse(self, text: str) -> ParseResult: + """ + Parses the input text and extracts all available tool calls. + """ if self.START_TOKEN not in text: return text, None try: - matches = self.PATTERN.findall(text) + # Using finditer to capture ALL tool calls in the sequence + matches = list(self.PATTERN.finditer(text)) if not matches: return text, None tool_calls: List[ChatCompletionMessageToolCall] = [] + for match in matches: - tc_type, func_name, func_args = match + func_name = match.group("function_name").strip() + func_args = match.group("function_arguments").strip() + tool_calls.append( ChatCompletionMessageToolCall( id=f"call_{uuid.uuid4().hex[:8]}", type="function", function=Function( - name=func_name.strip(), - arguments=func_args.strip(), + name=func_name, + arguments=func_args, ), ) ) - if not tool_calls: - return text, None + if tool_calls: + # Content is text before the first tool call block + content_index = text.find(self.START_TOKEN) + content = text[:content_index].strip() + return content if content else None, tool_calls - # Content is everything before the tool calls section - content = text[: text.find(self.START_TOKEN)].strip() - return content if content else None, tool_calls + return text, None - except Exception: + except Exception as e: + logger.error(f"Error parsing DeepSeek V3 tool calls: {e}") return text, None
-
2 PRs closed without merging
From the close comments: duplicate 1 · merged via another change 1
Closed issues · 1
- Closed#989DeepSeek V3 parser drops tool calls when model returns multiple
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab