mlabonne's picture
ColBERT semantic tool selection (LFM2.5)
6d59171
Raw
History Blame Contribute Delete
1.92 kB
"""Precompute the 2D UMAP layout of the tool embeddings for the background constellation.
Run once locally (needs the model + HF auth + umap-learn):
python build_layout.py
Embeds every tool's routing_text with LFM2.5-Embedding-350M (the same text and prompt
the live search uses), projects to 2D with UMAP (cosine metric), normalises to [0,1]
with a small inset, and writes static/layout.json — a list of
{key: "domain|name", name, domain, x, y}. The frontend renders one dot per tool and
lights up the ones a query retrieves. The Space itself does not need umap-learn.
"""
from __future__ import annotations
import json
import pathlib
import numpy as np
import search as S
import toolset as T
OUT = pathlib.Path(__file__).resolve().parent / "static" / "layout.json"
def _normalize(xy: np.ndarray) -> np.ndarray:
lo, hi = xy.min(axis=0), xy.max(axis=0)
span = np.where(hi - lo == 0, 1.0, hi - lo)
return (xy - lo) / span # -> [0, 1]; the frontend adds border padding
def main() -> None:
import umap # heavy (numba); only needed for this offline build
tools = T.load_catalog()
print(f"embedding {len(tools)} tools with {S.MODEL_ID} ...", flush=True)
emb = S._encode([t.routing_text for t in tools], prompt_name="document")
n_neighbors = min(15, max(2, len(tools) - 1))
reducer = umap.UMAP(
n_components=2, metric="cosine", n_neighbors=n_neighbors,
min_dist=0.12, random_state=42,
)
xy = _normalize(np.asarray(reducer.fit_transform(emb), dtype=np.float64))
pts = [
{"key": f"{t.domain}|{t.name}", "name": t.name, "domain": t.domain,
"x": round(float(xy[i, 0]), 4), "y": round(float(xy[i, 1]), 4)}
for i, t in enumerate(tools)
]
OUT.write_text(json.dumps(pts), encoding="utf-8")
print(f"wrote {len(pts)} points to {OUT}", flush=True)
if __name__ == "__main__":
main()