""" 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]}