From aa0b3bc924a908f6cb3cc91a7451e3fbf96b4024 Mon Sep 17 00:00:00 2001 From: taohui Date: Tue, 23 Sep 2025 00:28:44 +0800 Subject: [PATCH 1/3] fix(DeepSeekV31ToolParser): handle multiple tool parsing in non-streaming output Signed-off-by: taohui --- .../tool_use/test_deepseekv31_tool_parser.py | 59 +++++++++++++++++++ .../tool_parsers/deepseekv31_tool_parser.py | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 tests/tool_use/test_deepseekv31_tool_parser.py diff --git a/tests/tool_use/test_deepseekv31_tool_parser.py b/tests/tool_use/test_deepseekv31_tool_parser.py new file mode 100644 index 000000000000..dffc85401c58 --- /dev/null +++ b/tests/tool_use/test_deepseekv31_tool_parser.py @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +import pytest + +from vllm.entrypoints.openai.tool_parsers import DeepSeekV31ToolParser +from vllm.transformers_utils.tokenizer import get_tokenizer + +MODEL = "deepseek-ai/DeepSeek-V3.1" + + +@pytest.fixture(scope="module") +def deepseekv31_tokenizer(): + return get_tokenizer(tokenizer_name=MODEL) + + +@pytest.fixture +def parser(deepseekv31_tokenizer): + return DeepSeekV31ToolParser(deepseekv31_tokenizer) + + +def test_extract_tool_calls_with_tool(parser): + model_output = ( + "normal text" + + "<|tool▁calls▁begin|>" + + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + + "<|tool▁calls▁end|>" + ) + result = parser.extract_tool_calls(model_output, None) + assert result.tools_called + assert len(result.tool_calls) == 1 + assert result.tool_calls[0].function.name == "foo" + assert result.tool_calls[0].function.arguments == "{\"x\":1}" + assert result.content == "normal text" + + +def test_extract_tool_calls_with_multiple_tools(parser): + model_output = ( + "some prefix text" + + "<|tool▁calls▁begin|>" + + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + + "<|tool▁call▁begin|>bar<|tool▁sep|>{\"y\":2}<|tool▁call▁end|>" + + "<|tool▁calls▁end|>" + + " some suffix text" + ) + + result = parser.extract_tool_calls(model_output, None) + + assert result.tools_called + assert len(result.tool_calls) == 2 + + assert result.tool_calls[0].function.name == "foo" + assert result.tool_calls[0].function.arguments == "{\"x\":1}" + + assert result.tool_calls[1].function.name == "bar" + assert result.tool_calls[1].function.arguments == "{\"y\":2}" + + # prefix is content + assert result.content == "some prefix text" diff --git a/vllm/entrypoints/openai/tool_parsers/deepseekv31_tool_parser.py b/vllm/entrypoints/openai/tool_parsers/deepseekv31_tool_parser.py index ff9188190f3f..09095f899177 100644 --- a/vllm/entrypoints/openai/tool_parsers/deepseekv31_tool_parser.py +++ b/vllm/entrypoints/openai/tool_parsers/deepseekv31_tool_parser.py @@ -39,7 +39,7 @@ def __init__(self, tokenizer: AnyTokenizer): self.tool_call_end_token: str = "<|tool▁call▁end|>" self.tool_call_regex = re.compile( - r"<|tool▁call▁begin|>(?P.*)<|tool▁sep|>(?P.*)<|tool▁call▁end|>" + r"<|tool▁call▁begin|>(?P.*?)<|tool▁sep|>(?P.*?)<|tool▁call▁end|>" ) self.stream_tool_call_portion_regex = re.compile( From d54d8bc153365334b96663730dc0795e185bf2ab Mon Sep 17 00:00:00 2001 From: taohui Date: Tue, 23 Sep 2025 00:58:13 +0800 Subject: [PATCH 2/3] fix code style Signed-off-by: taohui --- tests/tool_use/test_deepseekv31_tool_parser.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/tool_use/test_deepseekv31_tool_parser.py b/tests/tool_use/test_deepseekv31_tool_parser.py index dffc85401c58..297eb1fd7e2d 100644 --- a/tests/tool_use/test_deepseekv31_tool_parser.py +++ b/tests/tool_use/test_deepseekv31_tool_parser.py @@ -21,10 +21,9 @@ def parser(deepseekv31_tokenizer): def test_extract_tool_calls_with_tool(parser): model_output = ( - "normal text" - + "<|tool▁calls▁begin|>" - + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" - + "<|tool▁calls▁end|>" + "normal text" + "<|tool▁calls▁begin|>" + + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + + "<|tool▁calls▁end|>" ) result = parser.extract_tool_calls(model_output, None) assert result.tools_called @@ -36,12 +35,10 @@ def test_extract_tool_calls_with_tool(parser): def test_extract_tool_calls_with_multiple_tools(parser): model_output = ( - "some prefix text" - + "<|tool▁calls▁begin|>" - + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" - + "<|tool▁call▁begin|>bar<|tool▁sep|>{\"y\":2}<|tool▁call▁end|>" - + "<|tool▁calls▁end|>" - + " some suffix text" + "some prefix text" + "<|tool▁calls▁begin|>" + + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + + "<|tool▁call▁begin|>bar<|tool▁sep|>{\"y\":2}<|tool▁call▁end|>" + + "<|tool▁calls▁end|>" + " some suffix text" ) result = parser.extract_tool_calls(model_output, None) From a6c600fbe49246ab73601d0e930713e17925258d Mon Sep 17 00:00:00 2001 From: taohui Date: Tue, 23 Sep 2025 01:08:49 +0800 Subject: [PATCH 3/3] fix code style Signed-off-by: taohui --- tests/tool_use/test_deepseekv31_tool_parser.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/tool_use/test_deepseekv31_tool_parser.py b/tests/tool_use/test_deepseekv31_tool_parser.py index 297eb1fd7e2d..5f6b266d3aa1 100644 --- a/tests/tool_use/test_deepseekv31_tool_parser.py +++ b/tests/tool_use/test_deepseekv31_tool_parser.py @@ -23,8 +23,7 @@ def test_extract_tool_calls_with_tool(parser): model_output = ( "normal text" + "<|tool▁calls▁begin|>" + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + - "<|tool▁calls▁end|>" - ) + "<|tool▁calls▁end|>") result = parser.extract_tool_calls(model_output, None) assert result.tools_called assert len(result.tool_calls) == 1 @@ -38,8 +37,7 @@ def test_extract_tool_calls_with_multiple_tools(parser): "some prefix text" + "<|tool▁calls▁begin|>" + "<|tool▁call▁begin|>foo<|tool▁sep|>{\"x\":1}<|tool▁call▁end|>" + "<|tool▁call▁begin|>bar<|tool▁sep|>{\"y\":2}<|tool▁call▁end|>" + - "<|tool▁calls▁end|>" + " some suffix text" - ) + "<|tool▁calls▁end|>" + " some suffix text") result = parser.extract_tool_calls(model_output, None)