From 063844c1522d001cb223c2f8c312ae6f6ff048cd Mon Sep 17 00:00:00 2001 From: chaunceyjiang Date: Tue, 3 Jun 2025 10:59:54 +0000 Subject: [PATCH 1/2] [Bugfix]: Fix the incompatibility issue with tool_choice 'required' when Thinking is enabled Signed-off-by: chaunceyjiang --- vllm/entrypoints/openai/serving_chat.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vllm/entrypoints/openai/serving_chat.py b/vllm/entrypoints/openai/serving_chat.py index ea8e187dc6b7..864ce2ac25ee 100644 --- a/vllm/entrypoints/openai/serving_chat.py +++ b/vllm/entrypoints/openai/serving_chat.py @@ -932,7 +932,8 @@ async def chat_completion_full_generator( else: logprobs = None auto_tools_called = False - + reasoning_content = None + content = output.text if self.reasoning_parser: try: reasoning_parser = self.reasoning_parser(tokenizer) @@ -944,9 +945,6 @@ async def chat_completion_full_generator( reasoning_content, content = ( reasoning_parser.extract_reasoning_content( output.text, request=request)) - else: - reasoning_content = None - content = output.text # if auto tools are not enabled, and a named tool choice using # outlines is not being used @@ -981,7 +979,7 @@ async def chat_completion_full_generator( # the fields of FunctionDefinition are a superset of the # tool call outputs and can be used for parsing tool_calls = TypeAdapter( - list[FunctionDefinition]).validate_json(output.text) + list[FunctionDefinition]).validate_json(content) message = ChatMessage( role=role, content="", From f8cb8dfaec8ee216882747305c1586abb60c6c9c Mon Sep 17 00:00:00 2001 From: chaunceyjiang Date: Tue, 3 Jun 2025 11:17:34 +0000 Subject: [PATCH 2/2] [Bugfix]: Fix the incompatibility issue with tool_choice 'required' when Thinking is enabled Signed-off-by: chaunceyjiang --- .../test_completion_with_function_calling.py | 2 +- vllm/entrypoints/openai/serving_chat.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/entrypoints/openai/test_completion_with_function_calling.py b/tests/entrypoints/openai/test_completion_with_function_calling.py index dad76b54c5e9..dcc8918357c3 100644 --- a/tests/entrypoints/openai/test_completion_with_function_calling.py +++ b/tests/entrypoints/openai/test_completion_with_function_calling.py @@ -8,7 +8,7 @@ from ...utils import RemoteOpenAIServer # any model with a chat template should work here -MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct" +MODEL_NAME = "Qwen/Qwen3-0.6B" @pytest.fixture(scope="module") diff --git a/vllm/entrypoints/openai/serving_chat.py b/vllm/entrypoints/openai/serving_chat.py index 864ce2ac25ee..19659d97f642 100644 --- a/vllm/entrypoints/openai/serving_chat.py +++ b/vllm/entrypoints/openai/serving_chat.py @@ -319,10 +319,13 @@ def _filter_delta_text(delta_text: str, def extract_tool_call_required_streaming( self, previous_text: str, - current_text: str, + current_text: Optional[str], delta_text: str, function_name_returned: bool, ) -> tuple[Optional[DeltaMessage], bool]: + if current_text is None or current_text == "": + # if the current text is empty, we cannot parse it + return None, function_name_returned try: obj = partial_json_parser.loads(current_text) except partial_json_parser.core.exceptions.MalformedJSON: @@ -649,10 +652,18 @@ async def chat_completion_stream_generator( current_text = previous_text + delta_text fn_name_returned = function_name_returned[i] + if self.reasoning_parser: + _, content = \ + reasoning_parser.extract_reasoning_content( + current_text, + request + ) + else: + content = current_text delta_message, function_name_returned[i] = ( self.extract_tool_call_required_streaming( previous_text=previous_text, - current_text=current_text, + current_text=content, delta_text=delta_text, function_name_returned=fn_name_returned)) @@ -932,8 +943,7 @@ async def chat_completion_full_generator( else: logprobs = None auto_tools_called = False - reasoning_content = None - content = output.text + if self.reasoning_parser: try: reasoning_parser = self.reasoning_parser(tokenizer) @@ -945,6 +955,9 @@ async def chat_completion_full_generator( reasoning_content, content = ( reasoning_parser.extract_reasoning_content( output.text, request=request)) + else: + reasoning_content = None + content = output.text # if auto tools are not enabled, and a named tool choice using # outlines is not being used @@ -978,6 +991,7 @@ async def chat_completion_full_generator( # the fields of FunctionDefinition are a superset of the # tool call outputs and can be used for parsing + assert content is not None tool_calls = TypeAdapter( list[FunctionDefinition]).validate_json(content) message = ChatMessage(