From 53d763f437331082dd3e3cbdbd10c8b4311cd774 Mon Sep 17 00:00:00 2001 From: "Claude 2.0" Date: Sun, 3 May 2026 21:21:14 +0800 Subject: [PATCH] fix(parser): fallback prev_tool_call_arr to streamed args on parse failure When _parse_xml_function_call fails during streaming, prev_tool_call_arr still holds the "{}" placeholder from the header-sent step. The serving layer's remaining-args check then sees "{}" as the expected argument and computes a wrong remainder, causing {"arguments": "{}"} to be emitted twice. Add a parse_succeeded flag to track whether parsing succeeded, and fall back to the incrementally streamed arguments from streamed_args_for_tool when parsing fails. The closing brace is appended to match the serving layer's remainder check expectations. Co-Authored-By: Claude Opus 4.7 --- vllm/tool_parsers/qwen3coder_tool_parser.py | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/vllm/tool_parsers/qwen3coder_tool_parser.py b/vllm/tool_parsers/qwen3coder_tool_parser.py index 7b089ceffbc0..1e0fc9e6d31f 100644 --- a/vllm/tool_parsers/qwen3coder_tool_parser.py +++ b/vllm/tool_parsers/qwen3coder_tool_parser.py @@ -639,6 +639,7 @@ def extract_tool_calls_streaming( func_content_end = tool_text.find(self.function_end_token, func_start) if func_content_end != -1: func_content = tool_text[func_start:func_content_end] + parse_succeeded = False try: parsed_tool = self._parse_xml_function_call( func_content, @@ -649,12 +650,36 @@ def extract_tool_calls_streaming( self.prev_tool_call_arr[self.current_tool_index][ "arguments" ] = parsed_tool.function.arguments + parse_succeeded = True except Exception: logger.debug( "Failed to parse tool call during streaming: %s", tool_text, exc_info=True, ) + # When _parse_xml_function_call fails (returns None + # or throws), prev_tool_call_arr still has the "{}" + # placeholder from the header-sent step. Fall back + # to the arguments that were incrementally streamed so + # the serving layer's remaining-args check produces + # correct output instead of double-emitting "{}". + # Only trigger when parsing actually failed — a successful + # parse with empty parameters produces "{}" legitimately. + if ( + not parse_succeeded + and self.current_tool_index < len(self.prev_tool_call_arr) + and self.current_tool_index + < len(self.streamed_args_for_tool) + ): + # Append closing brace so prev_tool_call_arr + # matches streamed_args_for_tool after the "+=" + # "}" below — otherwise the serving layer's + # remainder check loses the closing brace. + self.prev_tool_call_arr[self.current_tool_index][ + "arguments" + ] = self.streamed_args_for_tool[ + self.current_tool_index + ] + "}" if self.current_tool_index < len(self.streamed_args_for_tool): self.streamed_args_for_tool[self.current_tool_index] += "}"