mishig's picture
mishig HF Staff
Theorem Explorer: search UI + API for github.com/openai/math
c5ced83 verified
Raw History Blame Contribute Delete
4.84 kB
"""Hybrid search over the openai/math catalogue.
Each family is indexed as one document (title + summary + aliases + plain-English
line) plus one document per paper (title + abstract). A family's dense score is
the max cosine similarity over its documents; its BM25 score is the max BM25
score over the same documents. The two family rankings are fused with
reciprocal-rank fusion. This is the configuration that scored best on the
eval (see eval/RESULTS.md: qwen3-0.6b+expanded, hybrid_rrf).
"""
import json
import threading
from collections import OrderedDict
from pathlib import Path
import bm25s
import numpy as np
import Stemmer
MODEL_ID = "Qwen/Qwen3-Embedding-0.6B"
# Document vectors in index/ were computed with this exact revision; queries must use it too.
MODEL_REVISION = "97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3"
QUERY_PROMPT = ("Instruct: Given a search query about mathematics, retrieve research results "
"whose statement answers or matches the query\nQuery: ")
RRF_K = 60
def family_doc_text(f: dict) -> str:
return f"{f['title']}. {f['summary']} Also known as: {', '.join(f['aliases'])}. {f['plain']}"
def paper_doc_text(p: dict) -> str:
return f"{p['title']}. {p['abstract']}"
def build_docs(families: list[dict]) -> list[dict]:
"""Documents in family order: each family's summary doc, then its papers."""
docs = []
for fi, f in enumerate(families):
docs.append({"family": fi, "paper": None, "text": family_doc_text(f)})
for pi, p in enumerate(f["papers"]):
docs.append({"family": fi, "paper": pi, "text": paper_doc_text(p)})
return docs
class SearchEngine:
def __init__(self, index_dir: Path, load_model: bool = True):
self.catalog = json.loads((index_dir / "catalog.json").read_text())
self.families = self.catalog["families"]
self.docs = build_docs(self.families)
self.emb = np.load(index_dir / "embeddings.npy")
assert self.emb.shape[0] == len(self.docs), "index out of date: rebuild with pipeline/build_index.py"
doc_family = np.array([d["family"] for d in self.docs])
self.starts = np.flatnonzero(np.r_[True, doc_family[1:] != doc_family[:-1]])
self.ends = np.r_[self.starts[1:], len(self.docs)]
self.stemmer = Stemmer.Stemmer("english")
self.bm25 = bm25s.BM25()
self.bm25.index(bm25s.tokenize([d["text"] for d in self.docs], stopwords="en",
stemmer=self.stemmer, show_progress=False), show_progress=False)
self.model = None
self._lock = threading.Lock()
self._cache: OrderedDict[str, np.ndarray] = OrderedDict()
if load_model:
from sentence_transformers import SentenceTransformer
self.model = SentenceTransformer(MODEL_ID, revision=MODEL_REVISION, device="cpu")
self.model.max_seq_length = 512
def embed_query(self, query: str) -> np.ndarray:
if query in self._cache:
self._cache.move_to_end(query)
return self._cache[query]
with self._lock:
vec = self.model.encode([query], prompt=QUERY_PROMPT, normalize_embeddings=True,
convert_to_numpy=True)[0].astype(np.float32)
self._cache[query] = vec
if len(self._cache) > 2048:
self._cache.popitem(last=False)
return vec
def _family_max(self, doc_scores: np.ndarray) -> np.ndarray:
return np.maximum.reduceat(doc_scores, self.starts)
def search(self, query: str) -> list[dict]:
"""All families ranked for `query`, best first."""
sims = self.emb @ self.embed_query(query)
dense = self._family_max(sims)
tokens = bm25s.tokenize([query], stopwords="en", stemmer=self.stemmer,
return_ids=False, show_progress=False)[0]
lexical = self._family_max(self.bm25.get_scores(tokens)) if tokens else np.zeros_like(dense)
dense_rank = np.empty(len(dense), dtype=int)
dense_rank[np.argsort(-dense, kind="stable")] = np.arange(len(dense))
lex_rank = np.empty(len(lexical), dtype=int)
lex_rank[np.argsort(-lexical, kind="stable")] = np.arange(len(lexical))
fused = 1 / (RRF_K + dense_rank + 1) + (lexical > 0) / (RRF_K + lex_rank + 1)
results = []
for fi in np.argsort(-fused, kind="stable"):
best = self.starts[fi] + int(np.argmax(sims[self.starts[fi]:self.ends[fi]]))
results.append({
"id": self.families[fi]["id"],
"score": round(float(fused[fi]), 6),
"similarity": round(float(dense[fi]), 4),
"keyword_match": bool(lexical[fi] > 0),
"matched_paper": self.docs[best]["paper"],
})
return results