Skip to content

Commit d974acc

Browse files
CopilotturbomamCopilot
authored
fix(cross_ontology_search): embed cache, strict cosine, early qv=None return, hardware comment (#561)
* Initial plan * fix cross_ontology_search: clarify hardware comment, add embed cache, strict cosine, early return on qv=None * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mark Andrew Miller <MAM@lbl.gov> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 7b64bbf commit d974acc

1 file changed

Lines changed: 26 additions & 5 deletions

File tree

metpo/pipeline/cross_ontology_search.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@
3636
import click
3737

3838
OLS_SEARCH = "https://www.ebi.ac.uk/ols4/api/search"
39-
# Default embedding backend: free, local, runs on the M5 GPU. Nothing paid, no API
39+
# Default embedding backend: free, local, runs on CPU/GPU-capable hardware. Nothing paid, no API
4040
# key. Both the endpoint and the model are CLI-parameterized so anyone can point at
4141
# a different local model, a remote Ollama server, or other hardware.
4242
DEFAULT_EMBED_URL = "http://localhost:11434/api/embeddings"
4343
DEFAULT_EMBED_MODEL = "nomic-embed-text"
4444

45+
# In-memory cache to avoid redundant embedding API calls within a run.
46+
_EMBED_CACHE = {}
47+
4548

4649
def ols_candidates(label, rows=20):
4750
"""Lexical candidate classes from OLS4 for a label (IRI, curie, label, def, ontology)."""
@@ -90,8 +93,22 @@ def embed(text, model, embed_url=DEFAULT_EMBED_URL):
9093
return None
9194

9295

96+
def _get_cached_embedding(text, model, embed_url):
97+
normalized = text.strip(". ")
98+
key = (model, embed_url, normalized)
99+
if key not in _EMBED_CACHE:
100+
v = embed(normalized, model, embed_url)
101+
if v is not None:
102+
_EMBED_CACHE[key] = v
103+
return v
104+
return _EMBED_CACHE[key]
105+
106+
93107
def cosine(a, b):
94-
dot = sum(x * y for x, y in zip(a, b, strict=False))
108+
if len(a) != len(b):
109+
msg = f"cosine vector dimension mismatch: len(a)={len(a)} != len(b)={len(b)}"
110+
raise ValueError(msg)
111+
dot = sum(x * y for x, y in zip(a, b, strict=True))
95112
na = math.sqrt(sum(x * x for x in a))
96113
nb = math.sqrt(sum(x * x for x in b))
97114
return dot / (na * nb) if na and nb else 0.0
@@ -130,10 +147,14 @@ def load_terms(metpo_tsv, label, definition):
130147
def rank_candidates(term, cands, use_embeddings, model, embed_url):
131148
"""Attach a 'similarity' score to each candidate (embedding cosine or lexical order)."""
132149
if use_embeddings and cands:
133-
qv = embed((term["label"] + ". " + term["definition"]).strip(". "), model, embed_url)
150+
qv = _get_cached_embedding(term["label"] + ". " + term["definition"], model, embed_url)
151+
if not qv:
152+
for rank, c in enumerate(cands):
153+
c["similarity"] = round(1.0 - rank / max(len(cands), 1), 4)
154+
return cands
134155
for c in cands:
135-
cv = embed((c["label"] + ". " + c["definition"]).strip(". "), model, embed_url)
136-
c["similarity"] = round(cosine(qv, cv), 4) if (qv and cv) else 0.0
156+
cv = _get_cached_embedding(c["label"] + ". " + c["definition"], model, embed_url)
157+
c["similarity"] = round(cosine(qv, cv), 4) if cv else 0.0
137158
cands.sort(key=lambda c: c["similarity"], reverse=True)
138159
else:
139160
for rank, c in enumerate(cands):

0 commit comments

Comments
 (0)