Semblance / mcp_server /engine.py
yueyvettehao's picture
Upload full app (app.py + core/agents/mcp_server/ui/assets/examples/...)
fb2ae51 verified
Raw
History Blame Contribute Delete
6.32 kB
"""Deterministic engine: the math behind the MCP tools. No LLM.
Loads the shipped embeddings + descriptions once (warm). For a pathway not in the shipped
`.npz`, BioLORD is lazy-loaded and the description (or prettified name) is embedded on the fly
— so the core needs no vector DB (exact-key lookup).
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Optional
import numpy as np
import config
from core.schema import ComparisonResult, CutoffParams, GseaResult, PairDrillDown
from core.signatures import Signature, build_signatures
from core.similarity import (
best_match_matrix,
cluster,
cosine_matrix,
nes_correlation,
)
def _prettify(name: str) -> str:
return name.replace("_", " ").lower()
def _jaccard(a: set, b: set) -> Optional[float]:
if not a and not b:
return None
union = a | b
return len(a & b) / len(union) if union else None
class Engine:
def __init__(
self,
embeddings_npz: Path = config.EMBEDDINGS_NPZ,
descriptions_json: Path = config.DESCRIPTIONS_JSON,
):
data = np.load(embeddings_npz, allow_pickle=False)
self._emb: dict[str, np.ndarray] = {
n: v.astype(float) for n, v in zip(data["names"].tolist(), data["vectors"])
}
self._desc: dict[str, str] = (
json.loads(Path(descriptions_json).read_text()) if Path(descriptions_json).exists() else {}
)
self._model = None # lazy BioLORD, only if an unknown pathway appears
self._embedder_id = config.EMBEDDER_DEFAULT
# --- embedding lookup ---------------------------------------------------
def _embed(self, name: str) -> np.ndarray:
vec = self._emb.get(name)
if vec is not None:
return vec
if self._model is None:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(self._embedder_id, device=config.EMBED_DEVICE)
text = self._desc.get(name) or _prettify(name)
vec = np.asarray(self._model.encode([text], normalize_embeddings=True)[0], dtype=float)
self._emb[name] = vec # cache for the rest of this session
return vec
def embed_query(self, text: str) -> np.ndarray:
"""Embed a free-text query / concept with the warm BioLORD (no caching; transient)."""
if self._model is None:
from sentence_transformers import SentenceTransformer
self._model = SentenceTransformer(self._embedder_id, device=config.EMBED_DEVICE)
return np.asarray(self._model.encode([text], normalize_embeddings=True)[0], dtype=float)
# --- MCP tool implementations ------------------------------------------
def compare(self, results: list[dict], params: Optional[dict] = None) -> dict:
cparams = CutoffParams.model_validate(params or {})
warnings: list[str] = []
sigs: list[Signature] = []
for rdict in results:
sigs.extend(build_signatures(GseaResult.model_validate(rdict), cparams, self._embed, warnings))
labels = [s.label for s in sigs]
if not sigs:
return ComparisonResult(
signatures=[], similarity=[], order=[], linkage=[], clusters={},
nes_correlation=[], pairs=[], warnings=warnings or ["no signatures after filtering"],
).model_dump()
sim = (best_match_matrix(sigs) if cparams.aggregation == "best_match"
else cosine_matrix(np.stack([s.vector for s in sigs])))
order, linkage_rows, flat = cluster(sim, config.CLUSTER_CUT_FRACTION)
clusters = {labels[i]: int(flat[i]) for i in range(len(labels))}
nes_corr = nes_correlation(sigs)
pairs = self._drill_down(sigs, sim, nes_corr)
return ComparisonResult(
signatures=labels,
similarity=sim.tolist(),
order=order,
linkage=linkage_rows,
clusters=clusters,
nes_correlation=nes_corr,
pairs=pairs,
warnings=warnings,
).model_dump()
def _drill_down(
self, sigs: list[Signature], sim: np.ndarray, nes_corr: list, top: int = config.TOP_PAIRS,
) -> list[PairDrillDown]:
m = len(sigs)
ranked = sorted(
((sim[i][j], i, j) for i in range(m) for j in range(i + 1, m)),
reverse=True,
)[:top]
out: list[PairDrillDown] = []
for _, i, j in ranked:
a, b = sigs[i], sigs[j]
sa, sb = set(a.pathways), set(b.pathways)
le_a = {g for genes in a.leading_edge for g in genes}
le_b = {g for genes in b.leading_edge for g in genes}
out.append(PairDrillDown(
a=a.label, b=b.label,
semantic=float(sim[i][j]),
nes_corr=nes_corr[i][j],
shared_pathways=sorted(sa & sb),
unique_a=sorted(sa - sb),
unique_b=sorted(sb - sa),
top_shared_themes=self._top_themes(a, b),
leading_edge_jaccard=_jaccard(le_a, le_b),
))
return out
@staticmethod
def _top_themes(a: Signature, b: Signature, k: int = 3) -> list[dict]:
"""Top semantically-matched pathway pairs across the two signatures."""
M = np.clip(np.asarray(a.embeddings) @ np.asarray(b.embeddings).T, -1.0, 1.0)
flat = sorted(
((float(M[x, y]), x, y) for x in range(M.shape[0]) for y in range(M.shape[1])),
reverse=True,
)
themes, seen_a, seen_b = [], set(), set()
for s, x, y in flat:
if x in seen_a or y in seen_b:
continue
themes.append({"a": a.pathways[x], "b": b.pathways[y], "sim": round(s, 4)})
seen_a.add(x); seen_b.add(y)
if len(themes) >= k:
break
return themes
def describe(self, names: list[str]) -> dict:
return {n: self._desc.get(n) or _prettify(n) for n in names}
def health(self) -> dict:
return {
"status": "ok",
"n_embeddings": len(self._emb),
"embed_dim": config.EMBED_DIM,
"embedder": self._embedder_id,
"model_loaded": self._model is not None,
}