File size: 2,361 Bytes
be665b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
"""
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]}