chatvns / app /retriever.py
liamxdev's picture
Upload folder using huggingface_hub
34b531b verified
Raw
History Blame Contribute Delete
1.82 kB
from __future__ import annotations
from dataclasses import replace
import logging
from app.config import RERANK_CANDIDATE_LIMIT
from app.keyword_search import keyword_search
from app.reranker import get_reranker
from app.runtime_auth import has_hf_api_key
from app.schemas import RetrievedChunk
from app.vector_store import retrieve as dense_retrieve
logger = logging.getLogger(__name__)
def hybrid_retrieve(
query: str,
top_k: int,
ticker: str | None = None,
dense_weight: float = 0.65,
keyword_weight: float = 0.35,
rrf_k: int = 60,
) -> list[RetrievedChunk]:
dense_limit = max(top_k * 4, 20)
keyword_limit = max(top_k * 4, 20)
dense_hits: list[RetrievedChunk] = []
if has_hf_api_key():
try:
dense_hits = dense_retrieve(query, top_k=dense_limit, ticker=ticker)
except Exception as exc: # noqa: BLE001
logger.warning("Dense retrieval unavailable; using BM25 fallback: %s", exc)
keyword_hits = keyword_search(query, top_k=keyword_limit, ticker=ticker)
merged: dict[str, RetrievedChunk] = {}
scores: dict[str, float] = {}
for rank, hit in enumerate(dense_hits, start=1):
merged[hit.id] = hit
scores[hit.id] = scores.get(hit.id, 0.0) + dense_weight / (rrf_k + rank)
for rank, hit in enumerate(keyword_hits, start=1):
merged.setdefault(hit.id, hit)
scores[hit.id] = scores.get(hit.id, 0.0) + keyword_weight / (rrf_k + rank)
ranked = sorted(scores.items(), key=lambda item: item[1], reverse=True)
candidate_limit = max(top_k, RERANK_CANDIDATE_LIMIT)
candidates = [
replace(merged[chunk_id], score=score)
for chunk_id, score in ranked[:candidate_limit]
]
return get_reranker().rerank(query, candidates, top_k=top_k)