[Misc] Use VLLMValidationError in Cohere request validation (#52467)

Signed-off-by: frank-suwen <suwenw2@outlook.com> Co-authored-by: Codex <noreply@openai.com>

Frank committed Aug 23, 2026 at 17:52 UTC cd329413e2bb2086c2c97c373dcc3bd1ff29fa9f
3 files changed +36 -9
tests/entrypoints/cohere/test_api_router.py
+8 -3
@@ -36,6 +36,10 @@ from vllm.entrypoints.serve.exception_handling.handlers.http import (
36 from vllm.entrypoints.serve.exception_handling.handlers.validation import (
37 validation_exception_handler,
38 )
39 +from vllm.entrypoints.serve.exception_handling.handlers.vllm_error import (
40 + vllm_error_handler,
41 +)
42 +from vllm.exceptions import VLLMError
43
44
45 @pytest.fixture(autouse=True)
@@ -83,7 +87,7 @@ def _build_app(handler: _Handler | None) -> FastAPI:
87
88 def _build_app_with_vllm_handlers(handler: _Handler | None) -> FastAPI:
89 """Build a FastAPI app that mirrors the real vLLM setup by installing
86 - ``validation_exception_handler`` and ``http_exception_handler``. The
90 + validation, HTTP, and vLLM exception handlers. The
91 :class:`CohereErrorEnvelopeMiddleware` registered by ``attach_router``
92 is expected to translate any resulting vLLM ``ErrorResponse`` body
93 into the ``CohereError`` wire shape.
@@ -96,6 +100,7 @@ def _build_app_with_vllm_handlers(handler: _Handler | None) -> FastAPI:
100 app.state.args = Namespace(log_error_stack=False)
101 app.exception_handler(RequestValidationError)(validation_exception_handler)
102 app.exception_handler(HTTPException)(http_exception_handler)
103 + app.exception_handler(VLLMError)(vllm_error_handler)
104 return app
105
106
@@ -333,8 +338,8 @@ class TestCohereErrorEnvelope:
338
339 def test_validation_error_body_is_cohere_shaped(self):
340 # ``model=""`` and ``messages=[]`` trip our custom field
336 - # validators, which raise pydantic ValueErrors and are routed
337 - # through ``validation_exception_handler`` in the real vLLM
341 + # validators, which raise VLLMValidationError and are routed
342 + # through ``vllm_error_handler`` in the real vLLM
343 # server (producing the ``{"error": {...}}`` shape).
344 app = _build_app_with_vllm_handlers(handler=None)
345 with TestClient(app) as client:
tests/entrypoints/cohere/test_protocol.py
+17 -3
@@ -6,7 +6,8 @@ The module is mostly a thin wrapper around the official ``cohere`` SDK
6 types plus a few local additions:
7
8 * :class:`CohereError` envelope.
9 -* :class:`CohereChatV2Request` (model required, ``max_tokens`` non-negative).
9 +* :class:`CohereChatV2Request` (model and messages required,
10 + ``max_tokens`` non-negative).
11 * :class:`CohereChatV2Response` plus the usage / logprob helpers.
12 * The streaming event subclasses that bake a wire-format ``type``
13 discriminator into ``model_dump()`` so SSE consumers can demux on it.
@@ -33,6 +34,7 @@ from vllm.entrypoints.cohere.protocol import (
34 ToolCallStartEvent,
35 ToolPlanDeltaEvent,
36 )
37 +from vllm.exceptions import VLLMValidationError
38
39 # ======================================================================
40 # CohereError
@@ -52,16 +54,28 @@ class TestCohereError:
54
55 class TestCohereChatV2Request:
56 def test_empty_model_rejected(self):
55 - with pytest.raises(ValidationError, match="model is required"):
57 + with pytest.raises(VLLMValidationError, match="model is required") as exc_info:
58 CohereChatV2Request(model="", messages=[{"role": "user", "content": "hi"}])
59 + assert exc_info.value.parameter == "model"
60 + assert exc_info.value.value is None
61
62 def test_negative_max_tokens_rejected(self):
59 - with pytest.raises(ValidationError, match="non-negative"):
63 + with pytest.raises(VLLMValidationError, match="non-negative") as exc_info:
64 CohereChatV2Request(
65 model="m",
66 messages=[{"role": "user", "content": "hi"}],
67 max_tokens=-1,
68 )
69 + assert exc_info.value.parameter == "max_tokens"
70 + assert exc_info.value.value == -1
71 +
72 + def test_empty_messages_rejected(self):
73 + with pytest.raises(
74 + VLLMValidationError, match="messages must contain at least one message"
75 + ) as exc_info:
76 + CohereChatV2Request(model="m", messages=[])
77 + assert exc_info.value.parameter == "messages"
78 + assert exc_info.value.value is None
79
80 def test_zero_max_tokens_allowed(self):
81 # Zero is allowed (the docs allow 0 -> return prompt only).
vllm/entrypoints/cohere/protocol.py
+11 -3
@@ -46,6 +46,8 @@ from cohere.types import (
46 )
47 from pydantic import BaseModel, Field, field_validator
48
49 +from vllm.exceptions import VLLMValidationError
50 +
51 # Re-export the SDK wire-format types alongside our local extensions so
52 # ``vllm.entrypoints.cohere.serving`` and friends can import everything
53 # they need from this module.
@@ -186,14 +188,18 @@ class CohereChatV2Request(BaseModel):
188 @classmethod
189 def _validate_model(cls, v: str) -> str:
190 if not v:
189 - raise ValueError("model is required")
191 + raise VLLMValidationError("model is required", parameter="model")
192 return v
193
194 @field_validator("max_tokens")
195 @classmethod
196 def _validate_max_tokens(cls, v: int | None) -> int | None:
197 if v is not None and v < 0:
196 - raise ValueError("max_tokens must be non-negative")
198 + raise VLLMValidationError(
199 + "max_tokens must be non-negative",
200 + parameter="max_tokens",
201 + value=v,
202 + )
203 return v
204
205 @field_validator("messages", mode="before")
@@ -233,7 +239,9 @@ class CohereChatV2Request(BaseModel):
239 @classmethod
240 def _validate_messages(cls, v: list[ChatMessageV2]) -> list[ChatMessageV2]:
241 if not v:
236 - raise ValueError("messages must contain at least one message")
242 + raise VLLMValidationError(
243 + "messages must contain at least one message", parameter="messages"
244 + )
245 return v
246
247