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, 08:30 UTC
[Misc] Use VLLMValidationError consistently in chat completion and completion protocol validators (#36254)
Signed-off-by: umut-polat <52835619+umut-polat@users.noreply.github.com>
Umut Polat committed
Jun 1, 2026 at 07:04 UTC
f46e6be169909d4bf2c383b1852123a121fe90e2
3 files changed
+100
-24
tests/tool_use/test_chat_completion_request_validations.py
+65
@@ -116,3 +116,68 @@ def test_no_reasoning_fields_unchanged():
116
assistant_msg = request.messages[1]
117
assert assistant_msg.get("reasoning") is None
118
assert "reasoning_content" not in assistant_msg
119
+
120
+
121
+SAMPLE_TOOL = {
122
+ "type": "function",
123
+ "function": {
124
+ "name": "get_weather",
125
+ "description": "Get the weather",
126
+ "parameters": {
127
+ "type": "object",
128
+ "properties": {"location": {"type": "string"}},
129
+ },
130
+ },
131
+}
132
+
133
+
134
+def test_structured_outputs_with_named_tool_choice_rejected():
135
+ """structured_outputs cannot be combined with a named tool_choice."""
136
+ with pytest.raises(
137
+ ValueError,
138
+ match="structured outputs or tools, not both",
139
+ ):
140
+ ChatCompletionRequest.model_validate(
141
+ {
142
+ "messages": [{"role": "user", "content": "Hello"}],
143
+ "model": "facebook/opt-125m",
144
+ "tools": [SAMPLE_TOOL],
145
+ "tool_choice": {
146
+ "type": "function",
147
+ "function": {"name": "get_weather"},
148
+ },
149
+ "structured_outputs": {"json": {"type": "object"}},
150
+ }
151
+ )
152
+
153
+
154
+def test_structured_outputs_with_auto_tool_choice_allowed():
155
+ """structured_outputs with tool_choice 'auto' should be allowed."""
156
+ request = ChatCompletionRequest.model_validate(
157
+ {
158
+ "messages": [{"role": "user", "content": "Hello"}],
159
+ "model": "facebook/opt-125m",
160
+ "tools": [SAMPLE_TOOL],
161
+ "tool_choice": "auto",
162
+ "structured_outputs": {"json": {"type": "object"}},
163
+ }
164
+ )
165
+ assert request.tool_choice == "auto"
166
+
167
+
168
+def test_multiple_structured_outputs_rejected():
169
+ """Only one kind of structured output constraint is allowed."""
170
+ with pytest.raises(
171
+ ValueError,
172
+ match="You can only use one kind of constraints",
173
+ ):
174
+ ChatCompletionRequest.model_validate(
175
+ {
176
+ "messages": [{"role": "user", "content": "Hello"}],
177
+ "model": "facebook/opt-125m",
178
+ "structured_outputs": {
179
+ "json": {"type": "object"},
180
+ "regex": ".*",
181
+ },
182
+ }
183
+ )
vllm/entrypoints/openai/chat_completion/protocol.py
+29
-20
@@ -740,19 +740,19 @@ class ChatCompletionRequest(OpenAIBaseModel):
740
)
741
# you can only use one kind of constraints for structured outputs
742
if count > 1:
743
- raise ValueError(
743
+ raise VLLMValidationError(
744
"You can only use one kind of constraints for structured "
745
- "outputs ('json', 'regex' or 'choice')."
745
+ "outputs ('json', 'regex' or 'choice').",
746
)
747
# you can only either use structured outputs or tools, not both
748
- if count > 1 and data.get("tool_choice", "none") not in (
748
+ if count > 0 and data.get("tool_choice", "none") not in (
749
"none",
750
"auto",
751
"required",
752
):
753
- raise ValueError(
753
+ raise VLLMValidationError(
754
"You can only either use constraints for structured outputs "
755
- "or tools, not both."
755
+ "or tools, not both.",
756
)
757
return data
758
@@ -784,17 +784,21 @@ class ChatCompletionRequest(OpenAIBaseModel):
784
if "tool_choice" in data and data["tool_choice"] is not None:
785
# ensure that if "tool choice" is specified, tools are present
786
if "tools" not in data or data["tools"] is None:
787
- raise ValueError("When using `tool_choice`, `tools` must be set.")
787
+ raise VLLMValidationError(
788
+ "When using `tool_choice`, `tools` must be set.",
789
+ parameter="tool_choice",
790
+ )
791
792
# make sure that tool choice is either a named tool
793
# OR that it's set to "auto" or "required"
794
if data["tool_choice"] not in ["auto", "required"] and not isinstance(
795
data["tool_choice"], dict
796
):
794
- raise ValueError(
797
+ raise VLLMValidationError(
798
f"Invalid value for `tool_choice`: {data['tool_choice']}! "
799
'Only named tools, "none", "auto" or "required" '
797
- "are supported."
800
+ "are supported.",
801
+ parameter="tool_choice",
802
)
803
804
# ensure that if "tool_choice" is specified as an object,
@@ -807,29 +811,33 @@ class ChatCompletionRequest(OpenAIBaseModel):
811
valid_tool = False
812
function = data["tool_choice"].get("function")
813
if not isinstance(function, dict):
810
- raise ValueError(
814
+ raise VLLMValidationError(
815
f"Invalid value for `function`: `{function}` in "
812
- f"`tool_choice`! {correct_usage_message}"
816
+ f"`tool_choice`! {correct_usage_message}",
817
+ parameter="tool_choice.function",
818
)
819
if "name" not in function:
815
- raise ValueError(
820
+ raise VLLMValidationError(
821
f"Expected field `name` in `function` in "
817
- f"`tool_choice`! {correct_usage_message}"
822
+ f"`tool_choice`! {correct_usage_message}",
823
+ parameter="tool_choice.function.name",
824
)
825
function_name = function["name"]
826
if not isinstance(function_name, str) or len(function_name) == 0:
821
- raise ValueError(
827
+ raise VLLMValidationError(
828
f"Invalid `name` in `function`: `{function_name}`"
823
- f" in `tool_choice`! {correct_usage_message}"
829
+ f" in `tool_choice`! {correct_usage_message}",
830
+ parameter="tool_choice.function.name",
831
)
832
for tool in data["tools"]:
833
if tool["function"]["name"] == function_name:
834
valid_tool = True
835
break
836
if not valid_tool:
830
- raise ValueError(
837
+ raise VLLMValidationError(
838
"The tool specified in `tool_choice` does not match any"
832
- " of the specified `tools`"
839
+ " of the specified `tools`",
840
+ parameter="tool_choice",
841
)
842
return data
843
@@ -837,9 +845,9 @@ class ChatCompletionRequest(OpenAIBaseModel):
845
@classmethod
846
def check_generation_prompt(cls, data):
847
if data.get("continue_final_message") and data.get("add_generation_prompt"):
840
- raise ValueError(
848
+ raise VLLMValidationError(
849
"Cannot set both `continue_final_message` and "
842
- "`add_generation_prompt` to True."
850
+ "`add_generation_prompt` to True.",
851
)
852
return data
853
@@ -849,8 +857,9 @@ class ChatCompletionRequest(OpenAIBaseModel):
857
if data.get("cache_salt") is not None and (
858
not isinstance(data["cache_salt"], str) or not data["cache_salt"]
859
):
852
- raise ValueError(
853
- "Parameter 'cache_salt' must be a non-empty string if provided."
860
+ raise VLLMValidationError(
861
+ "Parameter 'cache_salt' must be a non-empty string if provided.",
862
+ parameter="cache_salt",
863
)
864
return data
865
vllm/entrypoints/openai/completion/protocol.py
+6
-4
@@ -447,8 +447,9 @@ class CompletionRequest(OpenAIBaseModel):
447
)
448
449
if prompt_is_empty and embeds_is_empty:
450
- raise ValueError(
451
- "Either prompt or prompt_embeds must be provided and non-empty."
450
+ raise VLLMValidationError(
451
+ "Either prompt or prompt_embeds must be provided and non-empty.",
452
+ parameter="prompt",
453
)
454
455
return data
@@ -459,8 +460,9 @@ class CompletionRequest(OpenAIBaseModel):
460
if data.get("cache_salt") is not None and (
461
not isinstance(data["cache_salt"], str) or not data["cache_salt"]
462
):
462
- raise ValueError(
463
- "Parameter 'cache_salt' must be a non-empty string if provided."
463
+ raise VLLMValidationError(
464
+ "Parameter 'cache_salt' must be a non-empty string if provided.",
465
+ parameter="cache_salt",
466
)
467
return data
468