Semblance / scripts /build_embeddings.py
yueyvettehao's picture
Upload full app (app.py + core/agents/mcp_server/ui/assets/examples/...)
fb2ae51 verified
Raw
History Blame Contribute Delete
2.88 kB
"""Offline asset build: BioLORD-embed the Hallmark descriptions → committed assets.
Outputs (committed, ~150 KB total — the only persistent runtime data):
assets/pathway_descriptions.json name -> short description
assets/pathway_embeddings.npz names: str (N,) | vectors: float32 (N, 768), L2-normalized
Run once (downloads BioLORD-2023 ~420 MB to the HF cache on first use; CPU; ~1 min):
python scripts/build_embeddings.py
python scripts/build_embeddings.py --model BAAI/bge-base-en-v1.5 # contrast baseline
100% free / CPU / no paid services. Network is used only to download the open model; the
resulting assets are fully offline at runtime.
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
import numpy as np
# Make the repo root importable when run as `python scripts/build_embeddings.py`.
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts"))
import config # noqa: E402
from _hallmark_descriptions import HALLMARK_DESCRIPTIONS # noqa: E402
def build(model_id: str = config.EMBEDDER_DEFAULT) -> None:
descriptions: dict[str, str] = dict(HALLMARK_DESCRIPTIONS)
config.ASSETS_DIR.mkdir(parents=True, exist_ok=True)
# 1. descriptions.json
config.DESCRIPTIONS_JSON.write_text(json.dumps(descriptions, indent=2, sort_keys=True))
print(f"[build] wrote {config.DESCRIPTIONS_JSON.name} ({len(descriptions)} pathways)")
# 2. embeddings.npz
from sentence_transformers import SentenceTransformer
names = list(descriptions.keys())
texts = [descriptions[n] for n in names]
print(f"[build] loading {model_id} on {config.EMBED_DEVICE} (first run downloads the model)…")
model = SentenceTransformer(model_id, device=config.EMBED_DEVICE)
vectors = model.encode(
texts, normalize_embeddings=True, batch_size=32, show_progress_bar=True
).astype(np.float32)
# 3. verify invariants before committing
assert vectors.shape == (len(names), config.EMBED_DIM), vectors.shape
norms = np.linalg.norm(vectors, axis=1)
assert np.allclose(norms, 1.0, atol=1e-4), f"vectors not L2-normalized: {norms.min()}..{norms.max()}"
assert not np.isnan(vectors).any(), "NaN in embeddings"
np.savez_compressed(
config.EMBEDDINGS_NPZ,
names=np.array(names, dtype=str),
vectors=vectors,
)
size_kb = config.EMBEDDINGS_NPZ.stat().st_size / 1024
print(f"[build] wrote {config.EMBEDDINGS_NPZ.name} "
f"({len(names)} x {config.EMBED_DIM}, {size_kb:.0f} KB, model={model_id})")
if __name__ == "__main__":
ap = argparse.ArgumentParser(description="Build pathway description + embedding assets.")
ap.add_argument("--model", default=config.EMBEDDER_DEFAULT, help="sentence-transformers model id")
build(ap.parse_args().model)