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 5, 02:15 UTC
[Bugfix] Reject empty tools array with HTTP 400 (#39780)
Signed-off-by: Jigang Zhou <zjg0907008@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
jigangz committed
Apr 15, 2026 at 21:08 UTC
18013df6ae27c3fb941307c46c975227126d641f
4 files changed
+23
-23
tests/tool_parsers/test_ernie45_moe_tool_parser.py
+1
-1
@@ -328,7 +328,7 @@ def test_extract_tool_calls_streaming_incremental(
328
expected_content,
329
):
330
"""Verify the Ernie45 Parser streaming behavior by verifying each chunk is as expected.""" # noqa: E501
331
- request = ChatCompletionRequest(model=MODEL, messages=[], tools=[])
331
+ request = ChatCompletionRequest(model=MODEL, messages=[])
332
333
tool_calls_dict = {}
334
for delta_message in stream_delta_message_generator(
tests/tool_parsers/test_xlam_tool_parser.py
+1
-1
@@ -484,7 +484,7 @@ def test_extract_tool_calls_streaming_incremental(
484
expected_content,
485
):
486
"""Verify the XLAM Parser streaming behavior by verifying each chunk is as expected.""" # noqa: E501
487
- request = ChatCompletionRequest(model=MODEL, messages=[], tools=[])
487
+ request = ChatCompletionRequest(model=MODEL, messages=[])
488
489
chunks = []
490
for delta_message in stream_delta_message_generator(
tests/tool_use/test_chat_completion_request_validations.py
+9
-9
@@ -26,15 +26,15 @@ def test_chat_completion_request_with_no_tools():
26
)
27
assert request.tool_choice == "none"
28
29
- # tools key present but empty
30
- request = ChatCompletionRequest.model_validate(
31
- {
32
- "messages": [{"role": "user", "content": "Hello"}],
33
- "model": "facebook/opt-125m",
34
- "tools": [],
35
- }
36
- )
37
- assert request.tool_choice == "none"
29
+ # tools key present but empty -- should be rejected
30
+ with pytest.raises(ValueError, match="must not be an empty array"):
31
+ ChatCompletionRequest.model_validate(
32
+ {
33
+ "messages": [{"role": "user", "content": "Hello"}],
34
+ "model": "facebook/opt-125m",
35
+ "tools": [],
36
+ }
37
+ )
38
39
40
@pytest.mark.parametrize("tool_choice", ["auto", "required"])
vllm/entrypoints/openai/chat_completion/protocol.py
+12
-12
@@ -681,6 +681,18 @@ class ChatCompletionRequest(OpenAIBaseModel):
681
@model_validator(mode="before")
682
@classmethod
683
def check_tool_usage(cls, data):
684
+ if isinstance(data, ValueError):
685
+ raise data
686
+ if not isinstance(data, dict):
687
+ return data
688
+
689
+ # Reject empty tools array, matching OpenAI API behavior
690
+ if data.get("tools") == []:
691
+ raise ValueError(
692
+ "`tools` must not be an empty array. "
693
+ "Either provide at least one tool or omit the field entirely."
694
+ )
695
+
696
# if "tool_choice" is not specified but tools are provided,
697
# default to "auto" tool_choice
698
if "tool_choice" not in data and data.get("tools"):
@@ -707,18 +719,6 @@ class ChatCompletionRequest(OpenAIBaseModel):
719
"are supported."
720
)
721
710
- # if tool_choice is "required" but the "tools" list is empty,
711
- # override the data to behave like "none" to align with
712
- # OpenAI’s behavior.
713
- if (
714
- data["tool_choice"] == "required"
715
- and isinstance(data["tools"], list)
716
- and len(data["tools"]) == 0
717
- ):
718
- data["tool_choice"] = "none"
719
- del data["tools"]
720
- return data
721
-
722
# ensure that if "tool_choice" is specified as an object,
723
# it matches a valid tool
724
correct_usage_message = (