Spaces:
Runtime error
Runtime error
| """ | |
| Runtime candidate-side index builder. | |
| The dense embeddings and BM25 index are computed FROM THE UPLOADED candidates | |
| file at rank time — they are NOT loaded from by-candidate_id artifacts. This is | |
| what lets rank.py score an arbitrary judge-supplied dataset correctly, even when | |
| the candidate_ids are unseen (or collide with the original dataset but carry | |
| different content). | |
| Why this is cheap enough to stay inside the 5-min / 16GB / CPU / zero-network | |
| budget: the embedder is model2vec (potion-base-8M), a static torch-free model at | |
| ~19K chunks/s on CPU. 100K candidates ≈ 0.5M chunks ≈ ~26s to embed; BM25 build | |
| ≈ a few seconds. The model weights (~30MB) are vendored into | |
| artifacts/potion-base-8M, so loading is local — no network call. | |
| Only JD-side artifacts (the trained ranker, jd_query_vectors, hypothetical | |
| resumes) remain precomputed, because those are dataset-independent. | |
| Parity contract: chunking/tokenization come from src.utils, the SAME helpers the | |
| offline precompute uses, and embeddings are stored float16 like the offline | |
| artifact — so runtime features match what the XGBoost model was trained on. | |
| """ | |
| from __future__ import annotations | |
| from collections import defaultdict | |
| from pathlib import Path | |
| from typing import Callable, Iterable | |
| import numpy as np | |
| from src.utils import candidate_to_bm25_text, candidate_to_chunks, tokenize | |
| DEFAULT_MODEL_DIR = Path("artifacts") / "potion-base-8M" | |
| def build_dense_index(candidates: Iterable[dict], model_dir: Path = DEFAULT_MODEL_DIR) -> dict: | |
| """ | |
| Embed every candidate's chunks with the local model2vec model. | |
| Returns the same structure load_precomputed produces for the dense channel: | |
| candidate_embeddings : np.ndarray (n_chunks, dim) float16 | |
| candidate_ids : list[str] (candidate_id per row, chunk order) | |
| cid_to_idx : dict[str, int] first row per candidate | |
| cid_to_rows : dict[str, list[int]] all rows per candidate | |
| """ | |
| from model2vec import StaticModel | |
| model = StaticModel.from_pretrained(str(model_dir)) | |
| all_chunks: list[str] = [] | |
| all_ids: list[str] = [] | |
| for c in candidates: | |
| cid = c["candidate_id"] | |
| for chunk in candidate_to_chunks(c): | |
| all_chunks.append(chunk) | |
| all_ids.append(cid) | |
| if not all_chunks: | |
| dim = model.dim | |
| return { | |
| "candidate_embeddings": np.zeros((0, dim), dtype=np.float16), | |
| "candidate_ids": [], | |
| "cid_to_idx": {}, | |
| "cid_to_rows": {}, | |
| } | |
| embeddings = model.encode(all_chunks).astype(np.float16) | |
| cid_to_idx: dict[str, int] = {} | |
| cid_to_rows: dict[str, list[int]] = defaultdict(list) | |
| for i, cid in enumerate(all_ids): | |
| if cid not in cid_to_idx: | |
| cid_to_idx[cid] = i | |
| cid_to_rows[cid].append(i) | |
| return { | |
| "candidate_embeddings": embeddings, | |
| "candidate_ids": all_ids, | |
| "cid_to_idx": cid_to_idx, | |
| "cid_to_rows": dict(cid_to_rows), | |
| } | |
| def build_bm25_index(candidates: Iterable[dict]) -> dict: | |
| """ | |
| Build a BM25Okapi index over the uploaded candidates. | |
| Returns: | |
| bm25_data : {"bm25": BM25Okapi, "candidate_ids": list[str]} (for retrieval) | |
| bm25_cid_to_idx : dict[str, int] (for features) | |
| """ | |
| from rank_bm25 import BM25Okapi | |
| corpus_tokens: list[list[str]] = [] | |
| corpus_ids: list[str] = [] | |
| for c in candidates: | |
| corpus_tokens.append(tokenize(candidate_to_bm25_text(c))) | |
| corpus_ids.append(c["candidate_id"]) | |
| if not corpus_tokens: | |
| corpus_tokens = [[""]] # BM25Okapi rejects an empty corpus | |
| index = BM25Okapi(corpus_tokens) | |
| return { | |
| "bm25_data": {"bm25": index, "candidate_ids": corpus_ids}, | |
| "bm25_cid_to_idx": {cid: i for i, cid in enumerate(corpus_ids)}, | |
| } | |
| def attach_runtime_index( | |
| precomputed: dict, | |
| candidates: dict | Iterable[dict], | |
| model_dir: Path = DEFAULT_MODEL_DIR, | |
| tick: Callable[[str], None] | None = None, | |
| ) -> dict: | |
| """ | |
| Compute candidate-side dense + BM25 indexes from `candidates` and inject them | |
| into `precomputed`, overwriting any stale by-id artifacts. Returns bm25_data | |
| for src.retrieval.bm25_retrieve. | |
| `candidates` may be the rank.py dict {cid: candidate} or any iterable of | |
| candidate dicts. | |
| """ | |
| cand_iter = candidates.values() if isinstance(candidates, dict) else list(candidates) | |
| cand_list = list(cand_iter) | |
| def say(msg: str) -> None: | |
| if tick: | |
| tick(msg) | |
| say("Runtime index: embedding candidates…") | |
| dense = build_dense_index(cand_list, model_dir) | |
| precomputed["candidate_embeddings"] = dense["candidate_embeddings"] | |
| precomputed["candidate_ids"] = dense["candidate_ids"] | |
| precomputed["cid_to_idx"] = dense["cid_to_idx"] | |
| precomputed["cid_to_rows"] = dense["cid_to_rows"] | |
| say(f"Runtime index: dense done, {dense['candidate_embeddings'].shape[0]} chunks") | |
| say("Runtime index: building BM25…") | |
| bm25 = build_bm25_index(cand_list) | |
| precomputed["bm25_index"] = bm25["bm25_data"]["bm25"] | |
| precomputed["bm25_cid_to_idx"] = bm25["bm25_cid_to_idx"] | |
| precomputed.pop("bm25_all_scores", None) # drop any cached scores from a prior dataset | |
| say("Runtime index: BM25 done") | |
| return bm25["bm25_data"] | |