@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 2b237c7a41
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 2, 01:00 UTC

[Bugfix] Honor tool_choice="none" in Chat Completions streaming (#42752)

Signed-off-by: hoobnn <111053672+hoobnn@users.noreply.github.com> Signed-off-by: sfeng33 <4florafeng@gmail.com> Co-authored-by: sfeng33 <4florafeng@gmail.com>

hoobnn committed Jun 4, 2026 at 04:27 UTC 2b237c7a4100316de7843884d72af9c402e35e53
2 files changed +40
tests/parser/test_streaming.py
+37
@@ -36,11 +36,24 @@ def tokenizer():
36 return get_tokenizer("Qwen/Qwen3-32B")
37
38
39 +TOOLS = [
40 + {
41 + "type": "function",
42 + "function": {
43 + "name": "get_weather",
44 + "parameters": {"type": "object", "properties": {}},
45 + },
46 + }
47 +]
48 +
49 +
50 @pytest.fixture
51 def request_obj():
52 return ChatCompletionRequest(
53 model="test-model",
54 messages=[{"role": "user", "content": "hi"}],
55 + tools=TOOLS,
56 + tool_choice="auto",
57 )
58
59
@@ -328,3 +341,27 @@ def test_parse_delta_finished_appends_remaining_args(tokenizer, request_obj):
341 tc.function.arguments for tc in tool_calls if tc.function.arguments
342 )
343 assert tool_args.endswith(remainder)
344 +
345 +
346 +def test_parse_delta_tool_choice_none(tokenizer, request_obj):
347 + parser = make_parser(tokenizer, reasoning=False, tool=True)
348 + request = request_obj.model_copy(update={"tool_choice": "none"})
349 + results = stream_text(parser, tokenizer, MODEL_OUTPUT, request, prompt_token_ids=[])
350 + reasoning, content, tool_calls = collect_fields(results)
351 +
352 + assert reasoning == ""
353 + assert len(tool_calls) == 0
354 + assert "<tool_call>" in content
355 + assert "get_weather" in content
356 +
357 +
358 +def test_parse_delta_tool_choice_none_with_reasoning(tokenizer, request_obj):
359 + parser = make_parser(tokenizer, reasoning=True, tool=True)
360 + request = request_obj.model_copy(update={"tool_choice": "none"})
361 + results = stream_text(parser, tokenizer, MODEL_OUTPUT, request, prompt_token_ids=[])
362 + reasoning, content, tool_calls = collect_fields(results)
363 +
364 + assert "let me think about this" in reasoning
365 + assert len(tool_calls) == 0
366 + assert "<tool_call>" in content
367 + assert "get_weather" in content
vllm/parser/abstract_parser.py
+3
@@ -706,6 +706,9 @@ class DelegatingParser(Parser):
706 tool_call_id_type: str = "random",
707 function_name_returned: bool = False,
708 ) -> tuple[DeltaMessage | None, bool]:
709 + if request.tool_choice == "none":
710 + return (DeltaMessage(content=delta_text) if delta_text else None), False
711 +
712 assert self._tool_parser is not None
713 supports_required_and_named = self._tool_parser.supports_required_and_named
714 if (