186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Tests for omlx/api/rerank_models.py — the Pydantic schemas served at
|
|
/v1/rerank. Pins down Cohere/Jina compatibility: required fields,
|
|
multimodal query/document shapes, defaults, and the auto-generated
|
|
``id`` prefix that downstream clients filter on.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from omlx.api.rerank_models import (
|
|
RerankRequest,
|
|
RerankResponse,
|
|
RerankResult,
|
|
RerankUsage,
|
|
)
|
|
|
|
|
|
class TestRerankRequest:
|
|
def test_minimal_text_request(self):
|
|
req = RerankRequest(
|
|
model="qwen3-reranker",
|
|
query="best wireless headphones",
|
|
documents=["Sony WH-1000XM5", "Bose QC45"],
|
|
)
|
|
assert req.model == "qwen3-reranker"
|
|
assert req.query == "best wireless headphones"
|
|
assert req.documents == ["Sony WH-1000XM5", "Bose QC45"]
|
|
|
|
def test_defaults(self):
|
|
req = RerankRequest(model="m", query="q", documents=["d"])
|
|
assert req.top_n is None
|
|
assert req.return_documents is True # Cohere-compat default
|
|
assert req.max_chunks_per_doc is None
|
|
|
|
def test_dict_query_for_multimodal(self):
|
|
image = (
|
|
"data:image/png;base64,"
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/"
|
|
"x8AAwMCAO+/p9sAAAAASUVORK5CYII="
|
|
)
|
|
req = RerankRequest(
|
|
model="qwen3-vl-reranker",
|
|
query={"text": "a red car", "image": image},
|
|
documents=["doc1"],
|
|
)
|
|
assert req.query == {"text": "a red car", "image": image}
|
|
|
|
def test_dict_documents_for_multimodal(self):
|
|
req = RerankRequest(
|
|
model="qwen3-vl-reranker",
|
|
query="cars",
|
|
documents=[
|
|
{"text": "ferrari", "image": "data:image/png;base64,AAA"},
|
|
{"text": "porsche"},
|
|
],
|
|
)
|
|
assert isinstance(req.documents[0], dict)
|
|
assert req.documents[0]["image"].startswith("data:image/png")
|
|
|
|
def test_top_n_accepts_int(self):
|
|
req = RerankRequest(model="m", query="q", documents=["a", "b", "c"], top_n=2)
|
|
assert req.top_n == 2
|
|
|
|
def test_missing_model_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankRequest(query="q", documents=["d"]) # type: ignore[call-arg]
|
|
|
|
def test_missing_query_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankRequest(model="m", documents=["d"]) # type: ignore[call-arg]
|
|
|
|
def test_missing_documents_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankRequest(model="m", query="q") # type: ignore[call-arg]
|
|
|
|
def test_return_documents_false_round_trips(self):
|
|
req = RerankRequest(
|
|
model="m", query="q", documents=["d"], return_documents=False
|
|
)
|
|
restored = RerankRequest.model_validate(req.model_dump())
|
|
assert restored.return_documents is False
|
|
|
|
|
|
class TestRerankResult:
|
|
def test_minimal_result_with_no_document(self):
|
|
r = RerankResult(index=3, relevance_score=0.91)
|
|
assert r.index == 3
|
|
assert r.relevance_score == 0.91
|
|
assert r.document is None # return_documents=False path
|
|
|
|
def test_result_with_text_document(self):
|
|
r = RerankResult(
|
|
index=0, relevance_score=0.5, document={"text": "Sony WH-1000XM5"}
|
|
)
|
|
assert r.document == {"text": "Sony WH-1000XM5"}
|
|
|
|
def test_result_preserves_multimodal_document(self):
|
|
r = RerankResult(
|
|
index=1,
|
|
relevance_score=0.3,
|
|
document={"text": "ferrari", "image": "data:image/png;base64,AAA"},
|
|
)
|
|
assert "image" in r.document
|
|
assert r.document["image"].startswith("data:image/png")
|
|
|
|
def test_missing_index_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankResult(relevance_score=0.5) # type: ignore[call-arg]
|
|
|
|
def test_missing_score_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankResult(index=0) # type: ignore[call-arg]
|
|
|
|
|
|
class TestRerankUsage:
|
|
def test_required_field(self):
|
|
u = RerankUsage(total_tokens=42)
|
|
assert u.total_tokens == 42
|
|
|
|
def test_missing_total_tokens_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankUsage() # type: ignore[call-arg]
|
|
|
|
|
|
class TestRerankResponse:
|
|
def test_minimal_response(self):
|
|
resp = RerankResponse(
|
|
results=[RerankResult(index=0, relevance_score=0.9)],
|
|
model="qwen3-reranker",
|
|
)
|
|
assert resp.model == "qwen3-reranker"
|
|
assert len(resp.results) == 1
|
|
assert resp.usage is None # optional
|
|
|
|
def test_auto_id_has_rerank_prefix(self):
|
|
"""Cohere clients filter telemetry on the ``rerank-`` prefix."""
|
|
resp = RerankResponse(results=[], model="m")
|
|
assert resp.id.startswith("rerank-")
|
|
# 8 hex chars after the prefix
|
|
assert len(resp.id) == len("rerank-") + 8
|
|
|
|
def test_two_responses_get_distinct_ids(self):
|
|
a = RerankResponse(results=[], model="m")
|
|
b = RerankResponse(results=[], model="m")
|
|
assert a.id != b.id
|
|
|
|
def test_explicit_id_is_preserved(self):
|
|
resp = RerankResponse(id="rerank-custom123", results=[], model="m")
|
|
assert resp.id == "rerank-custom123"
|
|
|
|
def test_usage_attached(self):
|
|
resp = RerankResponse(
|
|
results=[],
|
|
model="m",
|
|
usage=RerankUsage(total_tokens=128),
|
|
)
|
|
assert resp.usage is not None
|
|
assert resp.usage.total_tokens == 128
|
|
|
|
def test_missing_results_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankResponse(model="m") # type: ignore[call-arg]
|
|
|
|
def test_missing_model_rejected(self):
|
|
with pytest.raises(ValidationError):
|
|
RerankResponse(results=[]) # type: ignore[call-arg]
|
|
|
|
def test_round_trip_via_json(self):
|
|
original = RerankResponse(
|
|
results=[
|
|
RerankResult(index=2, relevance_score=0.95, document={"text": "a"}),
|
|
RerankResult(index=0, relevance_score=0.40, document={"text": "b"}),
|
|
],
|
|
model="qwen3-reranker",
|
|
usage=RerankUsage(total_tokens=64),
|
|
)
|
|
restored = RerankResponse.model_validate_json(original.model_dump_json())
|
|
assert restored.model == original.model
|
|
assert restored.id == original.id
|
|
assert len(restored.results) == 2
|
|
assert restored.results[0].relevance_score == 0.95
|
|
assert restored.usage.total_tokens == 64
|