RthItalia commited on
Commit
281eb38
·
verified ·
1 Parent(s): f2a34d5

Add Gradio showcase app

Browse files
README.md CHANGED
@@ -19,6 +19,7 @@ Questo repository e una **showcase pubblica** di AIO System Core.
19
 
20
  ## Cosa include
21
  - Demo Streamlit locale (`app_showcase.py`)
 
22
  - Mini corpus dimostrativo (`demo_corpus.json`)
23
  - Documentazione tecnica essenziale
24
  - Policy non-commerciale
@@ -45,6 +46,14 @@ pip install -r requirements.txt
45
  streamlit run app_showcase.py
46
  ```
47
 
 
 
 
 
 
 
 
 
48
  ## Licenza
49
  - Libera per uso e studio
50
  - Uso commerciale non consentito senza autorizzazione scritta
@@ -52,4 +61,3 @@ streamlit run app_showcase.py
52
 
53
  ## DOI di riferimento AIO
54
  https://doi.org/10.6084/m9.figshare.31384528
55
-
 
19
 
20
  ## Cosa include
21
  - Demo Streamlit locale (`app_showcase.py`)
22
+ - Demo Gradio locale/Space (`app_gradio.py`)
23
  - Mini corpus dimostrativo (`demo_corpus.json`)
24
  - Documentazione tecnica essenziale
25
  - Policy non-commerciale
 
46
  streamlit run app_showcase.py
47
  ```
48
 
49
+ ## Avvio Gradio
50
+ ```bash
51
+ pip install -r requirements.txt
52
+ python app_gradio.py
53
+ ```
54
+
55
+ Per Hugging Face Spaces (SDK Gradio), usa `app_gradio.py` come entrypoint.
56
+
57
  ## Licenza
58
  - Libera per uso e studio
59
  - Uso commerciale non consentito senza autorizzazione scritta
 
61
 
62
  ## DOI di riferimento AIO
63
  https://doi.org/10.6084/m9.figshare.31384528
 
