# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project """ Tests for max_tokens_per_doc and max_tokens_per_query. """ import json import os from dataclasses import dataclass import pytest import requests from tests.utils import VLLM_PATH, RemoteOpenAIServer from vllm.entrypoints.pooling.scoring.protocol import RerankResponse os.environ["VLLM_LOGGING_LEVEL"] = "WARNING" TEMPLATE_DIR = str(VLLM_PATH / "examples/pooling/score/template") ExpectedPromptTokens = int | tuple[int, ...] long_query = "What is the capital of France?" * 20 long_doc = "The capital of France is Paris. " * 20 @dataclass class TestConfig: model: str args: list[str] without_truncated_prompt_tokens: ExpectedPromptTokens with_max_tokens_per_query_prompt_tokens: ExpectedPromptTokens with_max_tokens_per_doc_prompt_tokens: ExpectedPromptTokens with_max_tokens_per_query_and_doc_prompt_tokens: ExpectedPromptTokens RERANK_CONFIGS = [ # 1. cross-encoder TestConfig( model="jinaai/jina-reranker-v2-base-multilingual", args=[ "--enforce-eager", "--max-model-len", "1024", "--trust-remote-code", ], without_truncated_prompt_tokens=284, with_max_tokens_per_query_prompt_tokens=154, with_max_tokens_per_doc_prompt_tokens=154, with_max_tokens_per_query_and_doc_prompt_tokens=24, ), # 2. cross-encoder + score template TestConfig( model="Qwen/Qwen3-Reranker-0.6B", args=[ "--enforce-eager", "--max-model-len", "1024", "--hf-overrides", json.dumps( { "architectures": ["Qwen3ForSequenceClassification"], "classifier_from_token": ["no", "yes"], "is_original_qwen3_reranker": True, } ), "--chat-template", os.path.join(TEMPLATE_DIR, "qwen3_reranker.jinja"), ], without_truncated_prompt_tokens=352, with_max_tokens_per_query_prompt_tokens=223, with_max_tokens_per_doc_prompt_tokens=221, with_max_tokens_per_query_and_doc_prompt_tokens=92, ), # 3. bi-encoder TestConfig( model="intfloat/multilingual-e5-small", args=[ "--enforce-eager", "--max-model-len", "512", "--trust-remote-code", ], # This model has produced both prompt-token totals in CI/local cache; # keep truncation checks exact while tolerating the boundary delta. without_truncated_prompt_tokens=(285, 286), with_max_tokens_per_query_prompt_tokens=(155, 156), with_max_tokens_per_doc_prompt_tokens=155, with_max_tokens_per_query_and_doc_prompt_tokens=25, ), # 4. late-interaction TestConfig( model="answerdotai/answerai-colbert-small-v1", args=[ "--enforce-eager", "--max-model-len", "512", "--trust-remote-code", ], without_truncated_prompt_tokens=285, with_max_tokens_per_query_prompt_tokens=155, with_max_tokens_per_doc_prompt_tokens=155, with_max_tokens_per_query_and_doc_prompt_tokens=25, ), # 5. jinaai/jina-reranker-v3 TestConfig( model="jinaai/jina-reranker-v3", args=[ "--enforce-eager", "--max-model-len", "1024", "--trust-remote-code", ], without_truncated_prompt_tokens=567, with_max_tokens_per_query_prompt_tokens=308, with_max_tokens_per_doc_prompt_tokens=436, with_max_tokens_per_query_and_doc_prompt_tokens=177, ), ] def assert_prompt_tokens(actual: int, expected: ExpectedPromptTokens) -> None: if isinstance(expected, int): assert actual == expected else: assert actual in expected @pytest.fixture(scope="module", params=RERANK_CONFIGS, ids=lambda c: c.model) def server(request): config: TestConfig = request.param with RemoteOpenAIServer(config.model, config.args) as remote_server: yield config, remote_server def test_without_truncated(server): """Test that max_tokens_per_doc truncates documents correctly.""" config, remote_server = server response = requests.post( remote_server.url_for("rerank"), json={"model": config.model, "query": long_query, "documents": [long_doc]}, ) response.raise_for_status() rerank = RerankResponse.model_validate(response.json()) assert rerank.id is not None assert rerank.results is not None assert len(rerank.results) == 1 assert_prompt_tokens( rerank.usage.prompt_tokens, config.without_truncated_prompt_tokens, ) def test_max_tokens_per_query(server): """Test that max_tokens_per_doc truncates documents correctly.""" config, remote_server = server response = requests.post( remote_server.url_for("rerank"), json={ "model": config.model, "query": long_query, "documents": [long_doc], "max_tokens_per_query": 10, }, ) response.raise_for_status() rerank = RerankResponse.model_validate(response.json()) assert rerank.id is not None assert rerank.results is not None assert len(rerank.results) == 1 assert_prompt_tokens( rerank.usage.prompt_tokens, config.with_max_tokens_per_query_prompt_tokens, ) def test_max_tokens_per_doc(server): """Test that max_tokens_per_doc truncates documents correctly.""" config, remote_server = server response = requests.post( remote_server.url_for("rerank"), json={ "model": config.model, "query": long_query, "documents": [long_doc], "max_tokens_per_doc": 10, }, ) response.raise_for_status() rerank = RerankResponse.model_validate(response.json()) assert rerank.id is not None assert rerank.results is not None assert len(rerank.results) == 1 assert_prompt_tokens( rerank.usage.prompt_tokens, config.with_max_tokens_per_doc_prompt_tokens, ) def test_max_tokens_per_query_and_doc(server): """Test that max_tokens_per_doc truncates documents correctly.""" config, remote_server = server response = requests.post( remote_server.url_for("rerank"), json={ "model": config.model, "query": long_query, "documents": [long_doc], "max_tokens_per_query": 10, "max_tokens_per_doc": 10, }, ) response.raise_for_status() rerank = RerankResponse.model_validate(response.json()) assert rerank.id is not None assert rerank.results is not None assert len(rerank.results) == 1 assert_prompt_tokens( rerank.usage.prompt_tokens, config.with_max_tokens_per_query_and_doc_prompt_tokens, )