@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 769f27e701
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 11, 00:00 UTC

[OpenAI] Add parameter metadata to validation errors (#30134)

Signed-off-by: Rehan Khan <Rehan.Khan7@ibm.com>

R3hankhan committed Dec 23, 2025 at 17:00 UTC 769f27e7011f6e878de67453a018793cde36d77b
8 files changed +232 -72
vllm/entrypoints/openai/api_server.py
+11
@@ -909,6 +909,16 @@ def build_app(args: Namespace) -> FastAPI:
909
910 @app.exception_handler(RequestValidationError)
911 async def validation_exception_handler(_: Request, exc: RequestValidationError):
912 + from vllm.entrypoints.openai.protocol import VLLMValidationError
913 +
914 + param = None
915 + for error in exc.errors():
916 + if "ctx" in error and "error" in error["ctx"]:
917 + ctx_error = error["ctx"]["error"]
918 + if isinstance(ctx_error, VLLMValidationError):
919 + param = ctx_error.parameter
920 + break
921 +
922 exc_str = str(exc)
923 errors_str = str(exc.errors())
924
@@ -922,6 +932,7 @@ def build_app(args: Namespace) -> FastAPI:
932 message=message,
933 type=HTTPStatus.BAD_REQUEST.phrase,
934 code=HTTPStatus.BAD_REQUEST,
935 + param=param,
936 )
937 )
938 return JSONResponse(err.model_dump(), status_code=HTTPStatus.BAD_REQUEST)
vllm/entrypoints/openai/protocol.py
+91 -17
@@ -131,6 +131,36 @@ class ErrorResponse(OpenAIBaseModel):
131 error: ErrorInfo
132
133
134 +class VLLMValidationError(ValueError):
135 + """vLLM-specific validation error for request validation failures.
136 +
137 + Args:
138 + message: The error message describing the validation failure.
139 + parameter: Optional parameter name that failed validation.
140 + value: Optional value that was rejected during validation.
141 + """
142 +
143 + def __init__(
144 + self,
145 + message: str,
146 + *,
147 + parameter: str | None = None,
148 + value: Any = None,
149 + ) -> None:
150 + super().__init__(message)
151 + self.parameter = parameter
152 + self.value = value
153 +
154 + def __str__(self):
155 + base = super().__str__()
156 + extras = []
157 + if self.parameter is not None:
158 + extras.append(f"parameter={self.parameter}")
159 + if self.value is not None:
160 + extras.append(f"value={self.value}")
161 + return f"{base} ({', '.join(extras)})" if extras else base
162 +
163 +
164 class ModelPermission(OpenAIBaseModel):
165 id: str = Field(default_factory=lambda: f"modelperm-{random_uuid()}")
166 object: str = "model_permission"
@@ -466,7 +496,9 @@ class ResponsesRequest(OpenAIBaseModel):
496 @model_validator(mode="before")
497 def validate_prompt(cls, data):
498 if data.get("prompt") is not None:
469 - raise ValueError("prompt template is not supported")
499 + raise VLLMValidationError(
500 + "prompt template is not supported", parameter="prompt"
501 + )
502 return data
503
504 @model_validator(mode="before")
@@ -850,7 +882,10 @@ class ChatCompletionRequest(OpenAIBaseModel):
882 @classmethod
883 def validate_stream_options(cls, data):
884 if data.get("stream_options") and not data.get("stream"):
853 - raise ValueError("Stream options can only be defined when `stream=True`.")
885 + raise VLLMValidationError(
886 + "Stream options can only be defined when `stream=True`.",
887 + parameter="stream_options",
888 + )
889
890 return data
891
@@ -859,19 +894,29 @@ class ChatCompletionRequest(OpenAIBaseModel):
894 def check_logprobs(cls, data):
895 if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
896 if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
862 - raise ValueError(
863 - "`prompt_logprobs` are not available when `stream=True`."
897 + raise VLLMValidationError(
898 + "`prompt_logprobs` are not available when `stream=True`.",
899 + parameter="prompt_logprobs",
900 )
901
902 if prompt_logprobs < 0 and prompt_logprobs != -1:
867 - raise ValueError("`prompt_logprobs` must be a positive value or -1.")
903 + raise VLLMValidationError(
904 + "`prompt_logprobs` must be a positive value or -1.",
905 + parameter="prompt_logprobs",
906 + value=prompt_logprobs,
907 + )
908 if (top_logprobs := data.get("top_logprobs")) is not None:
909 if top_logprobs < 0 and top_logprobs != -1:
870 - raise ValueError("`top_logprobs` must be a positive value or -1.")
910 + raise VLLMValidationError(
911 + "`top_logprobs` must be a positive value or -1.",
912 + parameter="top_logprobs",
913 + value=top_logprobs,
914 + )
915
916 if (top_logprobs == -1 or top_logprobs > 0) and not data.get("logprobs"):
873 - raise ValueError(
874 - "when using `top_logprobs`, `logprobs` must be set to true."
917 + raise VLLMValidationError(
918 + "when using `top_logprobs`, `logprobs` must be set to true.",
919 + parameter="top_logprobs",
920 )
921
922 return data
@@ -1285,9 +1330,10 @@ class CompletionRequest(OpenAIBaseModel):
1330 for k in ("json", "regex", "choice")
1331 )
1332 if count > 1:
1288 - raise ValueError(
1333 + raise VLLMValidationError(
1334 "You can only use one kind of constraints for structured "
1290 - "outputs ('json', 'regex' or 'choice')."
1335 + "outputs ('json', 'regex' or 'choice').",
1336 + parameter="structured_outputs",
1337 )
1338 return data
1339
@@ -1296,14 +1342,23 @@ class CompletionRequest(OpenAIBaseModel):
1342 def check_logprobs(cls, data):
1343 if (prompt_logprobs := data.get("prompt_logprobs")) is not None:
1344 if data.get("stream") and (prompt_logprobs > 0 or prompt_logprobs == -1):
1299 - raise ValueError(
1300 - "`prompt_logprobs` are not available when `stream=True`."
1345 + raise VLLMValidationError(
1346 + "`prompt_logprobs` are not available when `stream=True`.",
1347 + parameter="prompt_logprobs",
1348 )
1349
1350 if prompt_logprobs < 0 and prompt_logprobs != -1:
1304 - raise ValueError("`prompt_logprobs` must be a positive value or -1.")
1351 + raise VLLMValidationError(
1352 + "`prompt_logprobs` must be a positive value or -1.",
1353 + parameter="prompt_logprobs",
1354 + value=prompt_logprobs,
1355 + )
1356 if (logprobs := data.get("logprobs")) is not None and logprobs < 0:
1306 - raise ValueError("`logprobs` must be a positive value.")
1357 + raise VLLMValidationError(
1358 + "`logprobs` must be a positive value.",
1359 + parameter="logprobs",
1360 + value=logprobs,
1361 + )
1362
1363 return data
1364
@@ -1311,7 +1366,10 @@ class CompletionRequest(OpenAIBaseModel):
1366 @classmethod
1367 def validate_stream_options(cls, data):
1368 if data.get("stream_options") and not data.get("stream"):
1314 - raise ValueError("Stream options can only be defined when `stream=True`.")
1369 + raise VLLMValidationError(
1370 + "Stream options can only be defined when `stream=True`.",
1371 + parameter="stream_options",
1372 + )
1373
1374 return data
1375
@@ -2138,7 +2196,15 @@ class TranscriptionRequest(OpenAIBaseModel):
2196 stream_opts = ["stream_include_usage", "stream_continuous_usage_stats"]
2197 stream = data.get("stream", False)
2198 if any(bool(data.get(so, False)) for so in stream_opts) and not stream:
2141 - raise ValueError("Stream options can only be defined when `stream=True`.")
2199 + # Find which specific stream option was set
2200 + invalid_param = next(
2201 + (so for so in stream_opts if data.get(so, False)),
2202 + "stream_include_usage",
2203 + )
2204 + raise VLLMValidationError(
2205 + "Stream options can only be defined when `stream=True`.",
2206 + parameter=invalid_param,
2207 + )
2208
2209 return data
2210
@@ -2351,7 +2417,15 @@ class TranslationRequest(OpenAIBaseModel):
2417 stream_opts = ["stream_include_usage", "stream_continuous_usage_stats"]
2418 stream = data.get("stream", False)
2419 if any(bool(data.get(so, False)) for so in stream_opts) and not stream:
2354 - raise ValueError("Stream options can only be defined when `stream=True`.")
2420 + # Find which specific stream option was set
2421 + invalid_param = next(
2422 + (so for so in stream_opts if data.get(so, False)),
2423 + "stream_include_usage",
2424 + )
2425 + raise VLLMValidationError(
2426 + "Stream options can only be defined when `stream=True`.",
2427 + parameter=invalid_param,
2428 + )
2429
2430 return data
2431
vllm/entrypoints/openai/serving_chat.py
+5 -9
@@ -417,8 +417,7 @@ class OpenAIServingChat(OpenAIServing):
417
418 generators.append(generator)
419 except ValueError as e:
420 - # TODO: Use a vllm-specific Validation Error
421 - return self.create_error_response(str(e))
420 + return self.create_error_response(e)
421
422 assert len(generators) == 1
423 (result_generator,) = generators
@@ -448,8 +447,7 @@ class OpenAIServingChat(OpenAIServing):
447 except GenerationError as e:
448 return self._convert_generation_error_to_response(e)
449 except ValueError as e:
451 - # TODO: Use a vllm-specific Validation Error
452 - return self.create_error_response(str(e))
450 + return self.create_error_response(e)
451
452 def get_chat_request_role(self, request: ChatCompletionRequest) -> str:
453 if request.add_generation_prompt:
@@ -682,7 +680,7 @@ class OpenAIServingChat(OpenAIServing):
680 tool_parsers = [None] * num_choices
681 except Exception as e:
682 logger.exception("Error in tool parser creation.")
685 - data = self.create_streaming_error_response(str(e))
683 + data = self.create_streaming_error_response(e)
684 yield f"data: {data}\n\n"
685 yield "data: [DONE]\n\n"
686 return
@@ -1328,9 +1326,8 @@ class OpenAIServingChat(OpenAIServing):
1326 except GenerationError as e:
1327 yield f"data: {self._convert_generation_error_to_streaming_response(e)}\n\n"
1328 except Exception as e:
1331 - # TODO: Use a vllm-specific Validation Error
1329 logger.exception("Error in chat completion stream generator.")
1333 - data = self.create_streaming_error_response(str(e))
1330 + data = self.create_streaming_error_response(e)
1331 yield f"data: {data}\n\n"
1332 # Send the final done message after all response.n are finished
1333 yield "data: [DONE]\n\n"
@@ -1354,8 +1351,7 @@ class OpenAIServingChat(OpenAIServing):
1351 except asyncio.CancelledError:
1352 return self.create_error_response("Client disconnected")
1353 except ValueError as e:
1357 - # TODO: Use a vllm-specific Validation Error
1358 - return self.create_error_response(str(e))
1354 + return self.create_error_response(e)
1355
1356 assert final_res is not None
1357
vllm/entrypoints/openai/serving_completion.py
+18 -8
@@ -23,6 +23,7 @@ from vllm.entrypoints.openai.protocol import (
23 PromptTokenUsageInfo,
24 RequestResponseMetadata,
25 UsageInfo,
26 + VLLMValidationError,
27 )
28 from vllm.entrypoints.openai.serving_engine import (
29 GenerationError,
@@ -247,8 +248,7 @@ class OpenAIServingCompletion(OpenAIServing):
248
249 generators.append(generator)
250 except ValueError as e:
250 - # TODO: Use a vllm-specific Validation Error
251 - return self.create_error_response(str(e))
251 + return self.create_error_response(e)
252
253 result_generator = merge_async_iterators(*generators)
254
@@ -308,8 +308,7 @@ class OpenAIServingCompletion(OpenAIServing):
308 except GenerationError as e:
309 return self._convert_generation_error_to_response(e)
310 except ValueError as e:
311 - # TODO: Use a vllm-specific Validation Error
312 - return self.create_error_response(str(e))
311 + return self.create_error_response(e)
312
313 # When user requests streaming but we don't stream, we still need to
314 # return a streaming response with a single event.
@@ -510,9 +509,8 @@ class OpenAIServingCompletion(OpenAIServing):
509 except GenerationError as e:
510 yield f"data: {self._convert_generation_error_to_streaming_response(e)}\n\n"
511 except Exception as e:
513 - # TODO: Use a vllm-specific Validation Error
512 logger.exception("Error in completion stream generator.")
515 - data = self.create_streaming_error_response(str(e))
513 + data = self.create_streaming_error_response(e)
514 yield f"data: {data}\n\n"
515 yield "data: [DONE]\n\n"
516
@@ -660,8 +658,11 @@ class OpenAIServingCompletion(OpenAIServing):
658 token = f"token_id:{token_id}"
659 else:
660 if tokenizer is None:
663 - raise ValueError(
664 - "Unable to get tokenizer because `skip_tokenizer_init=True`"
661 + raise VLLMValidationError(
662 + "Unable to get tokenizer because "
663 + "`skip_tokenizer_init=True`",
664 + parameter="skip_tokenizer_init",
665 + value=True,
666 )
667
668 token = tokenizer.decode(token_id)
@@ -720,6 +721,15 @@ class OpenAIServingCompletion(OpenAIServing):
721 request: CompletionRequest,
722 max_input_length: int | None = None,
723 ) -> RenderConfig:
724 + # Validate max_tokens before using it
725 + if request.max_tokens is not None and request.max_tokens > self.max_model_len:
726 + raise VLLMValidationError(
727 + f"'max_tokens' ({request.max_tokens}) cannot be greater than "
728 + f"the model's maximum context length ({self.max_model_len}).",
729 + parameter="max_tokens",
730 + value=request.max_tokens,
731 + )
732 +
733 max_input_tokens_len = self.max_model_len - (request.max_tokens or 0)
734 return RenderConfig(
735 max_length=max_input_tokens_len,
vllm/entrypoints/openai/serving_engine.py
+62 -15
@@ -57,6 +57,7 @@ from vllm.entrypoints.openai.protocol import (
57 TranscriptionRequest,
58 TranscriptionResponse,
59 TranslationRequest,
60 + VLLMValidationError,
61 )
62 from vllm.entrypoints.openai.serving_models import OpenAIServingModels
63 from vllm.entrypoints.pooling.classify.protocol import (
@@ -322,8 +323,10 @@ class OpenAIServing:
323 input_processor = self.input_processor
324 tokenizer = input_processor.tokenizer
325 if tokenizer is None:
325 - raise ValueError(
326 - "You cannot use beam search when `skip_tokenizer_init=True`"
326 + raise VLLMValidationError(
327 + "You cannot use beam search when `skip_tokenizer_init=True`",
328 + parameter="skip_tokenizer_init",
329 + value=True,
330 )
331
332 eos_token_id: int = tokenizer.eos_token_id # type: ignore
@@ -706,8 +709,7 @@ class OpenAIServing:
709 return None
710
711 except Exception as e:
709 - # TODO: Use a vllm-specific Validation Error
710 - return self.create_error_response(str(e))
712 + return self.create_error_response(e)
713
714 async def _collect_batch(
715 self,
@@ -738,14 +740,43 @@ class OpenAIServing:
740 return None
741
742 except Exception as e:
741 - return self.create_error_response(str(e))
743 + return self.create_error_response(e)
744
745 def create_error_response(
746 self,
745 - message: str,
747 + message: str | Exception,
748 err_type: str = "BadRequestError",
749 status_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
750 + param: str | None = None,
751 ) -> ErrorResponse:
752 + exc: Exception | None = None
753 +
754 + if isinstance(message, Exception):
755 + exc = message
756 +
757 + from vllm.entrypoints.openai.protocol import VLLMValidationError
758 +
759 + if isinstance(exc, VLLMValidationError):
760 + err_type = "BadRequestError"
761 + status_code = HTTPStatus.BAD_REQUEST
762 + param = exc.parameter
763 + elif isinstance(exc, (ValueError, TypeError, RuntimeError)):
764 + # Common validation errors from user input
765 + err_type = "BadRequestError"
766 + status_code = HTTPStatus.BAD_REQUEST
767 + param = None
768 + elif exc.__class__.__name__ == "TemplateError":
769 + # jinja2.TemplateError (avoid importing jinja2)
770 + err_type = "BadRequestError"
771 + status_code = HTTPStatus.BAD_REQUEST
772 + param = None
773 + else:
774 + err_type = "InternalServerError"
775 + status_code = HTTPStatus.INTERNAL_SERVER_ERROR
776 + param = None
777 +
778 + message = str(exc)
779 +
780 if self.log_error_stack:
781 exc_type, _, _ = sys.exc_info()
782 if exc_type is not None:
@@ -753,18 +784,27 @@ class OpenAIServing:
784 else:
785 traceback.print_stack()
786 return ErrorResponse(
756 - error=ErrorInfo(message=message, type=err_type, code=status_code.value)
787 + error=ErrorInfo(
788 + message=message,
789 + type=err_type,
790 + code=status_code.value,
791 + param=param,
792 + )
793 )
794
795 def create_streaming_error_response(
796 self,
761 - message: str,
797 + message: str | Exception,
798 err_type: str = "BadRequestError",
799 status_code: HTTPStatus = HTTPStatus.BAD_REQUEST,
800 + param: str | None = None,
801 ) -> str:
802 json_str = json.dumps(
803 self.create_error_response(
767 - message=message, err_type=err_type, status_code=status_code
804 + message=message,
805 + err_type=err_type,
806 + status_code=status_code,
807 + param=param,
808 ).model_dump()
809 )
810 return json_str
@@ -825,6 +865,7 @@ class OpenAIServing:
865 message=f"The model `{request.model}` does not exist.",
866 err_type="NotFoundError",
867 status_code=HTTPStatus.NOT_FOUND,
868 + param="model",
869 )
870
871 def _get_active_default_mm_loras(self, request: AnyRequest) -> LoRARequest | None:
@@ -991,11 +1032,13 @@ class OpenAIServing:
1032 ClassificationChatRequest: "classification",
1033 }
1034 operation = operations.get(type(request), "embedding generation")
994 - raise ValueError(
1035 + raise VLLMValidationError(
1036 f"This model's maximum context length is "
1037 f"{self.max_model_len} tokens. However, you requested "
1038 f"{token_num} tokens in the input for {operation}. "
998 - f"Please reduce the length of the input."
1039 + f"Please reduce the length of the input.",
1040 + parameter="input_tokens",
1041 + value=token_num,
1042 )
1043 return TokensPrompt(prompt=input_text, prompt_token_ids=input_ids)
1044
@@ -1017,20 +1060,24 @@ class OpenAIServing:
1060 # Note: input length can be up to model context length - 1 for
1061 # completion-like requests.
1062 if token_num >= self.max_model_len:
1020 - raise ValueError(
1063 + raise VLLMValidationError(
1064 f"This model's maximum context length is "
1065 f"{self.max_model_len} tokens. However, your request has "
1066 f"{token_num} input tokens. Please reduce the length of "
1024 - "the input messages."
1067 + "the input messages.",
1068 + parameter="input_tokens",
1069 + value=token_num,
1070 )
1071
1072 if max_tokens is not None and token_num + max_tokens > self.max_model_len:
1028 - raise ValueError(
1073 + raise VLLMValidationError(
1074 "'max_tokens' or 'max_completion_tokens' is too large: "
1075 f"{max_tokens}. This model's maximum context length is "
1076 f"{self.max_model_len} tokens and your request has "
1077 f"{token_num} input tokens ({max_tokens} > {self.max_model_len}"
1033 - f" - {token_num})."
1078 + f" - {token_num}).",
1079 + parameter="max_tokens",
1080 + value=max_tokens,
1081 )
1082
1083 return TokensPrompt(prompt=input_text, prompt_token_ids=input_ids)
vllm/entrypoints/openai/serving_responses.py
+18 -8
@@ -94,6 +94,7 @@ from vllm.entrypoints.openai.protocol import (
94 ResponsesResponse,
95 ResponseUsage,
96 StreamingResponsesResponse,
97 + VLLMValidationError,
98 )
99 from vllm.entrypoints.openai.serving_engine import (
100 GenerationError,
@@ -271,6 +272,7 @@ class OpenAIServingResponses(OpenAIServing):
272 err_type="invalid_request_error",
273 message=error_message,
274 status_code=HTTPStatus.BAD_REQUEST,
275 + param="input",
276 )
277 return None
278
@@ -282,6 +284,7 @@ class OpenAIServingResponses(OpenAIServing):
284 err_type="invalid_request_error",
285 message="logprobs are not supported with gpt-oss models",
286 status_code=HTTPStatus.BAD_REQUEST,
287 + param="logprobs",
288 )
289 if request.store and not self.enable_store and request.background:
290 return self.create_error_response(
@@ -294,6 +297,7 @@ class OpenAIServingResponses(OpenAIServing):
297 "the vLLM server."
298 ),
299 status_code=HTTPStatus.BAD_REQUEST,
300 + param="background",
301 )
302 if request.previous_input_messages and request.previous_response_id:
303 return self.create_error_response(
@@ -301,6 +305,7 @@ class OpenAIServingResponses(OpenAIServing):
305 message="Only one of `previous_input_messages` and "
306 "`previous_response_id` can be set.",
307 status_code=HTTPStatus.BAD_REQUEST,
308 + param="previous_response_id",
309 )
310 return None
311
@@ -457,8 +462,7 @@ class OpenAIServingResponses(OpenAIServing):
462 )
463 generators.append(generator)
464 except ValueError as e:
460 - # TODO: Use a vllm-specific Validation Error
461 - return self.create_error_response(str(e))
465 + return self.create_error_response(e)
466
467 assert len(generators) == 1
468 (result_generator,) = generators
@@ -546,7 +550,7 @@ class OpenAIServingResponses(OpenAIServing):
550 except GenerationError as e:
551 return self._convert_generation_error_to_response(e)
552 except Exception as e:
549 - return self.create_error_response(str(e))
553 + return self.create_error_response(e)
554
555 async def _make_request(
556 self,
@@ -630,8 +634,7 @@ class OpenAIServingResponses(OpenAIServing):
634 except asyncio.CancelledError:
635 return self.create_error_response("Client disconnected")
636 except ValueError as e:
633 - # TODO: Use a vllm-specific Validation Error
634 - return self.create_error_response(str(e))
637 + return self.create_error_response(e)
638
639 # NOTE: Implementation of stauts is still WIP, but for now
640 # we guarantee that if the status is not "completed", it is accurate.
@@ -1074,7 +1077,7 @@ class OpenAIServingResponses(OpenAIServing):
1077 response = self._convert_generation_error_to_response(e)
1078 except Exception as e:
1079 logger.exception("Background request failed for %s", request.request_id)
1077 - response = self.create_error_response(str(e))
1080 + response = self.create_error_response(e)
1081 finally:
1082 new_event_signal.set()
1083
@@ -1099,7 +1102,7 @@ class OpenAIServingResponses(OpenAIServing):
1102 response = self._convert_generation_error_to_response(e)
1103 except Exception as e:
1104 logger.exception("Background request failed for %s", request.request_id)
1102 - response = self.create_error_response(str(e))
1105 + response = self.create_error_response(e)
1106
1107 if isinstance(response, ErrorResponse):
1108 # If the request has failed, update the status to "failed".
@@ -1116,7 +1119,11 @@ class OpenAIServingResponses(OpenAIServing):
1119 starting_after: int | None = None,
1120 ) -> AsyncGenerator[StreamingResponsesResponse, None]:
1121 if response_id not in self.event_store:
1119 - raise ValueError(f"Unknown response_id: {response_id}")
1122 + raise VLLMValidationError(
1123 + f"Unknown response_id: {response_id}",
1124 + parameter="response_id",
1125 + value=response_id,
1126 + )
1127
1128 event_deque, new_event_signal = self.event_store[response_id]
1129 start_index = 0 if starting_after is None else starting_after + 1
@@ -1172,6 +1179,7 @@ class OpenAIServingResponses(OpenAIServing):
1179 return self.create_error_response(
1180 err_type="invalid_request_error",
1181 message="Cannot cancel a synchronous response.",
1182 + param="response_id",
1183 )
1184
1185 # Update the status to "cancelled".
@@ -1191,6 +1199,7 @@ class OpenAIServingResponses(OpenAIServing):
1199 err_type="invalid_request_error",
1200 message=f"Response with id '{response_id}' not found.",
1201 status_code=HTTPStatus.NOT_FOUND,
1202 + param="response_id",
1203 )
1204
1205 def _make_store_not_supported_error(self) -> ErrorResponse:
@@ -1203,6 +1212,7 @@ class OpenAIServingResponses(OpenAIServing):
1212 "starting the vLLM server."
1213 ),
1214 status_code=HTTPStatus.BAD_REQUEST,
1215 + param="store",
1216 )
1217
1218 async def _process_simple_streaming_events(
vllm/entrypoints/openai/speech_to_text.py
+19 -11
@@ -30,6 +30,7 @@ from vllm.entrypoints.openai.protocol import (
30 TranslationSegment,
31 TranslationStreamResponse,
32 UsageInfo,
33 + VLLMValidationError,
34 )
35 from vllm.entrypoints.openai.serving_engine import OpenAIServing, SpeechToTextRequest
36 from vllm.entrypoints.openai.serving_models import OpenAIServingModels
@@ -259,7 +260,11 @@ class OpenAISpeechToText(OpenAIServing):
260 )
261
262 if len(audio_data) / 1024**2 > self.max_audio_filesize_mb:
262 - raise ValueError("Maximum file size exceeded.")
263 + raise VLLMValidationError(
264 + "Maximum file size exceeded",
265 + parameter="audio_filesize_mb",
266 + value=len(audio_data) / 1024**2,
267 + )
268
269 with io.BytesIO(audio_data) as bytes_:
270 # NOTE resample to model SR here for efficiency. This is also a
@@ -287,12 +292,18 @@ class OpenAISpeechToText(OpenAIServing):
292 )
293 if request.response_format == "verbose_json":
294 if not isinstance(prompt, dict):
290 - raise ValueError(f"Expected prompt to be a dict,got {type(prompt)}")
295 + raise VLLMValidationError(
296 + "Expected prompt to be a dict",
297 + parameter="prompt",
298 + value=type(prompt).__name__,
299 + )
300 prompt_dict = cast(dict, prompt)
301 decoder_prompt = prompt.get("decoder_prompt")
302 if not isinstance(decoder_prompt, str):
294 - raise ValueError(
295 - f"Expected decoder_prompt to bestr, got {type(decoder_prompt)}"
303 + raise VLLMValidationError(
304 + "Expected decoder_prompt to be str",
305 + parameter="decoder_prompt",
306 + value=type(decoder_prompt).__name__,
307 )
308 prompt_dict["decoder_prompt"] = decoder_prompt.replace(
309 "<|notimestamps|>", "<|0.00|>"
@@ -412,7 +423,7 @@ class OpenAISpeechToText(OpenAIServing):
423
424 except ValueError as e:
425 logger.exception("Error in preprocessing prompt inputs")
415 - return self.create_error_response(str(e))
426 + return self.create_error_response(e)
427
428 list_result_generator: list[AsyncGenerator[RequestOutput, None]] | None = None
429 try:
@@ -448,8 +459,7 @@ class OpenAISpeechToText(OpenAIServing):
459 for i, prompt in enumerate(prompts)
460 ]
461 except ValueError as e:
451 - # TODO: Use a vllm-specific Validation Error
452 - return self.create_error_response(str(e))
462 + return self.create_error_response(e)
463
464 if request.stream:
465 return stream_generator_method(
@@ -523,8 +533,7 @@ class OpenAISpeechToText(OpenAIServing):
533 except asyncio.CancelledError:
534 return self.create_error_response("Client disconnected")
535 except ValueError as e:
526 - # TODO: Use a vllm-specific Validation Error
527 - return self.create_error_response(str(e))
536 + return self.create_error_response(e)
537
538 async def _speech_to_text_stream_generator(
539 self,
@@ -634,9 +643,8 @@ class OpenAISpeechToText(OpenAIServing):
643 )
644
645 except Exception as e:
637 - # TODO: Use a vllm-specific Validation Error
646 logger.exception("Error in %s stream generator.", self.task_type)
639 - data = self.create_streaming_error_response(str(e))
647 + data = self.create_streaming_error_response(e)
648 yield f"data: {data}\n\n"
649 # Send the final done message after all response.n are finished
650 yield "data: [DONE]\n\n"
vllm/entrypoints/renderer.py
+8 -4
@@ -12,6 +12,7 @@ import torch
12 from pydantic import Field
13
14 from vllm.config import ModelConfig
15 +from vllm.entrypoints.openai.protocol import VLLMValidationError
16 from vllm.inputs.data import EmbedsPrompt, TextPrompt, TokensPrompt
17 from vllm.inputs.parse import get_prompt_components, parse_raw_prompts
18 from vllm.tokenizers import TokenizerLike
@@ -162,8 +163,9 @@ class BaseRenderer(ABC):
163 ) -> list[EmbedsPrompt]:
164 """Load and validate base64-encoded embeddings into prompt objects."""
165 if not self.model_config.enable_prompt_embeds:
165 - raise ValueError(
166 - "You must set `--enable-prompt-embeds` to input `prompt_embeds`."
166 + raise VLLMValidationError(
167 + "You must set `--enable-prompt-embeds` to input `prompt_embeds`.",
168 + parameter="prompt_embeds",
169 )
170
171 def _load_and_validate_embed(embed: bytes) -> EmbedsPrompt:
@@ -396,10 +398,12 @@ class CompletionRenderer(BaseRenderer):
398 ) -> TokensPrompt:
399 """Create validated TokensPrompt."""
400 if max_length is not None and len(token_ids) > max_length:
399 - raise ValueError(
401 + raise VLLMValidationError(
402 f"This model's maximum context length is {max_length} tokens. "
403 f"However, your request has {len(token_ids)} input tokens. "
402 - "Please reduce the length of the input messages."
404 + "Please reduce the length of the input messages.",
405 + parameter="input_tokens",
406 + value=len(token_ids),
407 )
408
409 tokens_prompt = TokensPrompt(prompt_token_ids=token_ids)