__pycache__/app_gradio.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
app_gradio.py ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import json
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Dict, List
6
+
7
+ import gradio as gr
8
+ import numpy as np
9
+
10
+ try:
11
+ from sentence_transformers import SentenceTransformer
12
+ except Exception: # pragma: no cover
13
+ SentenceTransformer = None
14
+
15
+
16
+ STOPWORDS = {
17
+ "della",
18
+ "delle",
19
+ "dello",
20
+ "degli",
21
+ "dati",
22
+ "sono",
23
+ "come",
24
+ "questa",
25
+ "questo",
26
+ "nella",
27
+ "nelle",
28
+ "anche",
29
+ "molto",
30
+ "dove",
31
+ "quando",
32
+ "with",
33
+ "that",
34
+ "from",
35
+ "have",
36
+ "your",
37
+ "will",
38
+ "about",
39
+ "parlami",
40
+ "dimmi",
41
+ "spiegami",
42
+ "cosa",
43
+ "quale",
44
+ }
45
+
46
+
47
+ def normalizza_testo(t: str) -> str:
48
+ t = (t or "").replace("\n", " ").replace("\t", " ").strip()
49
+ return re.sub(r"\s+", " ", t)
50
+
51
+
52
+ def tokenizza(testo: str) -> List[str]:
53
+ candidati = re.findall(r"[A-Za-z0-9_]+", testo.lower())
54
+ return [t for t in candidati if len(t) >= 4 and t not in STOPWORDS]
55
+
56
+
57
+ class LocalHashEmbedder:
58
+ def __init__(self, dim: int = 384):
59
+ self.dim = int(dim)
60
+ self.name = f"local-hash-{self.dim}"
61
+
62
+ def _tokens(self, text: str) -> List[str]:
63
+ candidati = re.findall(r"[A-Za-z0-9_]+", text.lower())
64
+ return [t for t in candidati if len(t) >= 3 and t not in STOPWORDS]
65
+
66
+ def encode(self, texts):
67
+ if isinstance(texts, str):
68
+ texts = [texts]
69
+ out = np.zeros((len(texts), self.dim), dtype="float32")
70
+ for i, text in enumerate(texts):
71
+ clean = normalizza_testo(text)
72
+ toks = self._tokens(clean) or clean.lower().split()
73
+ for tok in toks:
74
+ h = int(hashlib.sha1(tok.encode("utf-8", errors="ignore")).hexdigest(), 16)
75
+ idx = h % self.dim
76
+ sign = -1.0 if ((h >> 8) & 1) else 1.0
77
+ out[i, idx] += sign
78
+ norm = float(np.linalg.norm(out[i]))
79
+ if norm > 0:
80
+ out[i] /= norm
81
+ return out
82
+
83
+
84
+ def inizializza_embedder():
85
+ model_name = "paraphrase-multilingual-MiniLM-L12-v2"
86
+ local_path = Path("aio_models") / model_name
87
+ if SentenceTransformer is not None and local_path.exists():
88
+ try:
89
+ model = SentenceTransformer(str(local_path), local_files_only=True)
90
+ return model, f"sentence-transformers(local-path): {local_path}"
91
+ except Exception:
92
+ pass
93
+ return LocalHashEmbedder(dim=384), "local-hash-embedder (showcase fallback)"
94
+
95
+
96
+ def vectorizza(model, testi: List[str]) -> np.ndarray:
97
+ v = model.encode(testi)
98
+ arr = np.array(v, dtype="float32")
99
+ norms = np.linalg.norm(arr, axis=1, keepdims=True)
100
+ norms[norms == 0] = 1.0
101
+ return arr / norms
102
+
103
+
104
+ def carica_corpus() -> List[Dict[str, str]]:
105
+ here = Path(__file__).resolve().parent
106
+ path = here / "demo_corpus.json"
107
+ data = json.loads(path.read_text(encoding="utf-8"))
108
+ for r in data:
109
+ r["text"] = normalizza_testo(r.get("text", ""))
110
+ return data
111
+
112
+
113
+ def prepara_engine():
114
+ model, backend = inizializza_embedder()
115
+ records = carica_corpus()
116
+ texts = [r["text"] for r in records]
117
+ mat = vectorizza(model, texts)
118
+ return model, backend, records, mat
119
+
120
+
121
+ def cerca(query: str, model, records, mat: np.ndarray, top_k: int = 5):
122
+ qv = vectorizza(model, [query])[0]
123
+ sims = mat @ qv
124
+ qtok = set(tokenizza(query))
125
+ out = []
126
+ for i, s in enumerate(sims):
127
+ rec = records[i]
128
+ ttok = set(tokenizza(rec["text"]))
129
+ overlap = len(qtok.intersection(ttok)) if qtok else 0
130
+ lex = (overlap / max(1, len(qtok))) if qtok else 0.0
131
+ score = 0.6 * float(s) + 0.4 * float(lex)
132
+ out.append(
133
+ {
134
+ "score": score,
135
+ "sim": float(s),
136
+ "lex": float(lex),
137
+ "domain": rec.get("domain", "generale"),
138
+ "source": rec.get("source", "showcase"),
139
+ "text": rec.get("text", ""),
140
+ }
141
+ )
142
+ out.sort(key=lambda x: x["score"], reverse=True)
143
+ return out[:top_k]
144
+
145
+
146
+ MODEL, BACKEND, RECORDS, MAT = prepara_engine()
147
+
148
+
149
+ def esegui_ricerca(query: str, top_k: int):
150
+ query = normalizza_testo(query)
151
+ if not query:
152
+ return "Inserisci una domanda.", f"Backend: {BACKEND} | Records demo: {len(RECORDS)}"
153
+
154
+ risultati = cerca(query, MODEL, RECORDS, MAT, top_k=int(top_k))
155
+ lines = []
156
+ for i, r in enumerate(risultati, start=1):
157
+ lines.append(
158
+ f"### {i}. [{r['domain']}] score={r['score']:.3f} sim={r['sim']:.3f} lex={r['lex']:.3f}\n"
159
+ f"source: `{r['source']}`\n\n{r['text']}\n"
160
+ )
161
+ lines.append(
162
+ "\n---\nLicenza Showcase: uso e studio liberi, uso commerciale solo su autorizzazione scritta. "
163
+ "Contatto: `info@rthitalia.com`"
164
+ )
165
+ return "\n".join(lines), f"Backend: {BACKEND} | Records demo: {len(RECORDS)}"
166
+
167
+
168
+ with gr.Blocks(title="AIO System Core - Public Showcase") as demo:
169
+ gr.Markdown("# AIO System Core - Public Showcase")
170
+ gr.Markdown(
171
+ "Demo pubblica controllata. Core proprietario e corpus completo restano privati."
172
+ )
173
+ stato = gr.Markdown(f"Backend: {BACKEND} | Records demo: {len(RECORDS)}")
174
+
175
+ with gr.Row():
176
+ query = gr.Textbox(
177
+ label="Inserisci una domanda",
178
+ value="Parlami del Mediterraneo e della geopolitica energetica",
179
+ lines=3,
180
+ )
181
+ top_k = gr.Slider(label="Top K", minimum=3, maximum=10, value=5, step=1)
182
+ run = gr.Button("Esegui ricerca", variant="primary")
183
+ output = gr.Markdown()
184
+
185
+ run.click(esegui_ricerca, inputs=[query, top_k], outputs=[output, stato])
186
+
187
+
188
+ if __name__ == "__main__":
189
+ demo.launch(server_name="0.0.0.0", server_port=7860)
requirements.txt CHANGED
@@ -1,3 +1,4 @@
1
  streamlit>=1.42
2
  numpy>=1.26
3
  sentence-transformers>=3.0
 
 
1
  streamlit>=1.42
2
  numpy>=1.26
3
  sentence-transformers>=3.0
4
+ gradio>=5.0