# NousResearch/hermes-agent · DeepSeek V3 parser multi-tool call handling

## Open now

- 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.

## TL;DR

- 5 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 2 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #1365. Verify it addresses your case.
- **Recommended:** read PR #1365 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-1300`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.40`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #989: DeepSeek V3 parser drops tool calls when model returns multiple

## Prior & competing PRs

- **#1300: fix: DeepSeek V3 parser drops multi-tool calls**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - 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)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `environments/tool_call_parsers/deepseek_v3_parser.py`
  - fix hunks:
  - `environments/tool_call_parsers/deepseek_v3_parser.py`

    ```diff
    @@ -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,
    ```

    ```diff
    @@ -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):
    ```

    ```diff
    @@ -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
    ```
- **#992: fix: DeepSeek V3 parser drops multi-tool calls (greedy regex)**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#1300`
- **#1015: fix: DeepSeek V3 parser drops tool calls when model returns multiple (#989)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#992`

## Fix status

- Merged to main in PR #1365 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-03-15`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
- Subsystem: `environments`
