tecuhtli commited on
Commit
ad7703f
·
verified ·
1 Parent(s): 57290d8

Update app.py

Browse files

"🚀 Primera versión de Mori Contextual 🤖🐈‍⬛"

Files changed (1) hide show
  1. app.py +280 -126
app.py CHANGED
@@ -1,208 +1,362 @@
1
- import os, sys
 
 
 
2
  os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
3
  import streamlit as st
4
  from pathlib import Path
5
- import torch, json, csv, warnings
6
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
7
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
8
- from pathlib import Path
9
  from unidecode import unidecode
10
  from datetime import datetime
11
- import uuid
 
 
 
 
 
 
 
 
 
12
 
13
- # 🆔 Asigna un ID de sesión si no existe
14
- if "user_id" not in st.session_state:
15
- st.session_state["user_id"] = str(uuid.uuid4())[:8] # Ej: "f6a9b3e2"
16
 
 
 
 
 
 
17
  def limpiar_input():
18
  st.session_state["entrada"] = ""
19
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- def guardar_interaccion_dual(pregunta, respuesta, tipo, user_id):
22
  timestamp = datetime.now().isoformat()
23
 
24
- # 📁 Carpeta local (dentro del Space)
25
  stats_dir = Path("Statistics")
26
  stats_dir.mkdir(parents=True, exist_ok=True)
27
 
28
- # 📄 CSV
29
  archivo_csv = stats_dir / "conversaciones_log.csv"
30
  existe_csv = archivo_csv.exists()
31
 
 
32
  with open(archivo_csv, mode="a", encoding="utf-8", newline="") as f_csv:
33
  writer = csv.writer(f_csv)
34
  if not existe_csv:
35
- writer.writerow(["timestamp", "user_id", "tipo", "pregunta", "respuesta"])
36
- writer.writerow([timestamp, user_id, tipo, pregunta, respuesta])
37
 
38
- # 📄 JSONL
39
  archivo_jsonl = stats_dir / "conversaciones_log.jsonl"
40
  with open(archivo_jsonl, mode="a", encoding="utf-8") as f_jsonl:
41
  registro = {
42
  "timestamp": timestamp,
43
  "user_id": user_id,
44
- "tipo": tipo,
45
- "pregunta": pregunta,
46
- "respuesta": respuesta
47
- }
48
  f_jsonl.write(json.dumps(registro, ensure_ascii=False) + "\n")
49
 
50
 
51
- # Función para cargar modelos
52
  @st.cache_resource
53
  def load_model(path_str):
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  path = Path(path_str).resolve()
55
  tokenizer = AutoTokenizer.from_pretrained(path, local_files_only=True)
56
  model = AutoModelForSeq2SeqLM.from_pretrained(path, local_files_only=True)
 
57
  return model, tokenizer
58
 
59
- def detectar_intencion(texto_usuario):
60
- texto = unidecode(texto_usuario.lower())
61
 
62
- social_keywords = [
63
- "hola", "chiste", "como estas", "gracias", "que pex", "broma", "saludos", "eres", "estudiante", "preguntar algo",
64
- "estas ahi", "que amable", "haces", "kiubo", "bro", "ey", "todo bien", "te puedo", "animo", "hasta luego", "me ayudas",
65
- "motiva", "no entiendo", "te gusta", "futbol", "quien eres", "sentimientos", "canelo", "america", "chivas", "background",
66
- "cuantos años", "proposito", "quien me habla", "te puedo preguntar", "ey bro", "quien te hizo", "que haces", "bonito",
67
- "piropo", "pex", "pasion", "hambre", "camara", "cansado", "adios"
68
- ]
 
 
 
 
69
 
70
- tecnico_keywords = [
71
- "modelo", "entrenamiento", "algoritmo", "regresion", "clasificacion", "overfitting", "datos", "que es",
72
- "define", "explicas", "pca", "cnn", "rnn", "clustering", "precision", "recall", "supervisado", "aprendizaje"
73
- ]
74
 
