Spaces:
Running
Running
Download backend/engine.py from mishig/openai-math-explorer: direct link, hf CLI and curl.
- Browser
- Download file 4.84 kB
-
https://huggingface.co/spaces/mishig/openai-math-explorer/resolve/main/backend/engine.py
- Command line
-
hf download hf://spaces/mishig/openai-math-explorer/backend/engine.py
-
curl -L -o engine.py https://huggingface.co/spaces/mishig/openai-math-explorer/resolve/main/backend/engine.py
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 | |