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(