75
- if any(p in texto for p in social_keywords):
76
- return "Social"
77
- elif any(p in texto for p in tecnico_keywords):
78
- return "Técnica"
79
- else:
80
- return "Técnica"
81
 
 
82
 
83
- def responder_social(texto_usuario, social_model, social_tokenizer):
84
- device = next(social_model.parameters()).device
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- inputs = social_tokenizer(
87
- texto_usuario, # ✅ sin agregar eos_token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  return_tensors="pt",
89
  padding=True,
90
- truncation=True,
91
  max_length=128 # ✅ especificado para evitar warning
92
  ).to(device)
93
 
94
- output_ids = social_model.generate(
 
95
  input_ids=inputs["input_ids"],
96
  attention_mask=inputs["attention_mask"], # ✅ FIX agregado
97
  max_length=50,
98
- pad_token_id=social_tokenizer.eos_token_id,
99
  do_sample=True,
100
  top_p=0.95,
101
- top_k=50
102
- )
103
-
104
- respuesta = social_tokenizer.decode(output_ids[0], skip_special_tokens=True)
105
- return respuesta.strip()
106
 
107
- def responder_tecnico(texto_usuario, technical_model, technical_tokenizer):
108
- entrada = "pregunta: " + texto_usuario
109
- device = next(technical_model.parameters()).device
 
110
 
111
- inputs = technical_tokenizer(
112
- entrada,
113
- return_tensors="pt",
114
- padding=True,
115
- truncation=True,
116
- max_length=128 # ✅ truncación explícita
117
- ).to(device)
118
 
119
- output = technical_model.generate(
120
- input_ids=inputs["input_ids"],
121
- attention_mask=inputs["attention_mask"], # ✅ FIX agregado
122
- max_length=64
123
- )
124
 
125
- respuesta = technical_tokenizer.decode(output[0], skip_special_tokens=True)
126
- return respuesta.strip()
 
 
 
 
 
 
 
 
 
 
127
 
 
128
 
129
- def responder_mori(texto_usuario):
130
- intencion = detectar_intencion(texto_usuario)
131
- #print("a ver: ", texto_usuario)
132
- if intencion == "Social":
133
- return responder_social(texto_usuario)
134
- elif intencion == "Técnica":
135
- return responder_tecnico(texto_usuario)
136
- else:
137
- return responder_tecnico(texto_usuario) #"🤔 No entendí bien... ¿quieres hablar de ciencia de datos o echar el chisme?"
138
 
 
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- if "historial" not in st.session_state:
142
- st.session_state.historial = []
 
 
143
 
 
144
 
145
- # Modelo Técnico
146
- tokenizer_tecnico = AutoTokenizer.from_pretrained("tecuhtli/mori-tecnico-model")
147
- model_tecnico = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-tecnico-model")
148
 
149
- # Modelo Social
150
- tokenizer_social = AutoTokenizer.from_pretrained("tecuhtli/mori-social-model")
151
- model_social = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-social-model")
152
 
153
- # Dispositivo
154
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
155
- model_social = model_social.to(device)
156
- model_tecnico = model_tecnico.to(device)
157
 
158
- st.title("🤖 Mori - Tu Asistente Personal")
159
- st.header("Experto en Procesamiento de Datos 🐈‍")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
- #entrada_usuario = st.text_area("📝 Escribe tu pregunta aquí")
 
 
162
 
163
- with st.form("formulario_mori"):
164
- entrada_usuario = st.text_area("📝 Escribe tu pregunta aquí", key="entrada", height=100)
165
- submitted = st.form_submit_button("Responder")
166
 
167
- if submitted:
168
- #if st.button("Responder") and entrada_usuario:
169
- opcion = detectar_intencion(entrada_usuario)
170
- if opcion == "Técnica":
171
- respuesta = "🧠 [Mori Técnico] " + responder_tecnico(entrada_usuario, model_tecnico, tokenizer_tecnico)
172
- st.success(respuesta)
173
- else:
174
- respuesta = "🤝 [Mori Social] " + responder_social(entrada_usuario, model_social, tokenizer_social)
175
- st.success(respuesta)
176
 
