From 69bdd345428408a2fdf745e225c87defbc2c07d0 Mon Sep 17 00:00:00 2001 From: Muhammad Fawaz <135441198+professorsab@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:11:11 +0500 Subject: [PATCH] [Bugfix] Fall back to Pydantic loc for param in validation errors (#46038) Signed-off-by: professorsab <135441198+professorsab@users.noreply.github.com> Co-authored-by: Mahad Durrani <114791389+mahadrehmann@users.noreply.github.com> --- .../serve/utils/test_server_utils.py | 65 +++++++++++++++++++ vllm/entrypoints/serve/utils/server_utils.py | 6 ++ 2 files changed, 71 insertions(+) create mode 100644 tests/entrypoints/serve/utils/test_server_utils.py diff --git a/tests/entrypoints/serve/utils/test_server_utils.py b/tests/entrypoints/serve/utils/test_server_utils.py new file mode 100644 index 0000000000..9189698613 --- /dev/null +++ b/tests/entrypoints/serve/utils/test_server_utils.py @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project +"""Tests that validation_exception_handler populates the `param` field +in its error response using the Pydantic error's `loc`, even when no +custom VLLMValidationError context is present. + +Previously, `param` was only populated for errors carrying a custom +VLLMValidationError in their Pydantic `ctx`. Plain validation failures +(missing fields, wrong types) left `param` as None, even though the +field name was readily available from `error['loc']`. +""" + +import json +from types import SimpleNamespace + +import pytest +from fastapi.exceptions import RequestValidationError + +from vllm.entrypoints.serve.utils.server_utils import validation_exception_handler + + +def _fake_request(log_error_stack: bool = False) -> SimpleNamespace: + """Minimal stand-in for a FastAPI Request - just enough for the + handler to read req.app.state.args.log_error_stack.""" + return SimpleNamespace( + app=SimpleNamespace( + state=SimpleNamespace(args=SimpleNamespace(log_error_stack=log_error_stack)) + ), + state=SimpleNamespace(), # no request_metadata -> hasattr(...) is False + ) + + +class TestValidationErrorParamFallback: + """Ensure `param` falls back to the Pydantic error's `loc` when no + custom VLLMValidationError context is present.""" + + @pytest.mark.parametrize( + ("error_type", "msg"), + [ + ("missing", "Field required"), + ("list_type", "Input should be a valid list"), + ], + ids=["missing-field", "wrong-type"], + ) + @pytest.mark.asyncio + async def test_param_falls_back_to_loc(self, error_type: str, msg: str): + errors = [{"type": error_type, "loc": ("body", "messages"), "msg": msg}] + exc = RequestValidationError(errors) + + response = await validation_exception_handler(_fake_request(), exc) + body = json.loads(response.body) + + assert body["error"]["param"] == "body.messages" + + @pytest.mark.asyncio + async def test_param_fallback_does_not_crash_on_non_dict_error(self): + """Schemathesis fuzzing found that errors[0] isn't always a dict. + The fallback must not crash in that case - it should just leave + param as None instead of raising.""" + exc = RequestValidationError(["some unexpected non-dict error"]) + + response = await validation_exception_handler(_fake_request(), exc) + body = json.loads(response.body) + + assert body["error"]["param"] is None diff --git a/vllm/entrypoints/serve/utils/server_utils.py b/vllm/entrypoints/serve/utils/server_utils.py index d24d492b61..93a8ef757d 100644 --- a/vllm/entrypoints/serve/utils/server_utils.py +++ b/vllm/entrypoints/serve/utils/server_utils.py @@ -427,6 +427,12 @@ async def validation_exception_handler(req: Request, exc: RequestValidationError param = ctx_error.parameter break + if param is None and errors: + first_error = errors[0] + loc = first_error.get("loc") if isinstance(first_error, dict) else None + if loc: + param = ".".join(str(part) for part in loc) + exc_str = str(exc) errors_str = str(errors)