amplegest / storage /reranker.py
Viney's picture
fix: background generation, parallel tools, prompt caching, Tavily cache
2401ce5
Raw
History Blame Contribute Delete
2.29 kB
"""storage/reranker.py β€” cross-encoder reranking for RAG chunks.
Wraps BAAI/bge-reranker-base via sentence-transformers.CrossEncoder.
Loaded lazily on first call so app startup stays fast (~100 MB model
downloaded once into ~/.cache/huggingface).
Used by storage/vector_store.search to re-rank Chroma's top-k candidates.
"""
from __future__ import annotations
from typing import Optional
_MODEL_NAME = "BAAI/bge-reranker-base"
_model = None # type: ignore[var-annotated]
def _get_model():
"""Lazy-load the cross-encoder. Returns None on import or load failure."""
global _model
if _model is None:
try:
from sentence_transformers import CrossEncoder
_model = CrossEncoder(_MODEL_NAME)
except Exception as exc: # pragma: no cover β€” defensive, not testable
import sys
print(f"[reranker] failed to load {_MODEL_NAME}: {exc}", file=sys.stderr)
_model = False # sentinel: tried and failed
return _model if _model is not False else None
def rerank(query: str, candidates: list[dict], top_k: int = 3) -> list[dict]:
"""Re-score (query, candidate.text) pairs with the cross-encoder, return top_k.
Each candidate is a dict with at least a 'text' field (matches the format
returned by storage.vector_store.search).
Falls back to the original ordering (truncated to top_k) if:
- candidates already <= top_k (no work to do)
- the cross-encoder model failed to load
- the predict call raises
"""
if not candidates or len(candidates) <= top_k:
return candidates[:top_k]
model = _get_model()
if model is None:
return candidates[:top_k]
try:
pairs = [(query, c.get("text", "")) for c in candidates]
scores = model.predict(pairs)
ranked = sorted(zip(candidates, scores), key=lambda x: float(x[1]), reverse=True)
return [c for c, _ in ranked[:top_k]]
except Exception:
return candidates[:top_k]
def warmup() -> None:
"""Eagerly load the cross-encoder model; call at app startup to avoid cold-start penalty."""
_get_model()
def reset_for_test() -> None:
"""Reset the cached model β€” used by unit tests that monkeypatch the loader."""
global _model
_model = None