Dense vectors are great at paraphrase but weak at exact identifiers: error codes, SKUs, function names, contract addresses. BM25 lexical search nails those. Hybrid retrieval runs both and fuses the results.
Reciprocal Rank Fusion (RRF) is the simple, robust fuser:
python
def rrf(rankings, k=60):
scores = {}
for ranking in rankings: # e.g. [dense_ids, bm25_ids]
for rank, doc_id in enumerate(ranking, start=1):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank)
return sorted(scores, key=scores.get, reverse=True)Also useful: - Metadata filters before search (tenant, language, date range, product version). - Query rewriting — expand pronouns from chat history into a standalone query before retrieving. - Multi-query — generate 3 paraphrases, retrieve for each, fuse.
Gotcha: apply the tenant/ACL filter inside the search call, not after ranking, or you will retrieve k documents and show the user zero while paying full latency — or worse, leak them.