UBCHelper / src /rerank.py
Devdan Schretlen
Claude Opus 5 (1M context)
Render the answer as Markdown, bound API timeouts, fix a false notice
9db5d1e
Raw History Blame Contribute Delete
2.36 kB
"""
Rerank — the precision stage of the retrieval funnel.
The bi-encoder (embeddings) scores query and document *separately*, so it can only
compare their summaries. A cross-encoder reads the query and a document *together* and
scores the pair jointly — far sharper, but far too slow to run against the whole corpus.
So it only ever sees the short candidate list that the cheap recall stage (dense + BM25)
already produced.
Reranking runs on Cohere's hosted rerank API (config.RERANKER_MODEL). Keeping it hosted
means no local torch / sentence-transformers dependency, so the service deploys on an
ordinary CPU host. Excerpts longer than the model's window are truncated server-side,
which is fine: the title/section header + the start of the body carry the ranking signal.
"""
from __future__ import annotations
import time
from . import config
from .retrieve import Result
# The Cohere client is created lazily and cached: only the hybrid_rerank mode needs it,
# so dense/hybrid modes never construct it.
_client = None
def _get_client():
global _client
if _client is None:
import cohere
_client = cohere.Client(config.COHERE_API_KEY, timeout=config.API_TIMEOUT)
return _client
def rerank(query: str, candidates: list[Result], top_k: int) -> list[Result]:
"""Re-score `candidates` jointly against `query`; return the top_k, best first.
The returned Results carry the Cohere relevance score (0-1, higher is better),
replacing whatever score the recall stage assigned.
"""
import cohere
client = _get_client()
# Trial keys are capped at 10 calls/minute; back off and retry on 429 so a batch
# (e.g. the eval loop) doesn't die mid-run.
for attempt in range(4):
try:
response = client.rerank(
model=config.RERANKER_MODEL,
query=query,
documents=[r.recipe["text"] for r in candidates],
top_n=top_k,
)
break
except cohere.errors.TooManyRequestsError:
if attempt == 3:
raise
time.sleep(15 * (attempt + 1))
return [
Result(
id=candidates[result.index].id,
score=result.relevance_score,
recipe=candidates[result.index].recipe,
)
for result in response.results
]