diff --git a/astrbot/core/provider/sources/xinference_rerank_source.py b/astrbot/core/provider/sources/xinference_rerank_source.py index 9c3a77c158..d834abfcc3 100644 --- a/astrbot/core/provider/sources/xinference_rerank_source.py +++ b/astrbot/core/provider/sources/xinference_rerank_source.py @@ -90,8 +90,7 @@ async def rerank( top_n: int | None = None, ) -> list[RerankResult]: if not self.model: - logger.error("Xinference rerank model is not initialized.") - return [] + raise RuntimeError("Xinference rerank model is not initialized") try: response = await self.model.rerank(documents, query, top_n) results = response.get("results", []) @@ -110,9 +109,12 @@ async def rerank( for result in results ] except Exception as e: + # An empty list reads as a legitimate empty rerank to the caller, + # which would overwrite the fused candidates with nothing (#10000). + # Propagate so the retrieval manager keeps the unreranked results. logger.error(f"Xinference rerank failed: {e}") logger.debug(f"Xinference rerank failed with exception: {e}", exc_info=True) - return [] + raise async def terminate(self) -> None: """关闭客户端会话""" diff --git a/tests/test_xinference_rerank_source.py b/tests/test_xinference_rerank_source.py new file mode 100644 index 0000000000..82f8393297 --- /dev/null +++ b/tests/test_xinference_rerank_source.py @@ -0,0 +1,38 @@ +"""Regression tests for the Xinference rerank provider failure contract (#10000). + +The provider used to swallow upstream failures and return an empty list, +which the retrieval manager read as a legitimate empty rerank result and +used to overwrite the fused candidates. Failures now propagate so the +manager can fall back to the unreranked results. +""" + +from unittest.mock import AsyncMock, Mock + +import pytest + +from astrbot.core.provider.sources.xinference_rerank_source import ( + XinferenceRerankProvider, +) + + +@pytest.fixture +def provider() -> XinferenceRerankProvider: + instance = XinferenceRerankProvider.__new__(XinferenceRerankProvider) + instance.model = Mock() + return instance + + +@pytest.mark.asyncio +async def test_rerank_failure_propagates_instead_of_empty_list(provider): + provider.model.rerank = AsyncMock(side_effect=RuntimeError("upstream down")) + + with pytest.raises(RuntimeError, match="upstream down"): + await provider.rerank(query="q", documents=["a", "b"]) + + +@pytest.mark.asyncio +async def test_uninitialized_model_raises_instead_of_empty_list(provider): + provider.model = None + + with pytest.raises(RuntimeError, match="not initialized"): + await provider.rerank(query="q", documents=["a"])