[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