Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions src/everos/component/rerank/dashscope_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
returned list is sorted score-descending defensively regardless of server
ordering. ``api_key`` is required — DashScope has no anonymous tier.

Only ``gte-rerank-v2`` is supported by this provider for now.
``qwen3-rerank`` uses a different endpoint / request shape and should be
added as a separate branch if EverOS decides to support it later.
``gte-rerank-v2`` and ``qwen3-rerank`` are supported. Both use the same native
endpoint and body; ``qwen3-rerank`` additionally takes the task instruction as
``parameters.instruct`` (local patch 2026-10-02: the intl region has no
``gte-rerank-v2``).
"""

from __future__ import annotations
Expand Down Expand Up @@ -69,7 +70,8 @@ class DashScopeRerankProvider:
"""

_SERVICE_PATH = "/api/v1/services/rerank/text-rerank/text-rerank"
_SUPPORTED_MODELS = frozenset({"gte-rerank-v2"})
_SUPPORTED_MODELS = frozenset({"gte-rerank-v2", "qwen3-rerank"})
_INSTRUCT_MODELS = frozenset({"qwen3-rerank"})

def __init__(
self,
Expand All @@ -84,7 +86,8 @@ def __init__(
) -> None:
if model not in self._SUPPORTED_MODELS:
raise ValueError(
f"DashScope rerank currently supports gte-rerank-v2 only; got {model!r}"
f"DashScope rerank supports {sorted(self._SUPPORTED_MODELS)}; "
f"got {model!r}"
)
self._model = model
self._api_key = api_key
Expand All @@ -103,9 +106,8 @@ async def rerank(
) -> list[RerankResult]:
"""Score every document against ``query``; return sorted desc.

``instruction`` is accepted for protocol parity but not transmitted:
DashScope applies the reranker template server-side, so there is no
client-side prompt to fill.
``instruction`` is sent as ``parameters.instruct`` for ``qwen3-rerank``;
``gte-rerank-v2`` takes no instruction, so it is dropped there.
"""
if not documents:
return []
Expand All @@ -115,7 +117,7 @@ async def rerank(
for offset in range(0, len(documents), self._batch_size)
]
chunk_results = await asyncio.gather(
*(self._score_chunk(query, docs) for _, docs in chunks)
*(self._score_chunk(query, docs, instruction) for _, docs in chunks)
)
scored: list[RerankResult] = []
for (offset, _), partial in zip(chunks, chunk_results, strict=True):
Expand All @@ -126,12 +128,18 @@ async def rerank(
return scored

async def _score_chunk(
self, query: str, documents: list[str]
self, query: str, documents: list[str], instruction: str | None = None
) -> list[RerankResult]:
parameters: dict[str, Any] = {
"return_documents": False,
"top_n": len(documents),
}
if instruction and self._model in self._INSTRUCT_MODELS:
parameters["instruct"] = instruction
payload: dict[str, Any] = {
"model": self._model,
"input": {"query": query, "documents": documents},
"parameters": {"return_documents": False, "top_n": len(documents)},
"parameters": parameters,
}
headers = {
"Authorization": f"Bearer {self._api_key}",
Expand Down
45 changes: 42 additions & 3 deletions tests/unit/test_component/test_rerank/test_dashscope_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,54 @@ def _ok_response(items: list[dict[str, float | int]]) -> httpx.Response:
return httpx.Response(200, json={"output": {"results": items}})


def test_only_gte_rerank_v2_is_supported() -> None:
with pytest.raises(ValueError, match="gte-rerank-v2 only"):
def test_unsupported_model_rejected() -> None:
with pytest.raises(ValueError, match="supports"):
DashScopeRerankProvider(
model="qwen3-rerank",
model="gte-rerank",
api_key="k",
base_url="https://dashscope.aliyuncs.com",
)


async def test_qwen3_rerank_forwards_instruction(
monkeypatch: pytest.MonkeyPatch,
) -> None:
seen_bodies: list[dict[str, object]] = []

def handler(req: httpx.Request) -> httpx.Response:
seen_bodies.append(json.loads(req.content))
return _ok_response([{"index": 0, "relevance_score": 0.5}])

_patch_httpx(monkeypatch, handler)
p = DashScopeRerankProvider(
model="qwen3-rerank",
api_key="k",
base_url="https://dashscope-intl.aliyuncs.com",
)
await p.rerank("q", ["a"], instruction="judge it")
await p.rerank("q", ["a"])
assert seen_bodies[0]["model"] == "qwen3-rerank"
assert seen_bodies[0]["parameters"]["instruct"] == "judge it"
assert "instruct" not in seen_bodies[1]["parameters"]


async def test_gte_rerank_v2_never_sends_instruction(
monkeypatch: pytest.MonkeyPatch,
) -> None:
seen_bodies: list[dict[str, object]] = []

def handler(req: httpx.Request) -> httpx.Response:
seen_bodies.append(json.loads(req.content))
return _ok_response([{"index": 0, "relevance_score": 0.5}])

_patch_httpx(monkeypatch, handler)
p = DashScopeRerankProvider(
model="gte-rerank-v2", api_key="k", base_url="https://dashscope.aliyuncs.com"
)
await p.rerank("q", ["a"], instruction="judge it")
assert "instruct" not in seen_bodies[0]["parameters"]


async def test_empty_documents_short_circuits(monkeypatch: pytest.MonkeyPatch) -> None:
calls = 0

Expand Down