@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / f46e6be169
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, 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