@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / e18b714b2e
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 14, 05:30 UTC

[Bugfix] Fix DeepSeekV31ToolParser to correctly parse multiple tools in non-streaming output (#25405)

Signed-off-by: taohui <taohui3@gmail.com>

Tao Hui committed Sep 24, 2025 at 20:58 UTC e18b714b2ed849f26cfbea3365855885856dbc58
2 files changed +55 -1
tests/tool_use/test_deepseekv31_tool_parser.py new
+54
@@ -0,0 +1,54 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +
4 +import pytest
5 +
6 +from vllm.entrypoints.openai.tool_parsers import DeepSeekV31ToolParser
7 +from vllm.transformers_utils.tokenizer import get_tokenizer
8 +
9 +MODEL = "deepseek-ai/DeepSeek-V3.1"
10 +
11 +
12 +@pytest.fixture(scope="module")
13 +def deepseekv31_tokenizer():
14 + return get_tokenizer(tokenizer_name=MODEL)
15 +
16 +
17 +@pytest.fixture
18 +def parser(deepseekv31_tokenizer):
19 + return DeepSeekV31ToolParser(deepseekv31_tokenizer)
20 +
21 +
22 +def test_extract_tool_calls_with_tool(parser):
23 + model_output = (
24 + "normal text" + "<|tool▁calls▁begin|>" +
25 + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" +
26 + "<|tool▁calls▁end|>")
27 + result = parser.extract_tool_calls(model_output, None)
28 + assert result.tools_called
29 + assert len(result.tool_calls) == 1
30 + assert result.tool_calls[0].function.name == "foo"
31 + assert result.tool_calls[0].function.arguments == "{\"x\":1}"
32 + assert result.content == "normal text"
33 +
34 +
35 +def test_extract_tool_calls_with_multiple_tools(parser):
36 + model_output = (
37 + "some prefix text" + "<|tool▁calls▁begin|>" +
38 + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" +
39 + "<|tool▁call▁begin|>bar<|tool▁sep|>{\"y\":2}<|tool▁call▁end|>" +
40 + "<|tool▁calls▁end|>" + " some suffix text")
41 +
42 + result = parser.extract_tool_calls(model_output, None)
43 +
44 + assert result.tools_called
45 + assert len(result.tool_calls) == 2
46 +
47 + assert result.tool_calls[0].function.name == "foo"
48 + assert result.tool_calls[0].function.arguments == "{\"x\":1}"
49 +
50 + assert result.tool_calls[1].function.name == "bar"
51 + assert result.tool_calls[1].function.arguments == "{\"y\":2}"
52 +
53 + # prefix is content
54 + assert result.content == "some prefix text"
vllm/entrypoints/openai/tool_parsers/deepseekv31_tool_parser.py
+1 -1
@@ -39,7 +39,7 @@ class DeepSeekV31ToolParser(ToolParser):
39 self.tool_call_end_token: str = "<|tool▁call▁end|>"
40
41 self.tool_call_regex = re.compile(
42 - r"<|tool▁call▁begin|>(?P<function_name>.*)<|tool▁sep|>(?P<function_arguments>.*)<|tool▁call▁end|>"
42 + r"<|tool▁call▁begin|>(?P<function_name>.*?)<|tool▁sep|>(?P<function_arguments>.*?)<|tool▁call▁end|>"
43 )
44
45 self.stream_tool_call_portion_regex = re.compile(