mirror of
https://github.com/vllm-project/vllm.git
synced 2026-08-15 02:08:10 +00:00
Fix chat completion 500 on non-object JSON bodies (#51654)
Signed-off-by: Tarun Kumar <[email protected]>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
"""Non-object JSON bodies must fail validation cleanly (4xx), not AttributeError (500).
|
||||
|
||||
mode=before validators that call data.get(...) without an isinstance(data, dict)
|
||||
guard raise AttributeError for string/list/scalar bodies and surface as HTTP 500.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[
|
||||
"this is not valid json{{{",
|
||||
["not", "an", "object"],
|
||||
42,
|
||||
None,
|
||||
],
|
||||
)
|
||||
def test_chat_completion_request_rejects_non_object_body(payload):
|
||||
with pytest.raises(ValidationError):
|
||||
ChatCompletionRequest.model_validate(payload)
|
||||
|
||||
|
||||
def test_chat_completion_request_cache_salt_still_validated_on_dict():
|
||||
with pytest.raises(ValidationError, match="cache_salt"):
|
||||
ChatCompletionRequest.model_validate(
|
||||
{
|
||||
"model": "qwen",
|
||||
"messages": [{"role": "user", "content": "hello"}],
|
||||
"cache_salt": "",
|
||||
}
|
||||
)
|
||||
@@ -745,6 +745,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def validate_response_format(cls, data):
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
response_format = data.get("response_format")
|
||||
if response_format is None:
|
||||
return data
|
||||
@@ -776,6 +778,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def validate_stream_options(cls, data):
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
if data.get("stream_options") and not data.get("stream"):
|
||||
raise VLLMValidationError(
|
||||
"Stream options can only be defined when `stream=True`.",
|
||||
@@ -787,6 +791,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_logprobs(cls, data):
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
if data.get("logprob_token_ids") and data.get("use_beam_search"):
|
||||
raise VLLMValidationError(
|
||||
"`logprob_token_ids` is not supported with beam search.",
|
||||
@@ -845,6 +851,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
|
||||
def check_structured_outputs_count(cls, data):
|
||||
if isinstance(data, ValueError):
|
||||
raise data
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
|
||||
if data.get("structured_outputs", None) is None:
|
||||
return data
|
||||
@@ -970,6 +978,8 @@ class ChatCompletionRequest(OpenAIBaseModel):
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def check_generation_prompt(cls, data):
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
if data.get("continue_final_message") and data.get("add_generation_prompt"):
|
||||
raise VLLMValidationError(
|
||||
"Cannot set both `continue_final_message` and "
|
||||
@@ -1105,6 +1115,8 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
|
||||
def check_batch_mode(cls, data: Any) -> Any:
|
||||
if isinstance(data, BatchChatCompletionRequest):
|
||||
data = data.model_dump(exclude_unset=True)
|
||||
if not isinstance(data, dict):
|
||||
return data
|
||||
if data.get("use_beam_search"):
|
||||
raise VLLMValidationError(
|
||||
"Batch chat completions do not support beam search. "
|
||||
@@ -1117,13 +1129,14 @@ class BatchChatCompletionRequest(OpenAIBaseModel):
|
||||
parameter="logprob_token_ids",
|
||||
)
|
||||
response_format = data.get("response_format")
|
||||
rf_type = (
|
||||
response_format.get("type")
|
||||
if isinstance(response_format, dict)
|
||||
else getattr(response_format, "type", None)
|
||||
)
|
||||
if rf_type == "structural_tag":
|
||||
validate_structural_tag_response_format(response_format)
|
||||
if response_format is not None:
|
||||
rf_type = (
|
||||
response_format.get("type")
|
||||
if isinstance(response_format, dict)
|
||||
else getattr(response_format, "type", None)
|
||||
)
|
||||
if rf_type == "structural_tag":
|
||||
validate_structural_tag_response_format(response_format)
|
||||
if (structured_outputs := data.get("structured_outputs")) is not None:
|
||||
validate_structured_outputs_structural_tag(structured_outputs)
|
||||
n = data.get("n", 1)
|
||||
|
||||
Reference in New Issue
Block a user