177
- # Guarda en historial
178
- st.session_state.historial.append(("Mori", respuesta))
179
- st.session_state.historial.append(("Tú", entrada_usuario))
180
 
181
- # 💾 Guarda en archivo para stats/dataset
182
- guardar_interaccion_dual(entrada_usuario, respuesta, opcion, st.session_state["user_id"])
 
 
183
 
184
 
185
-
186
 
 
 
187
 
188
- # 🔁 Muestra historial
189
- if st.session_state.historial:
190
- st.markdown("---")
191
- for autor, texto in reversed(st.session_state.historial):
192
- if autor == "Tú":
193
- st.markdown(f"🧍‍♂️ **{autor}**: {texto}")
194
  else:
195
- st.markdown(f"🤖 **{autor}**: {texto}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
 
 
 
 
 
197
 
198
- if st.session_state.historial:
199
- texto_chat = ""
200
- for autor, texto in st.session_state.historial:
201
- texto_chat += f"{autor}: {texto}\n\n"
202
 
203
- st.download_button(
204
- label="💾 Descargar conversación como .txt",
205
- data=texto_chat,
206
- file_name="conversacion_mori.txt",
207
- mime="text/plain"
208
- )
 
1
+ #***************************************************************************
2
+ #Importing Libraries
3
+ #***************************************************************************
4
+ import os, sys, torch, json, csv, warnings, joblib, uuid
5
  os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
6
  import streamlit as st
7
  from pathlib import Path
 
8
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
 
 
9
  from unidecode import unidecode
10
  from datetime import datetime
11
+ from huggingface_hub import hf_hub_download
12
+ #***************************************************************************
13
+ #Defining default paths for the model to work
14
+ #***************************************************************************
15
+
16
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
17
+
18
+ #***************************************************************************
19
+ #Setting up variables
20
+ #***************************************************************************
21
 
 
 
 
22
 
23
+ #***************************************************************************
24
+ #Functions
25
+ #***************************************************************************
26
+
27
+ # Function to clean the question field
28
  def limpiar_input():
29
  st.session_state["entrada"] = ""
30
 
31
+ # Function to save user interaction
32
+ def saving_interaction(question, response, context, user_id):
33
+
34
+ '''
35
+ inputs:
36
+
37
+ question --> User input question
38
+ response --> Mori response to the user question
39
+ context --> Context related to the user input, found by the trained classifier
40
+ user_id --> ID for the current user (Unique ID per session)
41
+
42
+ '''
43
 
44
+ # Getting the current time for the saving log
45
  timestamp = datetime.now().isoformat()
46
 
47
+ # Defining the path to save the current interaction
48
  stats_dir = Path("Statistics")
49
  stats_dir.mkdir(parents=True, exist_ok=True)
50
 
51
+ # Setting the file to save the interactions
52
  archivo_csv = stats_dir / "conversaciones_log.csv"
53
  existe_csv = archivo_csv.exists()
54
 
55
+ # Saving statistics as a csv
56
  with open(archivo_csv, mode="a", encoding="utf-8", newline="") as f_csv:
57
  writer = csv.writer(f_csv)
58
  if not existe_csv:
59
+ writer.writerow(["timestamp", "user_id", "contexto", "pregunta", "respuesta"])
60
+ writer.writerow([timestamp, user_id, context, question, response])
61
 
62
+ # Saving statiistics as a json file
63
  archivo_jsonl = stats_dir / "conversaciones_log.jsonl"
64
  with open(archivo_jsonl, mode="a", encoding="utf-8") as f_jsonl:
65
  registro = {
66
  "timestamp": timestamp,
67
  "user_id": user_id,
68
+ "context": context,
69
+ "pregunta": question,
70
+ "respuesta": response}
 
71
  f_jsonl.write(json.dumps(registro, ensure_ascii=False) + "\n")
72
 
73
 
74
+ # Function to load models within the huggingface respositories space
75
  @st.cache_resource
76
  def load_model(path_str):
77
+
78
+ '''
79
+ inputs:
80
+
81
+ path_str --> Path for loading models and tokenizers
82
+
83
+ outsputs:
84
+
85
+ model --> Loaded Model
86
+ tokenizer --> Loaded tokenizer
87
+
88
+ '''
89
+
90
  path = Path(path_str).resolve()
91
  tokenizer = AutoTokenizer.from_pretrained(path, local_files_only=True)
92
  model = AutoModelForSeq2SeqLM.from_pretrained(path, local_files_only=True)
93
+
94
  return model, tokenizer
95
 
 
 
96
 
97
+ # Funcion para clasificar las preguntas del usuario definiendo el contexto de las mismas
98
+ def classify_context(question, label_classes, model, tokenizer, device):
99
+
100
+ '''
101
+ inputs:
102
+
103
+ question --> Pregunta formulada por el usuario
104
+ label_classes --> Clases del label encoder para decodificar inferencias
105
+ model --> Clasificador para determinar el contexto de las pregutnas
106
+ tokenizer --> Tokenizer usada para clasificar contextos
107
+ device --> Usar el GPU o el CPU dependiendo de su disponibilidad
108
 
109
+ outsputs:
 
 
 
110
 
111
+ predicted_label --> Clasificacion de la pregunta en diversos contextos (clases)
 
 
 
 
 
112
 
113
+ '''
114
 
115
+ # Moviendo el modelo al device disponible
116
+ model = model.to(device)
117
+
118
+ # Procesando la entrada del usuario
119
+ inputs = tokenizer(question, return_tensors="pt", padding=True, truncation=True, max_length=128)
120
+ inputs = {key: val.to(device) for key, val in inputs.items()}
121
+
122
+ # Clasificacion de la pregunta del usuario en contextos
123
+ with torch.no_grad():
124
+ outputs = model(**inputs)
125
+ logits = outputs.logits
126
+
127
+ # Inferencia del clasificador
128
+ pred_intent = torch.argmax(logits, dim=1).item()
129
+ predicted_label = label_classes[pred_intent]
130
+
131
+ return predicted_label
132
 
133
+
134
+
135
+ # Funcion para generar respuestas tecnicas de Mori
136
+ def technical_asnwer(question, context, model, tokenizer, device):
137
+
138
+ '''
139
+ inputs:
140
+
141
+ question --> Pregunta formulada por el usuario
142
+ context --> Contexto de la preguntadel usario definido por el clasificador
143
+ model --> Modelo de Mori para responder preguntas tecnicas
144
+ tokenizer --> Tokenizer usado para procesar entradas y decoodificar respuestas
145
+ device --> Usar el GPU o el CPU dependiendo de su disponibilidad
146
+
147
+ outsputs:
148
+
149
+ response --> Respues de Mori tecnico (Modelo tecnico)
150
+
151
+ '''
152
+
153
+ # Moviendo el modelo al device disponible
154
+ model = model.to(device)
155
+
156
+ # Promp Engineering para ayudar a Mori a encontrar la mejor respuesta
157
+ input_text = f"Context: {context} [SEP] Question: {question}"
158
+
159
+ # Tokenizando el texto de entrada
160
+ inputs = tokenizer(input_text, return_tensors="pt").to(device)
161
+
162
+ # Generando la respuesta
163
+ summary_ids = model.generate(inputs['input_ids'], max_length=150, num_beams=5, early_stopping=True)
164
+
165
+ # Decodificando la respuesta
166
+ response = tokenizer.decode(summary_ids[0], skip_special_tokens=True)
167
+
168
+ return "🧠 [Mori Técnico] " + response.strip()
169
+
170
+
171
+ # Funcion para generar respuestas sociales de Mori
172
+ def social_asnwer(question, model, tokenizer, device):
173
+
174
+ '''
175
+ inputs:
176
+
177
+ question --> Pregunta formulada por el usuario
178
+ model --> Modelo de Mori para responder preguntas sociales
179
+ tokenizer --> Tokenizer usado para procesar entradas y decoodificar respuestas
180
+ device --> Usar el GPU o el CPU dependiendo de su disponibilidad
181
+
182
+ outsputs:
183
+
184
+ response --> Respues de Mori social (Modelo social)
185
+
186
+ '''
187
+
188
+ # Moviendo el modelo al device disponible
189
+ model = model.to(device)
190
+
191
+ # Tokenizando la entrada del usuario sin agregar <eos> explícitamente
192
+ inputs = tokenizer(
193
+ question, # ✅ sin agregar eos_token
194
  return_tensors="pt",
195
  padding=True,
196
+ truncation=True,
197
  max_length=128 # ✅ especificado para evitar warning
198
  ).to(device)
199
 
200
+ # Generando respuesta usando muestreo
201
+ output_ids = model.generate(
202
  input_ids=inputs["input_ids"],
203
  attention_mask=inputs["attention_mask"], # ✅ FIX agregado
204
  max_length=50,
205
+ pad_token_id= tokenizer.eos_token_id,
206
  do_sample=True,
207
  top_p=0.95,
208
+ top_k=50)
 
 
 
 
209
 
210
+ # Decodificando y limpiando la respuesta
211
+ response = tokenizer.decode(output_ids[0], skip_special_tokens=True)
212
+
213
+ return "🤝 [Mori Social] " + response.strip()
214
 
 
 
 
 
 
 
 
215
 
216
+ # Funcion para generar respuesta general de Mori
217
+ def contextual_asnwer(question, label_classes, context_model, cont_tok, tec_model, tec_tok, soc_model, soc_tok, device):
 
 
 
218
 
219
+ '''
220
+ inputs:
221
+
222
+ question --> Pregunta formulada por el usuario
223
+ label_classes --> Clases del label encoder para decodificar inferencias
224
+ context_model --> Clasificador para determinar el contexto de las pregutnas
225
+ cont_tok --> Tokenizer usada para clasificar contextos
226
+ tec_model --> Modelo de Mori para responder preguntas tecnicas
227
+ tec_tok --> Tokenizer usado por Mori Tenico
228
+ soc_model --> Modelo de Mori para responder preguntas sociales
229
+ soc_tok --> Tokenizer usado por Mori Social
230
+ device --> Usar el GPU o el CPU dependiendo de su disponibilidad
231
 
232
+ outsputs:
233
 
234
+ response --> Respues de Mori General (Respues con Prompt Engineering)
 
 
 
 
 
 
 
 
235
 
236
+ '''
237
 
238
+ # Detectar contexto usando el clasificador
239
+ context = classify_context(question, label_classes, context_model, cont_tok, device)
240
+
241
+ context_icons = {"social": "💬",
242
+ "modelos": "🔧",
243
+ "evaluación": "📏",
244
+ "optimización": "⚙️",
245
+ "visualización": "📈",
246
+ "aprendizaje": "🧠",
247
+ "vida digital" : "🧑‍💻",
248
+ "estadística": "📊",
249
+ "infraestructura": "🖥",
250
+ "datos": "📂",
251
+ "transformación digital": "🌀"}
252
+
253
+ icon = context_icons.get(context, "🧠")
254
+ #print(f"{icon} Contexto detectado: {context}") # (opcional para debug)
255
+ st.markdown(f"**{icon} Contexto detectado:** `{context}`")
256
+
257
+ if context == 'social':
258
+
259
+ # Generar respuesta contextual usando el modelo social
260
+ response = social_asnwer(question, soc_model,soc_tok, device)
261
 
262
+ else:
263
+
264
+ # Generar respuesta contextual usando el modelo tecnico
265
+ response = technical_asnwer(question, context, tec_model, tec_tok, device)
266
 
267
+ return response, context
268
 
269
+
 
 
270
 
271
+ #***************************************************************************
272
+ #MAIN
273
+ #***************************************************************************
274
 
275
+ if __name__ == '__main__':
 
 
 
276
 
277
+ # Setting historial for the current user
278
+ if "historial" not in st.session_state:
279
+ st.session_state.historial = []
280
+
281
+ # Addigning a new ID to the current user
282
+ if "user_id" not in st.session_state:
283
+ st.session_state["user_id"] = str(uuid.uuid4())[:8] # Ej: "f6a9b3e2"
284
+
285
+ # Loading classifier encoder classes:
286
+ labels_path = hf_hub_download(repo_id="tecuhtli/mori-context-model", filename="context_labels.pkl")
287
+ label_classes = joblib.load(labels_path)
288
+
289
+ # Loading Saved Models
290
+ # Modelo Contexto
291
+ cont_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-context-model")
292
+ context_model = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-context-model")
293
+
294
+ # Modelo Técnico
295
+ tec_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-tecnico-model")
296
+ tec_model = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-tecnico-model")
297
 
298
+ # Modelo Social
299
+ soc_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-social-model")
300
+ soc_model = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-social-model")
301
 
302
+ # Available Device
303
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
304
 
305
+ # Defining Moris Presetation
306
+ st.title("🤖 Mori - Tu Asistente Personal 🐈")
307
+ st.caption("💬 Puedes preguntarme conceptos técnicos como visualización, limpieza, BI, etc.")
308
+ st.caption("😅 Por el momento, solo puedo contestar preguntas como: ")
309
+ st.caption("🤓 ¿Como estas? ¿Que son?, Explícame algo, Define algo, ¿Para que sirven?")
310
+ st.caption("✏️ Escribe 'salir' para terminar.\n")
 
 
 
311
 
 
 
 
312
 
313
+ #entrada_usuario = st.text_area("📝 Escribe tu pregunta aquí")
314
+ with st.form("formulario_mori"):
315
+ user_question = st.text_area("📝 Escribe tu pregunta aquí", key="entrada", height=100)
316
+ submitted = st.form_submit_button("Responder")
317
 
318
 
319
+ if submitted:
320
 
321
+ if not user_question:
322
+ print("Mori: ¿Podrías repetir eso? No entendí bien 😅")
323
 
 
 
 
 
 
 
324
  else:
325
+
326
+ #if st.button("Responder") and entrada_usuario:
327
+ response, context = contextual_asnwer(user_question, label_classes, context_model, cont_tok, tec_model, tec_tok, soc_model, soc_tok, device)
328
+ st.success(response)
329
+
330
+ # Guarda en historial
331
+ st.session_state.historial.append(("Mori", response))
332
+ st.session_state.historial.append(("Tú", user_question))
333
+
334
+ # 💾 Guarda en archivo para stats/dataset
335
+ saving_interaction(user_question, response, context, st.session_state["user_id"])
336
+
337
+
338
+ # 🔁 Muestra historial
339
+ if st.session_state.historial:
340
+ st.markdown("---")
341
+ for autor, texto in reversed(st.session_state.historial):
342
+ if autor == "Tú":
343
+ st.markdown(f"🧍‍♂️ **{autor}**: {texto}")
344
+ else:
345
+ st.markdown(f"🤖 **{autor}**: {texto}")
346
+
347
+
348
+ if st.session_state.historial:
349
+ texto_chat = ""
350
+ for autor, texto in st.session_state.historial:
351
+ texto_chat += f"{autor}: {texto}\n\n"
352
 
353
+ st.download_button(
354
+ label="💾 Descargar conversación como .txt",
355
+ data=texto_chat,
356
+ file_name="conversacion_mori.txt",
357
+ mime="text/plain")
358
 
 
 
 
 
359
 
360
+ #***************************************************************************
361
+ #FIN
362
+ #***************************************************************************