[Bugfix] Preserve parallel HY-V3 calls delivered in one streaming delta (#53965)
Signed-off-by: Taneem Ibrahim <taneem.ibrahim@gmail.com>
Taneem Ibrahim committed
Aug 27, 2026 at 11:26 UTC
f18e29834b2114428aa5e261bfde09cb87965b4b
1 file changed
+49
-8
vllm/tool_parsers/hy_v3_tool_parser.py
+49
-8
@@ -419,12 +419,11 @@ class HYV3ToolParser(ToolParser):
419
return DeltaMessage(content=delta_text)
420
421
# Encountered tool_calls start tag; extract preceding content and buffer
422
+ content_delta: str | None = None
423
if self.tool_calls_start_token in delta_text:
424
text_parts = delta_text.split(self.tool_calls_start_token)
425
self._buffer += text_parts[-1]
425
- if text_parts[0]:
426
- return DeltaMessage(content=text_parts[0])
427
- # Don't return None; continue processing buffer for complete content
426
+ content_delta = text_parts[0] or None
427
else:
428
self._buffer += delta_text
429
@@ -441,6 +440,8 @@ class HYV3ToolParser(ToolParser):
440
# Haven't encountered tool_call start tag yet; keep buffering
441
start_idx = cur_text.find(self.tool_call_start_token)
442
if start_idx == -1 and self._streaming_tool_name is None:
443
+ if content_delta is not None:
444
+ return DeltaMessage(content=content_delta)
445
self._buffer = ""
446
return None
447
@@ -451,7 +452,9 @@ class HYV3ToolParser(ToolParser):
452
if sep_idx == -1:
453
# tool_sep not yet seen; keep buffering from tool_call_start
454
self._buffer = cur_text[start_idx:]
454
- return None
455
+ if content_delta is None:
456
+ return None
457
+ return DeltaMessage(content=content_delta)
458
459
# Extract tool name: between tool_call_start_token and tool_sep_token
460
name_start = start_idx + len(self.tool_call_start_token)
@@ -476,6 +479,7 @@ class HYV3ToolParser(ToolParser):
479
)
480
]
481
)
482
+ name_delta.content = content_delta
483
484
# Check if buffer already has complete arguments (all-in-one-delta)
485
if self.tool_call_end_token not in self._buffer:
@@ -496,6 +500,34 @@ class HYV3ToolParser(ToolParser):
500
]
501
)
502
503
+ def _drain_calls(self, request: ChatCompletionRequest) -> list[DeltaToolCall]:
504
+ deltas: list[DeltaToolCall] = []
505
+ while (start_idx := self._buffer.find(self.tool_call_start_token)) != -1:
506
+ end_idx = self._buffer.find(self.tool_call_end_token, start_idx)
507
+ if end_idx == -1:
508
+ break
509
+ end_idx += len(self.tool_call_end_token)
510
+ complete_call = self._buffer[start_idx:end_idx]
511
+ self._buffer = self._buffer[end_idx:]
512
+ for call in self._extract_tool_calls(complete_call, request):
513
+ self.current_tool_id += 1
514
+ arguments = call.function.arguments
515
+ self.prev_tool_call_arr.append(
516
+ {"name": call.function.name, "arguments": json.loads(arguments)}
517
+ )
518
+ self.streamed_args_for_tool.append(arguments)
519
+ deltas.append(
520
+ DeltaToolCall(
521
+ index=self.current_tool_id,
522
+ id=make_tool_call_id(),
523
+ type="function",
524
+ function=DeltaFunctionCall(
525
+ name=call.function.name, arguments=arguments
526
+ ),
527
+ )
528
+ )
529
+ return deltas
530
+
531
def _extract_streaming_incremental(
532
self,
533
name_delta: DeltaMessage | None,
@@ -632,9 +664,10 @@ class HYV3ToolParser(ToolParser):
664
self._streamed_json_len = end
665
666
# --- construct return DeltaMessage ---
667
+ delta: DeltaMessage | None
668
if name_delta is not None and argument_diff:
669
nd_func = name_delta.tool_calls[0].function
637
- return DeltaMessage(
670
+ delta = DeltaMessage(
671
tool_calls=[
672
DeltaToolCall(
673
index=self.current_tool_id,
@@ -647,9 +680,17 @@ class HYV3ToolParser(ToolParser):
680
)
681
]
682
)
683
+ delta.content = name_delta.content
684
elif name_delta is not None:
651
- return name_delta
685
+ delta = name_delta
686
elif argument_diff:
653
- return self._make_args_delta(argument_diff)
687
+ delta = self._make_args_delta(argument_diff)
688
else:
655
- return None
689
+ delta = None
690
+
691
+ extra_tool_calls = self._drain_calls(request) if is_complete else []
692
+ if extra_tool_calls:
693
+ if delta is None:
694
+ return DeltaMessage(tool_calls=extra_tool_calls)
695
+ delta.tool_calls.extend(extra_tool_calls)
696
+ return delta