Spaces:
Sleeping
Sleeping
mat.marra
Embeddings multilingue per il matching esercizi del gestionale (endpoint embed_texts)
be665b2 | """ | |
| 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]} | |