Files
zvec/python/tests/test_reranker.py
T
2025-12-30 11:02:17 +08:00

185 lines
6.5 KiB
Python

# Copyright 2025-present the zvec project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from unittest.mock import patch
import pytest
import math
from zvec import RrfReRanker, WeightedReRanker, Doc, MetricType
# ----------------------------
# RrfRanker Test Case
# ----------------------------
class TestRrfReRanker:
def test_init(self):
reranker = RrfReRanker(
query="test", topn=5, rerank_field="content", rank_constant=100
)
assert reranker.query == "test"
assert reranker.topn == 5
assert reranker.rerank_field == "content"
assert reranker.rank_constant == 100
def test_rrf_score(self):
reranker = RrfReRanker(query="test", rank_constant=60)
# 根据公式 1.0 / (k + rank + 1),其中k=60
assert reranker._rrf_score(0) == 1.0 / (60 + 0 + 1)
assert reranker._rrf_score(1) == 1.0 / (60 + 1 + 1)
assert reranker._rrf_score(10) == 1.0 / (60 + 10 + 1)
def test_rerank(self):
reranker = RrfReRanker(query="test", topn=3)
doc1 = Doc(id="1", score=0.8)
doc2 = Doc(id="2", score=0.7)
doc3 = Doc(id="3", score=0.9)
doc4 = Doc(id="4", score=0.6)
query_results = {"vector1": [doc1, doc2, doc3], "vector2": [doc3, doc1, doc4]}
results = reranker.rerank(query_results)
assert len(results) <= reranker.topn
for doc in results:
assert hasattr(doc, "score")
scores = [doc.score for doc in results]
assert scores == sorted(scores, reverse=True)
# ----------------------------
# WeightedRanker Test Case
# ----------------------------
class TestWeightedReRanker:
def test_init(self):
weights = {"vector1": 0.7, "vector2": 0.3}
reranker = WeightedReRanker(
query="test",
topn=5,
rerank_field="content",
metric=MetricType.L2,
weights=weights,
)
assert reranker.query == "test"
assert reranker.topn == 5
assert reranker.rerank_field == "content"
assert reranker.metric == MetricType.L2
assert reranker.weights == weights
def test_normalize_score(self):
reranker = WeightedReRanker(query="test")
score = reranker._normalize_score(1.0, MetricType.L2)
expected = 1.0 - 2 * math.atan(1.0) / math.pi
assert score == expected
score = reranker._normalize_score(1.0, MetricType.IP)
expected = 0.5 + math.atan(1.0) / math.pi
assert score == expected
score = reranker._normalize_score(1.0, MetricType.COSINE)
expected = 1.0 - 1.0 / 2.0
assert score == expected
with pytest.raises(ValueError, match="Unsupported metric type"):
reranker._normalize_score(1.0, "unsupported_metric")
def test_rerank(self):
weights = {"vector1": 0.7, "vector2": 0.3}
reranker = WeightedReRanker(
query="test", topn=3, weights=weights, metric=MetricType.L2
)
doc1 = Doc(id="1", score=0.8)
doc2 = Doc(id="2", score=0.7)
doc3 = Doc(id="3", score=0.9)
query_results = {"vector1": [doc1, doc2], "vector2": [doc2, doc3]}
results = reranker.rerank(query_results)
assert len(results) <= reranker.topn
for doc in results:
assert hasattr(doc, "score")
scores = [doc.score for doc in results]
assert scores == sorted(scores, reverse=True)
# # ----------------------------
# # QwenReRanker Test Case
# # ----------------------------
# class TestQwenReRanker:
# def test_init_without_query(self):
# with pytest.raises(ValueError):
# QwenReRanker()
#
# def test_init_without_api_key(self):
# with patch.dict(os.environ, {"DASHSCOPE_API_KEY": ""}):
# with pytest.raises(ValueError, match="DashScope API key is required"):
# QwenReRanker(query="test")
#
# @patch.dict(os.environ, {"DASHSCOPE_API_KEY": "test_key"})
# def test_init_with_env_api_key(self):
# reranker = QwenReRanker(query="test")
# assert reranker.query == "test"
# assert reranker._api_key == "test_key"
#
# def test_model_property(self):
# reranker = QwenReRanker(query="test", api_key="test_key")
# assert reranker.model == "gte-rerank-v2"
#
# reranker = QwenReRanker(query="test", model="custom-model", api_key="test_key")
# assert reranker.model == "custom-model"
#
# def test_rerank_empty_results(self):
# reranker = QwenReRanker(query="test", api_key="test_key")
# results = reranker.rerank({})
# assert results == []
#
# def test_rerank_no_documents(self):
# reranker = QwenReRanker(query="test", api_key="test_key")
# query_results = {"vector1": [Doc(id="1")]}
# with pytest.raises(ValueError, match="No documents to rerank"):
# reranker.rerank(query_results)
#
# @pytest.mark.skip(reason="Qwen ReRanker is not available in CI")
# def test_rerank_success(self):
# reranker = QwenReRanker(
# topn=3,
# query="test",
# api_key="*",
# rerank_field="content",
# )
# query_results = {
# "vector1": [
# Doc(id="1", fields={"content": "This is a test document."}),
# Doc(id="2", fields={"content": "Another test document."}),
# Doc(id="3", fields={"content": "Yet another test document."}),
# Doc(id="4", fields={"content": "One more test document."}),
# ],
# "vector2": [
# Doc(id="5", fields={"content": "This is a test document2."}),
# Doc(id="6", fields={"content": "Another test document2."}),
# Doc(id="7", fields={"content": "Yet another test document2."}),
# Doc(id="8", fields={"content": "One more test document2."}),
# ],
# }
# results = reranker.rerank(query_results)
# assert len(results) == 3