Source code for fastrag.retrieval.reranking_retriever
"""``RerankingRetriever``: narrowing any retrieval strategy with a reranker."""
from __future__ import annotations
from fastrag.exceptions import ConfigurationError
from fastrag.ingestion.steps.embedding import Embedder
from fastrag.models import Document
from fastrag.retrieval.base import Retriever
from fastrag.retrieval.reranking.base import Reranker
from fastrag.retrieval.vector import VectorRetriever
from fastrag.vector_store import VectorStore
[docs]
class RerankingRetriever(Retriever):
"""Over-fetch with a retriever, then narrow on relevance.
Composition, not configuration: a plain :class:`~fastrag.retrieval.vector.VectorRetriever`
over-fetches ``k`` candidates on cosine similarity, and ``reranker`` then
narrows them to ``top_k`` on relevance — a cross-encoder reading query
and document together, which similarity search alone cannot do. The
returned ``score``/``rank`` are the reranker's own, not the vector
search's — see :meth:`~fastrag.retrieval.reranking.base.Reranker.rerank`.
.. code-block:: python
async with MilvusVectorStore(url=MILVUS_URI) as vector_store:
retriever = RerankingRetriever(
vector_store=vector_store,
embedder=Model2VecEmbedder(model=EMBEDDING_MODEL),
reranker=CohereReranker(
model="rerank-v3.5", base_url=RERANK_BASE_URL, api_key=api_key
),
)
documents = await retriever.retrieve("What is AgrospAI?", k=200, top_k=5)
Args:
vector_store: The collection to over-fetch from. Opened by the
caller; this retriever neither opens nor closes it.
embedder: Embeds the query text — see
:class:`~fastrag.retrieval.vector.VectorRetriever`.
retriever: Any SDK retriever, including a hybrid retriever. Mutually
exclusive with the legacy vector_store/embedder pair.
candidate_k: Optional over-fetch count used even when the caller
requests only the final k results.
reranker: Scores the over-fetched documents against the query.
"""
def __init__(
self,
*,
reranker: Reranker,
retriever: Retriever | None = None,
vector_store: VectorStore | None = None,
embedder: Embedder | None = None,
candidate_k: int | None = None,
) -> None:
"""Store the collaborators; no I/O happens until :meth:`retrieve`."""
if retriever is not None:
if vector_store is not None or embedder is not None:
raise ConfigurationError(
"pass retriever or vector_store/embedder, not both"
)
else:
if vector_store is None or embedder is None:
raise ConfigurationError(
"provide retriever or both vector_store and embedder"
)
retriever = VectorRetriever(vector_store=vector_store, embedder=embedder)
if candidate_k is not None and candidate_k < 1:
raise ConfigurationError("candidate_k must be at least 1")
self.retriever = retriever
self.candidate_k = candidate_k
self.vector_store = vector_store
self.embedder = embedder
self.reranker = reranker
[docs]
async def retrieve(
self, query: str, *, k: int = 5, top_k: int | None = None
) -> list[Document]:
"""Fetch candidates through the wrapped retriever, then rerank to ``top_k``.
Args:
query: Natural-language query text.
k: How many documents the vector store over-fetches.
top_k: How many documents the reranker keeps, best first.
Defaults to ``k`` when omitted.
"""
if k < 1 or (top_k is not None and top_k < 1):
raise ConfigurationError("k and top_k must be at least 1")
hits = await self.retriever.retrieve(query, k=max(k, self.candidate_k or k))
if not hits:
return []
return await self.reranker.rerank(
query, hits, top_k=k if top_k is None else top_k
)