BestYou / embed.py
mat.marra
Embeddings multilingue per il matching esercizi del gestionale (endpoint embed_texts)
be665b2
Raw
History Blame Contribute Delete
2.36 kB
"""
Embeddings di frasi brevi (nomi di esercizi/alimenti) per il matching semantico del gestionale
BestYou (FitRework-KMP): due grafie dello stesso esercizio ("Lat pull dawn presa inversa" vs
"Lat pulldown presa inversa") producono vettori quasi identici -> il gestionale li aggancia al
catalogo con una similarita' coseno, senza chiamate a un LLM.
Modello: paraphrase-multilingual-MiniLM-L12-v2 (multilingue, ~470 MB, CPU-friendly).
I vettori sono NORMALIZZATI: la similarita' coseno e' il semplice prodotto scalare.
"""
import json
import os
import threading
_model = None
_model_lock = threading.Lock()
_MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
def _get_model():
global _model
if _model is None:
with _model_lock:
if _model is None:
# Import pigro: il tab misure resta usabile anche mentre il modello si carica.
from sentence_transformers import SentenceTransformer
_model = SentenceTransformer(_MODEL_NAME, device="cpu")
return _model
def preload_model_async():
"""Scalda il modello in background all'avvio dello Space: la prima chiamata vera non paga
il download/caricamento (che dopo un cold start puo' richiedere minuti)."""
threading.Thread(target=_get_model, daemon=True).start()
def embed_texts(texts_json: str, token: str = "") -> dict:
"""Entry point chiamato dal gestionale. Input: array JSON di stringhe (max 256, troncate a
200 caratteri). Output: {"vectors": [[...], ...]} nello stesso ordine, o {"error": "..."}."""
expected_token = os.environ.get("MEASURE_TOKEN", "")
if expected_token and token != expected_token:
return {"error": "token non valido"}
try:
texts = json.loads(texts_json)
except (TypeError, ValueError):
return {"error": "input non valido: atteso un array JSON di stringhe"}
if not isinstance(texts, list) or not texts or not all(isinstance(t, str) for t in texts):
return {"error": "input non valido: atteso un array JSON di stringhe non vuoto"}
texts = [t.strip()[:200] for t in texts][:256]
vectors = _get_model().encode(texts, normalize_embeddings=True, batch_size=64)
# round: dimezza il payload senza effetto pratico sulla similarita'.
return {"vectors": [[round(float(x), 6) for x in v] for v in vectors]}