From 81d8f4ebacaf4b0bf85ec559d5a0db1bcf5ade87 Mon Sep 17 00:00:00 2001 From: Taneem Ibrahim Date: Tue, 16 Jun 2026 00:42:43 -0500 Subject: [PATCH] [Misc] Added validation for Cohere /v2/embed input field exclusivity (#45640) Signed-off-by: Taneem Ibrahim --- .../pooling/embed/test_io_processor.py | 55 ++++++++++++++++++- vllm/entrypoints/pooling/embed/protocol.py | 11 ++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/tests/entrypoints/pooling/embed/test_io_processor.py b/tests/entrypoints/pooling/embed/test_io_processor.py index f4f1f4aa40..f0dea74044 100644 --- a/tests/entrypoints/pooling/embed/test_io_processor.py +++ b/tests/entrypoints/pooling/embed/test_io_processor.py @@ -3,7 +3,7 @@ """Unit tests for EmbedIOProcessor.""" import pytest -from pydantic import TypeAdapter +from pydantic import TypeAdapter, ValidationError from vllm import PoolingParams from vllm.entrypoints.pooling.embed.io_processor import EmbedIOProcessor @@ -105,6 +105,59 @@ class TestEmbeddingRequestParsing: assert request.chat_template_kwargs == {"instruction": "Represent the query: "} +class TestCohereEmbedRequestParsing: + """Unit tests for Cohere embed request parsing.""" + + @pytest.mark.parametrize( + "request_body", + [ + {"model": "test"}, + {"model": "test", "texts": ["hello"], "images": ["image-uri"]}, + { + "model": "test", + "texts": ["hello"], + "inputs": [ + {"content": [{"type": "text", "text": "hello"}]}, + ], + }, + { + "model": "test", + "images": ["image-uri"], + "inputs": [ + {"content": [{"type": "text", "text": "hello"}]}, + ], + }, + {"model": "test", "texts": []}, + {"model": "test", "images": []}, + {"model": "test", "inputs": []}, + ], + ) + def test_rejects_invalid_input_field_combinations(self, request_body): + with pytest.raises( + ValidationError, + match="Exactly one of texts, images, or inputs must be provided", + ): + CohereEmbedRequest(**request_body) + + @pytest.mark.parametrize( + "request_body", + [ + {"model": "test", "texts": ["hello"]}, + {"model": "test", "images": ["image-uri"]}, + { + "model": "test", + "inputs": [ + {"content": [{"type": "text", "text": "hello"}]}, + ], + }, + ], + ) + def test_accepts_exactly_one_non_empty_input_field(self, request_body): + request = CohereEmbedRequest(**request_body) + + assert request.model == "test" + + class TestResolveTruncation: """Unit tests for EmbedIOProcessor._resolve_cohere_truncation.""" diff --git a/vllm/entrypoints/pooling/embed/protocol.py b/vllm/entrypoints/pooling/embed/protocol.py index 99a07e4d82..8ec908f451 100644 --- a/vllm/entrypoints/pooling/embed/protocol.py +++ b/vllm/entrypoints/pooling/embed/protocol.py @@ -224,6 +224,17 @@ class CohereEmbedRequest(BaseModel): max_tokens: int | None = None priority: int = 0 + @model_validator(mode="after") + def validate_input_fields(self): + input_fields = (self.texts, self.images, self.inputs) + provided_fields = [field for field in input_fields if field is not None] + if len(provided_fields) != 1 or not provided_fields[0]: + raise ValueError( + "Exactly one of texts, images, or inputs must be provided, " + "and it must be non-empty" + ) + return self + # --------------------------------------------------------------------------- # Cohere /v2/embed — response models