Skip to content

Commit b7f0b50

Browse files
committed
fix(vllm): don't stream raw tool-call markup as content when a tool parser is active
When a tool_parser is configured and the request carries tools, the streaming loop emitted every text delta as delta.content — including the model's raw tool-call markup (e.g. <tool_call>...) — because extract_tool_calls only runs on the full output after the stream. Clients streaming a tool call therefore saw the unparsed tool-call syntax as assistant content, with the structured tool_calls arriving only in the final chunk. Buffer the text while a tool parser is active and let the existing end-of-stream ChatDelta carry the parsed tool_calls; when no tool call is found, flush the buffered content as a single content delta before the final chunk. Non-tool-parser streaming is unchanged. Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com>
1 parent 9ba8521 commit b7f0b50

1 file changed

Lines changed: 15 additions & 1 deletion

File tree

backend/python/vllm/backend.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,12 +597,18 @@ async def _predict(self, request, context, streaming=False):
597597
# Stream the results
598598
generated_text = ""
599599
last_output = None
600+
# When a tool parser is active for a tool-enabled request, the model's
601+
# raw tool-call markup (e.g. <tool_call>...) must not be streamed as
602+
# delta.content — clients would otherwise see the unparsed syntax. Buffer
603+
# the text and emit structured tool_calls (or the cleaned content) once
604+
# the full output is available and the parser has run, below.
605+
has_tool_parser = bool(self.tool_parser_cls and request.Tools)
600606
try:
601607
async for request_output in outputs:
602608
iteration_text = request_output.outputs[0].text
603609
last_output = request_output
604610

605-
if streaming:
611+
if streaming and not has_tool_parser:
606612
# Remove text already sent as vllm concatenates the text from previous yields
607613
delta_iteration_text = iteration_text.removeprefix(generated_text)
608614
# Send the partial result
@@ -698,6 +704,14 @@ async def _predict(self, request, context, streaming=False):
698704
)
699705

700706
if streaming:
707+
# If content was buffered (has_tool_parser) but the parser found no
708+
# tool call, the buffered text was never streamed — flush it as a
709+
# content delta before the final structured chunk.
710+
if has_tool_parser and not tool_calls_proto and content:
711+
yield backend_pb2.Reply(
712+
message=bytes(content, encoding='utf-8'),
713+
chat_deltas=[backend_pb2.ChatDelta(content=content)],
714+
)
701715
# Final chunk with structured data
702716
yield backend_pb2.Reply(
703717
message=b"",

0 commit comments

Comments
 (0)