Spaces:
Sleeping
Sleeping
File size: 1,360 Bytes
ce45eb0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | """BM25 lexical retrieval over the candidate set.
Pure-Python for inspectability and zero deps. FTS5 (SQLite) / Postgres text
search are the production swap-ins behind the same call signature.
"""
from __future__ import annotations
import math
import re
from collections import Counter
from typing import List, Tuple
from ..schema.item import ContextItem
_TOKEN = re.compile(r"[a-z0-9]+")
def tokenize(s: str) -> List[str]:
return _TOKEN.findall(s.lower())
def bm25_rank(query: str, items: List[ContextItem],
k1: float = 1.5, b: float = 0.75) -> List[Tuple[str, float]]:
docs = [tokenize(it.content) for it in items]
if not docs:
return []
avgdl = sum(len(d) for d in docs) / len(docs)
df: Counter = Counter()
for d in docs:
for term in set(d):
df[term] += 1
N = len(docs)
q_terms = tokenize(query)
scored = []
for it, d in zip(items, docs):
tf = Counter(d)
score = 0.0
for term in q_terms:
if term not in tf:
continue
idf = math.log(1 + (N - df[term] + 0.5) / (df[term] + 0.5))
denom = tf[term] + k1 * (1 - b + b * len(d) / avgdl)
score += idf * (tf[term] * (k1 + 1)) / denom
scored.append((it.id, score))
scored.sort(key=lambda x: x[1], reverse=True)
return scored
|