Prhub

#45640 [Misc] Added validation for Cohere /v2/embed input field exclusivity

原始 PR 作者 taneem-ibrahim 合并时间 2026-06-16 13:42 文件变更 2 提交数 4 评论 0 代码增减 +65 / -1

执行摘要

Cohere /v2/embed 互斥输入字段校验

Cohere API 文档将 textsimagesinputs 描述为互斥字段,但此前未进行强制校验,导致多个字段同时发送时空字段无法被正确拒绝,行为不明确。本 PR 旨在提前拒绝无效请求并给出清晰错误信息。

值得合入,设计简洁,测试覆盖全面。推荐作为防御性编程的示例参考。

讨论亮点

无 review 评论;PR 获得 noooop 的 approve 且无讨论。

实现拆解

  1. vllm/entrypoints/pooling/embed/protocol.pyCohereEmbedRequest 类中新增 @model_validator(mode="after") 装饰的 validate_input_fields 方法。该方法检查 textsimagesinputs 三个字段,统计非 None 且非空的字段数量,若不等于 1 则抛出 ValueError
  2. tests/entrypoints/pooling/embed/test_io_processor.py 中新增 TestCohereEmbedRequestParsing 测试类,包含参数化测试:
    • test_rejects_invalid_input_field_combinations:覆盖无字段、多个字段、空列表等共 7 种无效场景,断言抛出 ValidationError
    • test_accepts_exactly_one_non_empty_input_field:覆盖每个字段单独提供且非空的 3 种合法场景,验证请求构造成功。
文件 模块 状态 重要度
vllm/entrypoints/pooling/embed/protocol.py 协议定义 modified 6.62
tests/entrypoints/pooling/embed/test_io_processor.py IO 处理器 modified 6.49

关键符号

validate_input_fields

关键源码片段

vllm/entrypoints/pooling/embed/protocol.py core-logic

核心校验逻辑所在文件,新增 `validate_input_fields` 方法。

# vllm/entrypoints/pooling/embed/protocol.py
class CohereEmbedRequest(BaseModel):
    model: str | None = None
    input_type: str | None = None
    texts: list[str] | None = None
    images: list[str] | None = None
    inputs: list[CohereEmbedInput] | None = None
    output_dimension: int | None = None
    embedding_types: list[CohereEmbeddingType] | None = None
    truncate: CohereTruncate = "END"
    max_tokens: int | None = None
    priority: int = 0
​
    # PR #45640: 在模型实例化后 validator 阶段校验字段互斥性
    @model_validator(mode="after")
    def validate_input_fields(self):
        # 三个输入字段:texts, images, inputs
        input_fields = (self.texts, self.images, self.inputs)
        # 筛选出非 None 的字段
        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
tests/entrypoints/pooling/embed/test_io_processor.py test-coverage

测试覆盖合法与非法输入组合,确保校验器正确工作。

# tests/entrypoints/pooling/embed/test_io_processor.py
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"

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险极低。变更仅增加 Pydantic 校验器,未修改现有逻辑或接口约定。可能的风险是校验器使用 mode="after",若其他字段 validator 有依赖需注意顺序,但当前字段无交叉依赖。

对用户:之前发送多个字段或空字段的请求现在会被拒绝,得到 422 错误及明确消息,提升 API 体验。对系统:校验在反序列化阶段完成,不增加运行时开销。对团队:低影响、高收益的防御性编程。

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论