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)