Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions tests/tool_parsers/test_qwen3coder_tool_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1146,3 +1146,50 @@ def test_no_double_serialization_string_args(qwen3_tool_parser):
args = json.loads(raw_arguments)
assert args["message"] == "hello world"
assert '\\"hello world\\"' not in raw_arguments


def test_extract_tool_calls_streaming_split_tag(qwen3_tool_parser):
"""
This highlights the need to use current_text instead of delta_text.
"""
request = ChatCompletionRequest(model=MODEL, messages=[])

# Iteration 1: "<tool"
prev_text_1 = "I will use a tool."
delta_text_1 = "<tool"
curr_text_1 = prev_text_1 + delta_text_1

msg1 = qwen3_tool_parser.extract_tool_calls_streaming(
previous_text=prev_text_1,
current_text=curr_text_1,
delta_text=delta_text_1,
previous_token_ids=[1, 2, 3],
current_token_ids=[1, 2, 3, 4],
delta_token_ids=[4],
request=request
)

# Iteration 2: "_call>"
prev_text_2 = curr_text_1
delta_text_2 = "_call>"
curr_text_2 = prev_text_2 + delta_text_2

msg2 = qwen3_tool_parser.extract_tool_calls_streaming(
previous_text=prev_text_2,
current_text=curr_text_2,
delta_text=delta_text_2,
previous_token_ids=[1, 2, 3, 4],
current_token_ids=[1, 2, 3, 4, 5],
delta_token_ids=[5],
request=request
)

# The assertion must verify that the is_tool_call_started variable correctly switches to True
assert qwen3_tool_parser.is_tool_call_started is True, "is_tool_call_started should be True when '<tool_call>' is completed in current_text."

# and that the function does not return fragments of the tag in DeltaMessage(content=...)
if msg1 and msg1.content:
assert "<tool" not in msg1.content
if msg2 and msg2.content:
assert "_call>" not in msg2.content

27 changes: 19 additions & 8 deletions vllm/tool_parsers/qwen3coder_tool_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
Tool,
ToolParser,
)
from vllm.tool_parsers.utils import find_tool_properties
from vllm.tool_parsers.utils import find_tool_properties, partial_tag_overlap

logger = init_logger(__name__)

Expand Down Expand Up @@ -109,6 +109,7 @@ def _reset_streaming_state(self):
# Store accumulated parameters for type conversion
self.accumulated_params = {}
self.streaming_request = None
self._sent_content_idx = 0
Comment thread
ExtReMLapin marked this conversation as resolved.

def _convert_param_value(
self, param_value: str, param_name: str, param_config: dict, func_name: str
Expand Down Expand Up @@ -391,29 +392,39 @@ def extract_tool_calls_streaming(
# Handle normal content before tool calls
if not self.is_tool_call_started:
# Check if tool call is starting
tool_starts_count = current_text.count(self.tool_call_start_token)
if (
self.tool_call_start_token_id in delta_token_ids
or self.tool_call_start_token in delta_text
or tool_starts_count > self.current_tool_index
):
self.is_tool_call_started = True
# Return any content before the tool call
if self.tool_call_start_token in delta_text:
content_before = delta_text[
: delta_text.index(self.tool_call_start_token)
]
last_start = current_text.rfind(self.tool_call_start_token)
if last_start > self._sent_content_idx:
content_before = current_text[self._sent_content_idx:last_start]
self._sent_content_idx = last_start
Comment thread
ExtReMLapin marked this conversation as resolved.
Outdated
if content_before:
return DeltaMessage(content=content_before)
return None
else:
overlap = partial_tag_overlap(current_text, self.tool_call_start_token)
sendable_idx = len(current_text) - overlap

# Check if we're between tool calls - skip whitespace
if (
current_text.rstrip().endswith(self.tool_call_end_token)
and delta_text.strip() == ""
):
# We just ended a tool call, skip whitespace
self._sent_content_idx = len(current_text)
return None
Comment thread
ExtReMLapin marked this conversation as resolved.
# Normal content, no tool call
return DeltaMessage(content=delta_text)

if sendable_idx > self._sent_content_idx:
content = current_text[self._sent_content_idx:sendable_idx]
self._sent_content_idx = sendable_idx
if content:
return DeltaMessage(content=content)
return None

# Check if we're between tool calls (waiting for next one)
# Count tool calls we've seen vs processed
Expand Down
Loading