From d3f6b2a7b3c7c8dbec869b05eafa478601b4a27a Mon Sep 17 00:00:00 2001 From: Robert Shaw <114415538+robertgshaw2-redhat@users.noreply.github.com> Date: Wed, 14 Oct 2026 14:27:58 -0400 Subject: [PATCH] [Frontend] Clean Client Facing 500 Report (#58043) Co-authored-by: Claude --- .../test_error_sanitization.py | 18 ++++++++++++++++++ .../serve/exception_handling/error_response.py | 9 +++++++-- vllm/v1/engine/exceptions.py | 15 +++++++++++---- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/entrypoints/serve/exception_handling/test_error_sanitization.py b/tests/entrypoints/serve/exception_handling/test_error_sanitization.py index 98bf808965..c783fb7db6 100644 --- a/tests/entrypoints/serve/exception_handling/test_error_sanitization.py +++ b/tests/entrypoints/serve/exception_handling/test_error_sanitization.py @@ -135,3 +135,21 @@ class TestAffectedModulesUseSanitize: source = Path(spec.origin).read_text() assert "sanitize_message" in source, f"{module} does not call sanitize_message" assert "import" in source and "sanitize_message" in source + + +def test_engine_dead_error_response_hides_log_oriented_message(): + """Clients get a generic message with no reference to server internals.""" + from vllm.entrypoints.serve.exception_handling.error_response import ( + create_error_response, + ) + from vllm.v1.engine.exceptions import EngineDeadError + + exc = EngineDeadError() + err = create_error_response(exc) + + assert err.error.code == 500 + assert err.error.type == "InternalServerError" + assert err.error.message == EngineDeadError.CLIENT_MESSAGE + assert "stack trace" not in err.error.message + assert "log" not in err.error.message + assert "stack trace (above)" in str(exc) diff --git a/vllm/entrypoints/serve/exception_handling/error_response.py b/vllm/entrypoints/serve/exception_handling/error_response.py index 46e2504663..bf9d5290da 100644 --- a/vllm/entrypoints/serve/exception_handling/error_response.py +++ b/vllm/entrypoints/serve/exception_handling/error_response.py @@ -21,6 +21,7 @@ def create_error_response( if isinstance(message, Exception): exc = message + message = str(exc) logger.debug( "create_error_response called with %s: %s", type(exc).__name__, exc ) @@ -33,6 +34,7 @@ def create_error_response( VLLMUnprocessableEntityError, VLLMValidationError, ) + from vllm.v1.engine.exceptions import EngineDeadError if isinstance(exc, GracefulHTTPError): err_type = HTTPStatus(exc.http_status).phrase @@ -63,6 +65,11 @@ def create_error_response( ) status_code = exc.status_code param = None + elif isinstance(exc, EngineDeadError): + err_type = "InternalServerError" + status_code = HTTPStatus.INTERNAL_SERVER_ERROR + param = None + message = exc.CLIENT_MESSAGE elif isinstance(exc, VLLMServerError): # Any other server-caused error defaults to 500. err_type = "InternalServerError" @@ -88,8 +95,6 @@ def create_error_response( status_code = HTTPStatus.INTERNAL_SERVER_ERROR param = None - message = str(exc) - return ErrorResponse( error=ErrorInfo( message=sanitize_message(message), diff --git a/vllm/v1/engine/exceptions.py b/vllm/v1/engine/exceptions.py index edb0fe4261..e1d8e5f20b 100644 --- a/vllm/v1/engine/exceptions.py +++ b/vllm/v1/engine/exceptions.py @@ -10,12 +10,19 @@ class EngineGenerateError(VLLMServerError): class EngineDeadError(VLLMServerError): - """Raised when the EngineCore dies. Unrecoverable.""" + """Raised when the EngineCore dies. Unrecoverable. + + `str(exc)` is the log-oriented message for server logs and offline + tracebacks; `CLIENT_MESSAGE` is what API clients receive. + """ + + ENGINE_DEAD_MESSAGE = ( + "EngineCore encountered an issue. See stack trace (above) for the root cause." + ) + CLIENT_MESSAGE = "The server had an error while processing your request." def __init__(self, *args, suppress_context: bool = False, **kwargs): - ENGINE_DEAD_MESSAGE = "EngineCore encountered an issue. See stack trace (above) for the root cause." # noqa: E501 - - super().__init__(ENGINE_DEAD_MESSAGE, *args, **kwargs) + super().__init__(self.ENGINE_DEAD_MESSAGE, *args, **kwargs) # Make stack trace clearer when using with LLMEngine by # silencing irrelevant ZMQError. self.__suppress_context__ = suppress_context