tecuhtli commited on
Commit
a59e76c
·
verified ·
1 Parent(s): 4302827

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -978
app.py CHANGED
@@ -1,18 +1,18 @@
1
- #***************************************************************************
2
- # Importing Libraries
3
- #***************************************************************************
4
- import os, sys, warnings, torch, json, csv, warnings, joblib, uuid, re, unicodedata, faiss
 
 
5
  import numpy as np
6
- os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0"
7
  import streamlit as st
8
  import datetime as dt
9
  from pathlib import Path
10
- from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, AutoModelForSequenceClassification
11
- from unidecode import unidecode
12
- from datetime import datetime
13
  from huggingface_hub import hf_hub_download, login
14
  from sentence_transformers import SentenceTransformer # RAG embeddings
15
-
16
  #***************************************************************************
17
  #Setting up variables
18
  #***************************************************************************
@@ -24,25 +24,20 @@ HF_TOKEN = os.environ.get("HF_TOKEN")
24
  #***************************************************************************
25
  REPO_ID = "tecuhtli/Mori_FAISS_Full"
26
 
 
27
  #***************************************************************************
28
  # Sidebar controls for generation params
29
  #***************************************************************************
30
 
31
  def sidebar_params():
32
-
33
  with st.sidebar:
34
  st.title("🎮 Personalidad (FLAN-T5)")
35
 
36
  ss = st.session_state
37
- # Defaults (solo 1ª vez)
38
-
39
- # Estado inicial: ocultar ajustes avanzados
40
- ss = st.session_state
41
- if "show_llm_controls" not in ss:
42
- ss.show_llm_controls = False
43
 
44
-
45
- ss.setdefault("persona", "Mori Normal")
 
46
  ss.setdefault("mode", "beam") # 'beam' | 'sampling'
47
  ss.setdefault("max_new", 128)
48
  ss.setdefault("min_tok", 16)
@@ -52,7 +47,6 @@ def sidebar_params():
52
  ss.setdefault("temperature", 0.7)
53
  ss.setdefault("top_p", 0.9)
54
  ss.setdefault("repetition_penalty", 1.0)
55
- ss.setdefault("show_llm_controls", True) # Toggle principal
56
 
57
  # ----------------------------
58
  # Personalidad (presets)
@@ -61,133 +55,40 @@ def sidebar_params():
61
  c1, c2 = st.columns(2)
62
 
63
  with c1:
64
- if st.button("Normal 🧐", use_container_width=True):
65
- ss.update({
66
- "persona": "Mori Normal",
67
- "mode": "beam",
68
- "num_beams": 4,
69
- "max_new": 128,
70
- "min_tok": 16,
71
- "no_repeat": 3,
72
- "length_penalty": 1.0,
73
- "temperature": 0.7,
74
- "top_p": 0.9,
75
- "repetition_penalty": 1.0,
76
- })
77
  st.rerun()
78
 
79
  with c2:
80
- if st.button("Entusiasta 😃", use_container_width=True):
81
- ss.update({
82
- "persona": "Mori Entusiasta", # <- corregido
83
- "mode": "sampling",
84
- "max_new": 192,
85
- "min_tok": 48,
86
- "no_repeat": 3,
87
- "temperature": 1.25,
88
- "top_p": 0.95,
89
- "repetition_penalty": 1.0,
90
- })
91
  st.rerun()
92
 
93
  st.caption(f"Personalidad actual: **{ss.persona}**")
94
 
95
- # ----------------------------
96
- # Botón para mostrar/ocultar parámetros
97
- # ----------------------------
98
- if st.button(("🔼 Ocultar" if ss.show_llm_controls else "🔽 Mostrar") + " ajustes avanzados"):
99
- ss.show_llm_controls = not ss.show_llm_controls
100
- st.rerun()
101
-
102
- # ----------------------------
103
- # Controles del modelo (sliders, estrategia, etc.)
104
- # ----------------------------
105
- if ss.show_llm_controls:
106
- st.header("⚙️ Ajustes manuales")
107
- st.subheader("📝 Generación de texto")
108
- picked = st.radio(
109
- "Estrategia",
110
- ["Beam search (estable)", "Sampling (creativo)"],
111
- index=0 if ss.mode == "beam" else 1,
112
- help="https://huggingface.co/docs/transformers/generation_strategies"
113
- )
114
- ss.mode = "beam" if picked.startswith("Beam") else "sampling"
115
-
116
- st.subheader("🔧 Parámetros del modelo LLM")
117
- ss.max_new = st.slider(
118
- "max_new_tokens", 16, 256, int(ss.max_new), step=8,
119
- help="https://huggingface.co/docs/transformers/main_classes/text_generation"
120
- )
121
- ss.min_tok = st.slider(
122
- "min_tokens", 0, int(ss.max_new), int(ss.min_tok),
123
- help="https://huggingface.co/docs/transformers/main_classes/text_generation"
124
- )
125
- ss.no_repeat = st.slider(
126
- "no_repeat_ngram_size", 0, 6, int(ss.no_repeat),
127
- help="https://huggingface.co/docs/transformers/main_classes/text_generation"
128
- )
129
-
130
- # Subcontroles según modo
131
- if ss.mode == "beam":
132
- ss.num_beams = st.slider(
133
- "num_beams", 2, 8, int(ss.num_beams),
134
- help="https://huggingface.co/docs/transformers/main_classes/text_generation"
135
- )
136
- ss.length_penalty = st.slider(
137
- "length_penalty", 0.0, 2.0, float(ss.length_penalty),
138
- step=0.1, help="https://huggingface.co/docs/transformers/main_classes/text_generation"
139
- )
140
- else:
141
- ss.temperature = st.slider(
142
- "temperature", 0.1, 1.5, float(ss.temperature),
143
- step=0.05, help="https://huggingface.co/docs/transformers/main_classes/text_generation"
144
- )
145
- ss.top_p = st.slider(
146
- "top_p", 0.5, 1.0, float(ss.top_p),
147
- step=0.01, help="https://huggingface.co/docs/transformers/main_classes/text_generation"
148
- )
149
-
150
-
151
- # 🔹 Nueva sección que quieres mostrar siempre
152
- st.markdown("---")
153
- st.title("✏️🛠 Prompt Engineering")
154
-
155
- prompt_type = st.radio(
156
- "🧑🏽‍🔬 Modo de prompting",
157
- options=["Zero-shot", "One-shot", "Few-shot (3)"],
158
- index=0,
159
- help=("https://huggingface.co/docs/transformers/en/tasks/prompting"))
160
- st.session_state.prompt_type = prompt_type
161
-
162
- # Aquí podrías agregar el preview del prompt, o el comparador si lo usas
163
- # En session_state:
164
- if "PROMPT_CASES" not in st.session_state:
165
- st.session_state.PROMPT_CASES = load_prompt_cases()
166
-
167
-
168
  st.markdown("---")
169
- st.title("👀 RAG (Modelo Técnico)")
170
  ss.setdefault("use_rag", True)
171
- ss.setdefault("rag_k", 3)
172
- ss.use_rag = st.checkbox("Usar RAG (técnico)", value=ss.use_rag,
173
- help="Recupera evidencias de ./Vec_DataBase/mori.* y las cita en el prompt.")
174
- ss.rag_k = st.slider("k evidencias", 1, 5, int(ss.rag_k),
175
- help="https://huggingface.co/docs/transformers/en/model_doc/rag")
176
-
177
-
178
  st.markdown("---")
179
  st.title("🧾 Vista previa del Prompt")
180
-
181
- if "last_prompt" in st.session_state and st.session_state["last_prompt"]:
182
  with st.expander("Mostrar prompt generado"):
183
  st.text_area(
184
  "Prompt actual:",
185
- st.session_state["last_prompt"],
186
  height=200,
187
  disabled=True
188
  )
189
  else:
190
- st.caption("👉 Aún no se ha generado ningún prompt.")
191
 
192
  # ----------------------------
193
  # Construir diccionario de parámetros
@@ -200,55 +101,14 @@ def sidebar_params():
200
  "no_repeat_ngram_size": int(ss.no_repeat),
201
  "repetition_penalty": float(ss.repetition_penalty),
202
  }
203
- if ss.mode == "beam":
204
- params.update({
205
- "num_beams": int(ss.num_beams),
206
- "length_penalty": float(ss.length_penalty),
207
- })
208
- else:
209
- params.update({
210
- "temperature": float(ss.temperature),
211
- "top_p": float(ss.top_p),
212
- })
213
 
214
  return params
215
 
216
-
217
-
218
-
219
  #***************************************************************************
220
  # Functions
221
  #***************************************************************************
222
 
223
-
224
- def truncate_sentences(text: str, max_sentences: int = 4) -> str:
225
- _SENT_SPLIT = re.compile(r'(?<=[\.\!\?…])\s+')
226
- s = text.strip()
227
- if not s: return s
228
- parts = _SENT_SPLIT.split(s)
229
- cut = " ".join(parts[:max_sentences]).strip()
230
- if cut and cut[-1] not in ".!?…": cut += "."
231
- return cut
232
-
233
-
234
- def _load_json_safe(path: Path, fallback: dict) -> dict:
235
- try:
236
- with open(path, "r", encoding="utf-8") as f:
237
- return json.load(f)
238
- except Exception:
239
- return fallback
240
-
241
- def load_prompt_cases():
242
- base = Path("Prompts")
243
- tech = _load_json_safe(base / "prompts_technical.json", {"modes": {}})
244
- social = _load_json_safe(base / "prompts_social.json", {"modes": {}})
245
- return {"technical": tech, "social": social}
246
-
247
-
248
-
249
-
250
-
251
- # Function to clean the question field
252
  def limpiar_input():
253
  st.session_state["entrada"] = ""
254
 
@@ -257,14 +117,10 @@ def get_model_path(folder_name):
257
  return Path("Models") / folder_name
258
 
259
  # Function to save user interaction
260
- def saving_interaction(question, response, context, user_id):
261
- '''
262
- inputs:
263
- question --> User input question
264
- response --> Mori response to the user question
265
- context --> Context related to the user input, found by the trained classifier
266
- user_id --> ID for the current user (Unique ID per session)
267
- '''
268
  timestamp = dt.datetime.now().isoformat()
269
  stats_dir = Path("Statistics")
270
  stats_dir.mkdir(parents=True, exist_ok=True)
@@ -272,765 +128,52 @@ def saving_interaction(question, response, context, user_id):
272
  archivo_csv = stats_dir / "conversaciones_log.csv"
273
  existe_csv = archivo_csv.exists()
274
 
 
275
  with open(archivo_csv, mode="a", encoding="utf-8", newline="") as f_csv:
276
  writer = csv.writer(f_csv)
277
  if not existe_csv:
278
- writer.writerow(["timestamp", "user_id", "contexto", "pregunta", "respuesta"])
279
- writer.writerow([timestamp, user_id, context, question, response])
280
 
 
281
  archivo_jsonl = stats_dir / "conversaciones_log.jsonl"
282
  with open(archivo_jsonl, mode="a", encoding="utf-8") as f_jsonl:
283
  registro = {
284
  "timestamp": timestamp,
285
  "user_id": user_id,
286
- "context": context,
287
  "pregunta": question,
288
- "respuesta": response}
 
 
 
289
  f_jsonl.write(json.dumps(registro, ensure_ascii=False) + "\n")
290
 
291
  # Function to load models within the huggingface repositories space
292
  @st.cache_resource
293
- def load_model(path_str):
294
- path = Path(path_str).resolve()
295
- tokenizer = AutoTokenizer.from_pretrained(path, local_files_only=True)
296
- model = AutoModelForSeq2SeqLM.from_pretrained(path, local_files_only=True)
297
  return model, tokenizer
298
-
299
  #-------------------------------------------------------------------------
300
- # Function to correct Spanish sentences' punctuation and missing characters
301
- #-------------------------------------------------------------------------
302
-
303
- def polish_spanish(s: str) -> str:
304
- s = unicodedata.normalize("NFC", s).strip()
305
- s = re.sub(r'\s*[\[\(]\s*Mori\s+(?:Social|T[eé]nico|T[eé]cnico)\s*[\]\)]\s*', '', s, flags=re.I)
306
- fixes = [
307
- (r'(?i)(^|\W)T\s+puedes(?P<p>[^\w]|$)', r'\1Tú puedes\g<p>'),
308
- (r'(?i)(^|\W)T\s+(ya|eres|estas|estás|tienes|puedes)\b', r'\1Tú \2'),
309
- (r'(?i)\bclaro que s(?:i|í)?\b(?P<p>[,.\!?…])?', r'Claro que sí\g<p>'),
310
- (r'(?i)(^|\s)si,', r'\1Sí,'),
311
- (r'(?i)(\beso\s+)s(\s+est[áa]\b)', r'\1sí\2'),
312
- (r'(?i)(^|[\s,;:])s(\s+es\b)', r'\1sí\2'),
313
- (r'(?i)\btiles\b', 'útiles'),
314
- (r'(?i)\butiles\b', 'útiles'),
315
- (r'(?i)\butil\b', 'útil'),
316
- (r'(?i)\baqui\b', 'aquí'),
317
- (r'(?i)\baqu\b(?=\s+estoy\b)', 'aquí'),
318
- (r'(?i)\balgn\b', 'algún'),
319
- (r'(?i)\balgun\b', 'algún'),
320
- (r'(?i)\bAnimo\b', 'Ánimo'),
321
- (r'(?i)\bcario\b', 'cariño'),
322
- (r'(?i)\baprendisaje\b', 'aprendizaje'),
323
- (r'(?i)\bmanana\b', 'mañana'),
324
- (r'(?i)\bmaana\b', 'mañana'),
325
- (r'(?i)\benergia\b', 'energía'),
326
- (r'(?i)\benerga\b', 'energía'),
327
- (r'(?i)\bextrano\b', 'extraño'),
328
- (r'(?i)\bextrana\b', 'extraña'),
329
- (r'(?i)\bextranar\b', 'extrañar'),
330
- (r'(?i)\bextranarte\b', 'extrañarte'),
331
- (r'(?i)\bextranas\b', 'extrañas'),
332
- (r'(?i)\bextranos\b', 'extraños'),
333
- (r'(?i)\baqu\b', 'aquí'),
334
- (r'(?i)\baqui\b', 'aquí'),
335
- (r'(?i)\bestare\b', 'estaré'),
336
- (r'(?i)\bclarn\b', 'clarín'),
337
- (r'(?i)\bclarin\b', 'clarín'),
338
- (r'(?i)\bclar[íi]n\s+cornetas\b', 'clarín cornetas'),
339
- (r'(?i)(^|\s)s([,.;:!?])', r'\1Sí\2'),
340
- (r'(?i)\bfutbol\b', 'fútbol'),
341
- (r'(?i)(^|\s)as(\s+se\b)', r'\1Así\2'),
342
- (r'(?i)(^|\s)s(\s+orientarte\b)', r'\1sí\2'),
343
- (r'(?i)\bbuen dia\b', 'buen día'),
344
- (r'(?i)\bgran dia\b', 'gran día'),
345
- (r'(?i)\bdias\b', 'días'),
346
- (r'(?i)\bdia\b', 'día'),
347
- (r'(?i)\bgran da\b', 'gran día'),
348
- (r'(?i)\bacompa?a(r|rte|do|da|dos|das)?\b', r'acompaña\1'),
349
- (r'(?i)(^|\s)as([,.;:!?]|\s|$)', r'\1así\2'),
350
- (r'(?i)(^|\s)S lo se\b', r'\1Sí lo sé'),
351
- (r'(?i)(^|\s)S lo sé\b', r'\1Sí lo sé'),
352
- (r'(?i)\bcudese\b', 'cuídese'),
353
- (r'(?i)\bpequeo\b', 'pequeño'),
354
- (r'(?i)\bpequea\b', 'pequeña'),
355
- (r'(?i)\bpequeos\b', 'pequeños'),
356
- (r'(?i)\bpequeas\b', 'pequeñas'),
357
- (r'(?i)\bunico\b', 'único'),
358
- (r'(?i)\bunica\b', 'única'),
359
- (r'(?i)\bunicos\b', 'únicos'),
360
- (r'(?i)\bunicas\b', 'únicas'),
361
- (r'(?i)\bnico\b', 'único'),
362
- (r'(?i)\bnica\b', 'única'),
363
- (r'(?i)\bnicos\b', 'únicos'),
364
- (r'(?i)\bnicas\b', 'únicas'),
365
- (r'(?i)\bestadstico\b', 'estadístico'),
366
- (r'(?i)\bestadstica\b', 'estadística'),
367
- (r'(?i)\bestadsticos\b', 'estadísticos'),
368
- (r'(?i)\bestadsticas\b', 'estadísticas'),
369
- (r'(?i)\bcudate\b', 'cuídate'),
370
- (r'(?i)\bcuidate\b', 'cuídate'),
371
- (r'(?i)\bcuidese\b', 'cuídese'),
372
- (r'(?i)\bcudese\b', 'cuídese'),
373
- (r'(?i)\bcuidense\b', 'cuídense'),
374
- (r'(?i)\bcudense\b', 'cuídense'),
375
- (r'(?i)\bgracias por confiar en m\b', 'gracias por confiar en mí'),
376
- (r'(?i)\bcada dia\b', 'cada día'),
377
- (r'(?i)\bcada da\b', 'cada día'),
378
- (r'(?i)\bsegun\b', 'según'),
379
- (r'(?i)\bcaracteristica(s)?\b', r'característica\1'),
380
- (r'(?i)\bcaracterstica(s)?\b', r'característica\1'),
381
- (r'(?i)\b([a-záéíóúñ]+)cion\b', r'\1ción'),
382
- (r'(?i)\bdeterminacio\b', 'determinación'),
383
- ]
384
- for pat, rep in fixes:
385
- s = re.sub(pat, rep, s)
386
-
387
- s = re.sub(r'(?i)^eso es todo!(?P<r>(\s|$).*)', r'¡Eso es todo!\g<r>', s)
388
-
389
- def add_opening_q(m):
390
- cuerpo = m.group('qbody')
391
- if '¿' in cuerpo:
392
- return m.group(0)
393
- return f"{m.group('pre')}¿{cuerpo}"
394
- s = re.sub(r'(?P<pre>(^|[\.!\…]\s+))(?P<qbody>[^?]*\?)', add_opening_q, s)
395
-
396
- def _open_exclam(m):
397
- palabra = m.group('w')
398
- resto = m.group('r') or ''
399
- return f'¡{palabra}!{resto}'
400
- s = re.sub(r'(?i)^(?P<w>(hola|gracias|genial|perfecto|claro|por supuesto|con gusto|listo|vaya|wow|tu puedes|tú puedes|clarín|clarin|clarín cornetas))!(?P<r>(\s|$).*)',_open_exclam, s)
401
-
402
- s = re.sub(r'\s+', ' ', s).strip()
403
- if s and s[-1] not in ".!?…":
404
- s += "."
405
- return s
406
-
407
- #-------------------------------------------------------------------------
408
- # Function to remove repeated input in the Model answer
409
- #-------------------------------------------------------------------------
410
-
411
- def anti_echo(response: str, user_text: str) -> str:
412
- rn = normalize_for_route(response)
413
- un = normalize_for_route(user_text)
414
- def _clean_leading(s: str) -> str:
415
- s = re.sub(r'^\s*[,;:\-–—]\s*', '', s)
416
- s = re.sub(r'^\s+', '', s)
417
- return s
418
- if len(un) >= 4 and rn.startswith(un):
419
- cut = re.sub(r'^\s*[^,;:\.\!\?]{0,120}[,;:\-]\s*', '', response).lstrip()
420
- if cut and cut != response:
421
- return _clean_leading(cut)
422
- return _clean_leading(response[len(user_text):])
423
- return response
424
-
425
- #-------------------------------------------------------------------------
426
- # Normalization helpers
427
- #-------------------------------------------------------------------------
428
-
429
- def normalize_for_route(s: str) -> str:
430
- s = unicodedata.normalize("NFKD", s)
431
- s = "".join(ch for ch in s if not unicodedata.combining(ch))
432
- s = re.sub(r"[^\w\s-]", " ", s, flags=re.UNICODE)
433
- s = re.sub(r"\s+", " ", s).strip().lower()
434
- return s
435
-
436
- _Q_STARTERS = {
437
- "como","que","quien","quienes","cuando","donde","por que","para que",
438
- "cual","cuales","cuanto","cuantos","cuanta","cuantas"
439
- }
440
- _EXC_TRIGGERS = {"motiva","motivame","animate","animame","animo","ayudame","ayudame porfa", "clarin", "clarín", "clarinete", "clarin cornetas"}
441
- SPECIAL_NOPUNCT = {"kiubo", "quiubo", "que chido", "qué chido", "que buena onda"}
442
- _Q_VERB_STARTERS = {"eres","estas","estás","puedes","sabes","tienes","quieres","conoces",
443
- "crees","piensas","dirias","dirías","podrias","podrías","podras","podrás"}
444
-
445
- #-------------------------------------------------------------------------
446
- # Punctuation helpers
447
- #-------------------------------------------------------------------------
448
-
449
- def needs_question_marks(norm: str) -> bool:
450
- if "?" in norm: return False
451
- for w in _Q_STARTERS:
452
- if norm.startswith(w + " ") or norm == w:
453
- return True
454
- return False
455
-
456
- def needs_exclam(norm: str) -> bool:
457
- if "!" in norm: return False
458
- return any(t in norm for t in _EXC_TRIGGERS)
459
-
460
- #-------------------------------------------------------------------------
461
- # Greetings detection
462
- #-------------------------------------------------------------------------
463
-
464
- def is_slang_greeting(norm: str) -> bool:
465
- SHORT = {
466
- "que pex", "que onda", "ke pex", "k pex", "q onda",
467
- "kiubo", "quiubo", "quiubole", "quiúbole", "kionda", "q onda", "k onda",
468
- "que rollo", "ke onda", "que show", "que tranza"
469
- }
470
- if norm in SHORT: return True
471
- if re.match(r"^(q|k|ke|que)\s+(pex|onda|rollo|show|tranza)\b", norm): return True
472
- if re.match(r"^(kiubo|quiubo|quiubole|quiúbole|quiubol[e]?)\b", norm): return True
473
- return False
474
-
475
- #-------------------------------------------------------------------------
476
- # Capitalization & autopunct
477
- #-------------------------------------------------------------------------
478
-
479
- def capitalize_spanish(s: str) -> str:
480
- s = s.strip()
481
- i = 0
482
- while i < len(s) and not s[i].isalpha():
483
- i += 1
484
- if i < len(s):
485
- s = s[:i] + s[i].upper() + s[i+1:]
486
- return s
487
-
488
- def smart_autopunct(user_text: str) -> str:
489
- s = user_text.strip()
490
- if len(s) > 20:
491
- return capitalize_spanish(s)
492
- norm = normalize_for_route(s)
493
- if norm in SPECIAL_NOPUNCT:
494
- s = re.sub(r'[¿?!¡]+', '', s).strip()
495
- return capitalize_spanish(s)
496
- if norm.startswith("y si "):
497
- s = f"¿{s}?"
498
- return capitalize_spanish(s)
499
- if "?" in s and "¿" not in s:
500
- s = "¿" + s
501
- return capitalize_spanish(s)
502
- if "!" in s and "¡" not in s:
503
- s = "¡" + s
504
- return capitalize_spanish(s)
505
- if is_slang_greeting(norm):
506
- s = f"¡{s}!"
507
- return capitalize_spanish(s)
508
- if needs_question_marks(norm):
509
- s = f"¿{s}?"
510
- return capitalize_spanish(s)
511
- toks = norm.split()
512
- if toks and toks[0] in _Q_VERB_STARTERS:
513
- s = f"¿{s}?"
514
- return capitalize_spanish(s)
515
- if re.match(r"^(me\s+ayudas?|me\s+puedes|podrias?|podras?)\b", norm):
516
- s = f"¿{s}?"
517
- return capitalize_spanish(s)
518
- if needs_exclam(norm):
519
- s = f"¡{s}!"
520
- return capitalize_spanish(s)
521
- return capitalize_spanish(s)
522
-
523
- #-------------------------------------------------------------------------
524
- # Prompt builders
525
- #-------------------------------------------------------------------------
526
-
527
- def build_prompt_from_cases(domain: str, # "technical" | "social"
528
- prompt_type: str, # "Zero-shot" | "One-shot" | "Few-shot (3)"
529
- persona: str, # "Mori Normal" | "Mori Entusiasta"
530
- question: str,
531
- context: str | None = None) -> str:
532
- # Map UI → keys de JSON
533
- key_map = {
534
- "Zero-shot": "zero_shot",
535
- "One-shot": "one_shot",
536
- "Few-shot (3)": "few_shot_3"
537
- }
538
- mode_key = key_map.get(prompt_type, "zero_shot")
539
-
540
- data = st.session_state.PROMPT_CASES.get(domain, {}).get("modes", {}).get(mode_key)
541
-
542
- # Ajuste de tono por personalidad (opcional, simple)
543
- tone = data.get("tone", "")
544
-
545
- out_fmt = data.get("output_format", "")
546
- rules = "\n- ".join(data.get("rules", []))
547
- ctx_line = f"\n- Contexto: {context}" if (context and domain == "technical") else "social"
548
-
549
- # Ensamblar ejemplos si aplica
550
- examples = data.get("examples", [])
551
- ex_str = ""
552
- if examples:
553
- parts = []
554
- for i, ex in enumerate(examples, 1):
555
- parts.append(f"Ejemplo {i} →\nPregunta: {ex.get('input','')}\nRespuesta: {ex.get('output','')}")
556
- ex_str = "\n\n" + "\n\n".join(parts) + "\n\nAhora responde:"
557
-
558
- if 'social' in ctx_line:
559
- question = smart_autopunct(question)
560
- question = f"pregunta social:{question}"
561
- else:
562
- question = f"pregunta={question}"
563
-
564
- # Prompt final
565
- prompt = (
566
- f"Tarea: {data.get('instruction','')}\n"
567
- f"Reglas:\n- {rules}{ctx_line}\n"
568
- f"Estilo: {tone}\n"
569
- f"Formato de salida: {out_fmt}\n"
570
- f"{ex_str}\n"
571
- f"{question}\n"
572
- )
573
-
574
- return prompt.strip()
575
-
576
-
577
- #-------------------------------------------------------------------------
578
- # Seeds & helpers
579
  #-------------------------------------------------------------------------
580
 
581
  def set_seeds(seed: int = 42):
582
- random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
583
- if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)
 
 
 
584
  torch.backends.cudnn.deterministic = True
585
  torch.backends.cudnn.benchmark = False
586
 
587
- # --- Personalidades (solo estilo en prompt; parámetros ya vienen del sidebar) ---
588
-
589
- def persona_style_prompt(persona: str, domain: str) -> str:
590
- """Instrucción breve de estilo según personalidad y dominio (technical/social)."""
591
- if persona == "Mori Entusiasta":
592
- return (
593
- "Responde de forma natural y amistosa, con un toque reflexivo; agrega ejemplos sencillos o analogías cortas. "
594
- "Evita sonar formal y permite algo de color humano."
595
- )
596
- if persona == "Mori Objetivo": # ya no se usa, pero por compatibilidad
597
- return "Responde en pocas frases, directo y técnico, sin adornos; solo lo esencial."
598
- return "" # Mori Normal
599
-
600
- #-------------------------------------------------------------------------
601
- # Classifier
602
- #-------------------------------------------------------------------------
603
-
604
- def classify_context(question, label_classes, model, tokenizer, device):
605
- model = model.to(device)
606
- inputs = tokenizer(question, return_tensors="pt", padding=True, truncation=True, max_length=128)
607
- inputs = {k: v.to(device) for k, v in inputs.items()}
608
- with torch.no_grad():
609
- outputs = model(**inputs)
610
- logits = outputs.logits
611
- pred_intent = torch.argmax(logits, dim=1).item()
612
- predicted_label = label_classes[pred_intent]
613
- return predicted_label
614
-
615
- #-------------------------------------------------------------------------
616
- # Chatbot response for technical contexts using a Hugging Face model
617
- #-------------------------------------------------------------------------
618
-
619
- def technical_asnwer(question, context, model, tokenizer, device, gen_params=None):
620
- model = model.to(device).eval()
621
- #persona_name = (gen_params or {}).get("persona", st.session_state.get("persona", "Mori Normal"))
622
- #style = persona_style_prompt(persona_name, "technical")
623
- #style_tag = f" Estilo:{style}" if style else ""
624
- #input_text = f"definir: responde con la definición canónica exacta. Contexto={context} ; Pregunta={question}. {style_tag}"
625
-
626
-
627
- #persona_name = GEN_PARAMS.get("persona", st.session_state.persona)
628
- #prompt_type = st.session_state.get("prompt_type", "Zero-shot")
629
-
630
-
631
- persona_name = (gen_params or {}).get("persona", st.session_state.get("persona", "Mori Normal"))
632
- prompt_type = st.session_state.get("prompt_type", "Zero-shot")
633
-
634
-
635
- input_text = build_prompt_from_cases(
636
- domain="technical",
637
- prompt_type=prompt_type,
638
- persona=persona_name,
639
- question=question,
640
- context=context # el contexto detectado que ya pasas
641
- )
642
-
643
- st.session_state["last_prompt"] = input_text # o prompt
644
- st.session_state["just_generated"] = True
645
- #st.rerun()
646
- enc = tokenizer(input_text, return_tensors="pt", padding=True, truncation=True, max_length=256).to(device)
647
-
648
- bad_words = ["["]
649
- bad_ids = [tokenizer(bw, add_special_tokens=False).input_ids for bw in bad_words]
650
-
651
- # --- construir kwargs de generación, SIN tocar nada por personalidad ---
652
- max_new = int((gen_params).get("max_new_tokens"))
653
- min_new = int((gen_params).get("min_tokens")) # <- ahora SIEMPRE min_new_tokens
654
- no_repeat = int((gen_params).get("no_repeat_ngram_size"))
655
- rep_pen = float((gen_params).get("repetition_penalty"))
656
- mode = (gen_params or {}).get("mode", "beam")
657
-
658
- if mode == "sampling":
659
- temperature = float((gen_params or {}).get("temperature", 0.7))
660
- top_p = float((gen_params or {}).get("top_p", 0.9))
661
- kwargs = dict(
662
- do_sample=True,
663
- num_beams=1,
664
- temperature=max(0.1, temperature),
665
- top_p=min(1.0, max(0.5, top_p)),
666
- max_new_tokens=max_new,
667
- min_new_tokens=max(0, min_new), # 👈 consistente
668
- no_repeat_ngram_size=no_repeat,
669
- repetition_penalty=max(1.0, rep_pen),
670
- bad_words_ids=bad_ids,
671
- eos_token_id=tokenizer.eos_token_id,
672
- pad_token_id=tokenizer.pad_token_id,
673
- )
674
- else:
675
- num_beams = max(2, int((gen_params or {}).get("num_beams", 4)))
676
- length_penalty = float((gen_params or {}).get("length_penalty", 1.0))
677
- kwargs = dict(
678
- do_sample=False,
679
- num_beams=num_beams,
680
- length_penalty=length_penalty,
681
- max_new_tokens=max_new,
682
- min_new_tokens=max(0, min_new), # 👈 también aquí (no min_length)
683
- no_repeat_ngram_size=no_repeat,
684
- repetition_penalty=max(1.0, rep_pen),
685
- bad_words_ids=bad_ids,
686
- eos_token_id=tokenizer.eos_token_id,
687
- pad_token_id=tokenizer.pad_token_id,
688
- )
689
-
690
- out_ids = model.generate(
691
- input_ids=enc["input_ids"], attention_mask=enc["attention_mask"], **kwargs
692
- )
693
- text = tokenizer.decode(out_ids[0], skip_special_tokens=True)
694
-
695
- if persona_name == "Mori Normal":
696
- text = truncate_sentences(text, max_sentences=1)
697
-
698
- st.session_state["last_response"] = text
699
- #st.rerun()
700
-
701
-
702
- return polish_spanish(text)
703
-
704
- #-------------------------------------------------------------------------
705
- # Chatbot response for social contexts using a Hugging Face model
706
- #-------------------------------------------------------------------------
707
-
708
- def social_asnwer(question, model, tokenizer, device, gen_params=None, block_web=True):
709
-
710
- model = model.to(device).eval()
711
- persona_name = (gen_params or {}).get("persona", st.session_state.get("persona", "Mori Normal"))
712
- prompt_type = st.session_state.get("prompt_type", "Zero-shot")
713
- prompt = build_prompt_from_cases(
714
- domain="social",
715
- prompt_type=prompt_type,
716
- persona=persona_name,
717
- question=question)
718
-
719
- st.session_state["last_prompt"] = prompt # o prompt
720
- st.session_state["just_generated"] = True
721
-
722
-
723
- enc = tokenizer(prompt, return_tensors="pt", padding=True, truncation=True, max_length=192).to(device)
724
-
725
- bad_words = ["[", "Thanks", "thank you"]
726
- if block_web:
727
- bad_words += ["website", "http", "www", ".com"]
728
- bad_ids = [tokenizer(bw, add_special_tokens=False).input_ids for bw in bad_words]
729
-
730
-
731
- max_new = int((gen_params).get("max_new_tokens"))
732
- min_tokens = int((gen_params).get("min_tokens"))
733
- min_length = int(enc["input_ids"].shape[1]) + max(0, min_tokens)
734
- no_repeat = int((gen_params).get("no_repeat_ngram_size"))
735
- rep_pen = float((gen_params).get("repetition_penalty"))
736
- mode = (gen_params or {}).get("mode", "beam")
737
-
738
- if mode == "sampling":
739
- temperature = float((gen_params or {}).get("temperature", 0.7))
740
- top_p = float((gen_params or {}).get("top_p", 0.9))
741
- kwargs = dict(
742
- do_sample=True, num_beams=1,
743
- temperature=max(0.1, temperature),
744
- top_p=min(1.0, max(0.5, top_p)),
745
- max_new_tokens=max_new,
746
- #min_length=min_length,
747
- min_new_tokens=max(0, min_tokens),
748
- no_repeat_ngram_size=no_repeat,
749
- repetition_penalty=max(1.0, rep_pen),
750
- bad_words_ids=bad_ids,
751
- eos_token_id=tokenizer.eos_token_id,
752
- pad_token_id=tokenizer.pad_token_id,
753
- )
754
- else:
755
- num_beams = max(2, int((gen_params or {}).get("num_beams", 4)))
756
- length_penalty = float((gen_params or {}).get("length_penalty", 1.0))
757
- kwargs = dict(
758
- do_sample=False, num_beams=num_beams, length_penalty=length_penalty,
759
- max_new_tokens=max_new,
760
- #min_length=min_length,
761
- min_new_tokens=max(0, min_tokens), # <- usar min_new_tokens
762
- no_repeat_ngram_size=no_repeat,
763
- repetition_penalty=max(1.0, rep_pen),
764
- bad_words_ids=bad_ids,
765
- eos_token_id=tokenizer.eos_token_id,
766
- pad_token_id=tokenizer.pad_token_id,
767
-
768
- )
769
-
770
- out_ids = model.generate(
771
- input_ids=enc["input_ids"], attention_mask=enc["attention_mask"], **kwargs
772
- )
773
- text = tokenizer.decode(out_ids[0], skip_special_tokens=True)
774
- if persona_name == "Mori Normal":
775
- text = truncate_sentences(text, max_sentences=2)
776
- #text = anti_echo(text, question)
777
- text = polish_spanish(text)
778
- text = capitalize_spanish(text)
779
-
780
- st.session_state["last_response"] = text
781
- #st.rerun()
782
-
783
-
784
- return text
785
-
786
- #-------------------------------------------------------------------------
787
- # Rule overrides
788
- #-------------------------------------------------------------------------
789
-
790
- def rule_intent_override(user_text: str, predicted_label: str) -> str:
791
- n = normalize_for_route(user_text)
792
- if re.fullmatch(r"(motivame|motiva|animame|animo|ayudame|que tranza|qué tranza|que tranza mori|qué tranza mori)", n):
793
- return "social"
794
- return predicted_label
795
-
796
- #-------------------------------------------------------------------------
797
- # Router
798
- #-------------------------------------------------------------------------
799
-
800
- def contextual_asnwer(question, label_classes, context_model, cont_tok,
801
- tec_model, tec_tok, soc_model, soc_tok, device, gen_params=None, block_web=True):
802
- context = classify_context(question, label_classes, context_model, cont_tok, device)
803
- context = rule_intent_override(question, context)
804
-
805
- context_icons = {
806
- "social": "💬", "modelos": "🔧", "evaluación": "📏", "optimización": "⚙️",
807
- "visualización": "📈", "aprendizaje": "🧠", "vida digital": "🧑‍💻",
808
- "estadística": "📊", "infraestructura": "🖥", "datos": "📂", "transformación digital": "🌀"}
809
- icon = context_icons.get(context, "🧠")
810
-
811
- if gen_params and "seed" in gen_params:
812
- set_seeds(gen_params["seed"])
813
-
814
- if context == "social":
815
- # Nota: por resultados del análisis, RAG social no aporta (dataset muy redundante).
816
- # Puedes activarlo en el futuro si amplías la diversidad.
817
- return social_asnwer(question, soc_model, soc_tok, device, gen_params=gen_params, block_web=block_web), context
818
- else:
819
- # Técnico: si el usuario activó RAG, lo usamos
820
- use_rag = st.session_state.get("use_rag", False)
821
- if use_rag:
822
- # Carga única de E5+FAISS (cache_resource)
823
- dev_str = "cuda" if torch.cuda.is_available() else "cpu"
824
- e5, index, metas = load_rag_assets(dev_str)
825
- if e5 is None:
826
- # Fallback si no se encuentra la base RAG
827
- return technical_asnwer(question, context, tec_model, tec_tok, device, gen_params=gen_params), context
828
- resp = technical_answer_rag(
829
- question, tec_model, tec_tok, device, gen_params,
830
- e5=e5, index=index, metas=metas,
831
- k=st.session_state.get("rag_k", 5), sim_threshold=0.40
832
- )
833
- return resp, context
834
- else:
835
- return technical_asnwer(question, context, tec_model, tec_tok, device, gen_params=gen_params), context
836
-
837
-
838
-
839
- # ============================
840
- # RAG assets (carga única)
841
- # ============================
842
- @st.cache_resource
843
- def load_rag_assets(device_str: str = "cpu"):
844
- """
845
- Carga todo el stack RAG: modelo E5 + FAISS + metadatos desde Hugging Face.
846
- Se ejecuta una sola vez gracias a st.cache_resource.
847
- """
848
- REPO_ID = "tecuhtli/Mori_FAISS_Full" # Ajusta si cambia el repo
849
- TOKEN = os.getenv("HF_TOKEN") # Guardado en Settings → Repository secrets
850
- if not TOKEN:
851
- st.error("❌ No se encontró HF_TOKEN en los Secrets del Space.")
852
- st.stop()
853
-
854
- st.info("📥 Cargando base RAG desde Hugging Face...")
855
- try:
856
- # Descarga los archivos privados del dataset
857
- faiss_path = hf_hub_download(repo_id=REPO_ID, filename="mori.faiss", repo_type="dataset", token=TOKEN)
858
- ids_path = hf_hub_download(repo_id=REPO_ID, filename="mori_ids.npy", repo_type="dataset", token=TOKEN)
859
- meta_path = hf_hub_download(repo_id=REPO_ID, filename="mori_metas.json", repo_type="dataset", token=TOKEN)
860
-
861
- # Carga los datos locales (ya descargados)
862
- index = faiss.read_index(faiss_path)
863
- ids = np.load(ids_path, allow_pickle=True)
864
- with open(meta_path, "r", encoding="utf-8") as f:
865
- metas = json.load(f)
866
-
867
- # Carga el modelo de embeddings
868
- e5 = SentenceTransformer("intfloat/multilingual-e5-base", device=device_str)
869
-
870
- st.success(f"✅ RAG cargado: {index.ntotal} vectores listos.")
871
- return e5, index, metas
872
-
873
- except Exception as e:
874
- st.error(f"⚠️ Error al cargar el índice RAG: {e}")
875
- return None, None, None
876
-
877
-
878
- def rag_retrieve(e5, index, metas, user_text: str, k: int = 5):
879
- """Top-k por similitud coseno (IP + embeddings normalizados)."""
880
- if e5 is None or index is None or metas is None or index.ntotal == 0:
881
- return []
882
- qv = e5.encode([f"query: {user_text}"], normalize_embeddings=True,
883
- convert_to_numpy=True).astype("float32")
884
- k = max(1, min(int(k), index.ntotal))
885
- scores, idxs = index.search(qv, k)
886
- out = []
887
- for rank, (s, i) in enumerate(zip(scores[0], idxs[0]), 1):
888
- if i == -1:
889
- continue
890
- m = metas[i]
891
- out.append({
892
- "rank": rank, "score": float(s),
893
- "id": m.get("id",""),
894
- "canonical_term": m.get("canonical_term",""),
895
- "context": m.get("context",""),
896
- "input": m.get("input",""),
897
- "output": m.get("output",""),
898
- })
899
- return out
900
-
901
-
902
- def _format_evidence(passages):
903
- lines = []
904
- for p in passages:
905
- lines.append(
906
- f"[{p['rank']}] term='{p['canonical_term']}' ctx='{p['context']}'\n"
907
- f" Q: {p['input']}\n"
908
- f" A: {p['output']}"
909
- )
910
- return "\n".join(lines)
911
-
912
-
913
- def build_rag_prompt_technical(base_prompt: str, user_text: str, passages):
914
- ev_lines = []
915
- for p in passages:
916
- ev_lines.append(
917
- f"[{p['rank']}] term='{p.get('canonical_term','')}' ctx='{p.get('context','')}'\n"
918
- f"input: {p.get('input','')}\n"
919
- f"output: {p.get('output','')}"
920
- )
921
-
922
- ev_block = "\n".join(ev_lines)
923
- rag_rules = (
924
- "\n\n[ Modo RAG ]\n"
925
- "- Usa EXCLUSIVAMENTE la información relevante de las evidencias.\n"
926
- "- Si algo no aparece en las evidencias, dilo explícitamente.\n"
927
- "- Cita las evidencias con [n] (ej. [1], [3]).\n"
928
- )
929
- return f"{base_prompt.strip()}\n{rag_rules}\nEVIDENCIAS:\n{ev_block}\n"
930
-
931
-
932
- def get_bad_words_ids(tok):
933
- bad = []
934
- for sym in ["[", "]"]:
935
- ids = tok.encode(sym, add_special_tokens=False) # p.ej. [784]
936
- if ids and all(isinstance(t, int) and t >= 0 for t in ids):
937
- bad.append(ids) # [[784]]
938
- return bad
939
-
940
-
941
- # --- FUNCIÓN ACTUALIZADA: Prompt Engineering + RAG en capas separadas ---
942
- def technical_answer_rag(
943
- question, tec_model, tec_tok, device, gen_params,
944
- e5, index, metas, k=5, sim_threshold=0.40
945
- ):
946
- """Orquesta retrieval + (base_prompt de Prompt Engineering) + inyección RAG + generación."""
947
- passages = rag_retrieve(e5, index, metas, question, k=k)
948
- if not passages:
949
- return "No encontré evidencias relevantes para responder con certeza. ¿Puedes dar más contexto?"
950
-
951
- # 1) Prompt Engineering (ESTILO/ROL/PERSONA) → base_prompt
952
- persona_name = (gen_params or {}).get("persona", st.session_state.get("persona", "Mori Normal"))
953
- prompt_type = st.session_state.get("prompt_type", "Zero-shot")
954
- base_prompt = build_prompt_from_cases( # <<-- tu función existente de Prompt Engineering
955
- domain="technical",
956
- prompt_type="Zero-shot",
957
- persona=persona_name,
958
- question=question,
959
- context="RAG" # etiqueta informativa
960
- )
961
-
962
- # 2) RAG (CONTENIDO/EVIDENCIAS) → se inyecta SOBRE el base_prompt
963
- prompt = build_rag_prompt_technical("", question, passages)
964
-
965
- # 3) UI: guardar prompt y marcar baja similitud si aplica
966
- max_sim = passages[0]["score"]
967
- if max_sim < sim_threshold:
968
- prompt = "⚠️ Baja similitud con la base; podría faltar contexto.\n\n" + prompt
969
- st.session_state["last_prompt"] = prompt
970
- st.session_state["just_generated"] = True
971
-
972
- # 4) Generación
973
- enc = tec_tok(prompt, return_tensors="pt", padding=True, truncation=True, max_length=512).to(device)
974
-
975
- bad_ids = get_bad_words_ids(tec_tok) # opcional; puedes quitarlo si quieres permitir corchetes libres
976
-
977
- max_new = int(gen_params.get("max_new_tokens"))
978
- min_new = int(gen_params.get("min_tokens"))
979
- no_repeat = int(gen_params.get("no_repeat_ngram_size"))
980
- rep_pen = float(gen_params.get("repetition_penalty"))
981
- mode = gen_params.get("mode", "beam")
982
-
983
- # IDs de control (por si el tokenizer no los trae definidos)
984
- eos_id = tec_tok.eos_token_id or tec_tok.convert_tokens_to_ids("</s>")
985
- pad_id = tec_tok.pad_token_id or eos_id
986
-
987
- if mode == "sampling":
988
- temperature = float(gen_params.get("temperature", 0.7))
989
- top_p = float(gen_params.get("top_p", 0.9))
990
- kwargs = dict(
991
- do_sample=True, num_beams=1,
992
- temperature=max(0.1, temperature),
993
- top_p=min(1.0, max(0.5, top_p)),
994
- max_new_tokens=max_new,
995
- min_new_tokens=max(0, min_new),
996
- no_repeat_ngram_size=no_repeat,
997
- repetition_penalty=max(1.0, rep_pen),
998
- eos_token_id=eos_id,
999
- pad_token_id=pad_id,
1000
- )
1001
- else:
1002
- num_beams = max(2, int(gen_params.get("num_beams", 4)))
1003
- length_penalty = float(gen_params.get("length_penalty", 1.0))
1004
- kwargs = dict(
1005
- do_sample=False, num_beams=num_beams, length_penalty=length_penalty,
1006
- max_new_tokens=max_new,
1007
- min_new_tokens=max(0, min_new),
1008
- no_repeat_ngram_size=no_repeat,
1009
- repetition_penalty=max(1.0, rep_pen),
1010
- eos_token_id=eos_id,
1011
- pad_token_id=pad_id,
1012
- )
1013
-
1014
- if bad_ids: # solo si existen; evita [[[...]]] y errores de validación
1015
- kwargs["bad_words_ids"] = bad_ids
1016
-
1017
- out_ids = tec_model.generate(**enc, **kwargs)
1018
- text = tec_tok.decode(out_ids[0], skip_special_tokens=True)
1019
-
1020
- if persona_name == "Mori Normal":
1021
- text = truncate_sentences(text, max_sentences=1)
1022
- text = polish_spanish(text)
1023
-
1024
- st.session_state["last_response"] = text
1025
- return text
1026
-
1027
-
1028
-
1029
  #***************************************************************************
1030
  # MAIN
1031
  #***************************************************************************
1032
 
1033
- if __name__ == '__main__':
 
 
1034
 
1035
  # --- Estado que debe persistir en todos los reruns ---
1036
  ss = st.session_state
@@ -1038,57 +181,48 @@ if __name__ == '__main__':
1038
  ss.setdefault("last_prompt", "")
1039
  ss.setdefault("last_response", "")
1040
  ss.setdefault("just_generated", False)
1041
-
1042
  # Sidebar (control total)
1043
  GEN_PARAMS = sidebar_params()
1044
- GEN_PARAMS["persona"] = st.session_state.persona # por si acaso
1045
-
1046
- # Setting historial for the current user
1047
- #if "historial" not in st.session_state:
1048
- # st.session_state.historial = []
1049
 
1050
  # Assigning a new ID to the current user
1051
- if "user_id" not in st.session_state:
1052
- st.session_state["user_id"] = str(uuid.uuid4())[:8]
1053
 
1054
- # Loading classifier encoder classes:
1055
- labels_path = hf_hub_download(repo_id="tecuhtli/mori-context-model", filename="context_labels.pkl", use_auth_token=HF_TOKEN)
1056
- label_classes = joblib.load(labels_path)
1057
 
1058
- # Loading Saved Models
1059
- # Modelo Contexto
1060
- context_model = AutoModelForSequenceClassification.from_pretrained("tecuhtli/mori-context-model", use_auth_token=HF_TOKEN)
1061
- cont_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-context-model", use_auth_token=HF_TOKEN)
1062
-
1063
  # Modelo Técnico
1064
- tec_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-tecnico-model", use_auth_token=HF_TOKEN)
1065
- tec_model = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-tecnico-model", use_auth_token=HF_TOKEN)
 
 
 
1066
 
1067
- # Modelo Social
1068
- soc_tok = AutoTokenizer.from_pretrained("tecuhtli/mori-social-model", use_auth_token=HF_TOKEN)
1069
- soc_model = AutoModelForSeq2SeqLM.from_pretrained("tecuhtli/mori-social-model", use_auth_token=HF_TOKEN)
1070
 
1071
- # Available Device
1072
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
 
 
 
 
 
1073
 
1074
- # Defining Mori's Presentation
1075
- st.title("🤖 Mori - Tu Asistente Personal 🎓")
1076
- st.caption("🙋🏽‍ Puedes preguntarme conceptos técnicos como visualización, limpieza, BI, etc.")
1077
- st.caption("🙇🏽‍ Por el momento, solo puedo contestar preguntas simples como:")
1078
- st.caption("➡️ ¿Cómo estás? ¿Qué son?, Explícame algo, Define algo, ¿Para qué sirven?")
1079
- st.caption("🦾 Me siguen mejorando, más sobre mí en: [hazutecuhtli.github.io](https://github.com/hazutecuhtli/Mori_Development)")
1080
  st.markdown("<br>", unsafe_allow_html=True)
1081
- st.caption("✏️ Escribe 'salir' para terminar.")
 
1082
 
1083
  # 🔁 Limpieza segura antes del formulario
1084
- if st.session_state.pop("_clear_entrada", False):
1085
- if "entrada" in st.session_state:
1086
- del st.session_state["entrada"]
1087
 
1088
  # 🧠 Flash de respuesta (la guardamos, pero la mostraremos después del form)
1089
- _flash = st.session_state.pop("_flash_response", None)
1090
-
1091
 
 
1092
  with st.form("formulario_mori"):
1093
  user_question = st.text_area("📝 Escribe tu pregunta aquí", key="entrada", height=100)
1094
  submitted = st.form_submit_button("Responder")
@@ -1097,27 +231,42 @@ if __name__ == '__main__':
1097
  if not user_question:
1098
  st.info("Mori: ¿Podrías repetir eso? No entendí bien 😅")
1099
  else:
1100
- response, context = contextual_asnwer(
1101
- user_question, label_classes, context_model, cont_tok,
1102
- tec_model, tec_tok, soc_model, soc_tok, device,
1103
- gen_params=GEN_PARAMS, block_web=True,
1104
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1105
 
1106
  # 🧠 Guarda historial
1107
  hora_actual = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1108
- st.session_state.historial.append(("Tú", user_question, hora_actual))
1109
 
1110
  hora_actual = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
1111
- st.session_state.historial.append(("Mori", response, hora_actual))
1112
 
1113
  # 💾 Guarda conversación
1114
- saving_interaction(user_question, response, context, st.session_state["user_id"])
1115
 
1116
  # 🟩 Guarda respuesta para mostrar después del rerun
1117
- st.session_state["_flash_response"] = response
1118
 
1119
  # 🧼 Limpieza del textarea en el próximo ciclo
1120
- st.session_state["_clear_entrada"] = True
1121
 
1122
  # ♻️ Forzar refresh (sidebar verá el nuevo prompt)
1123
  st.rerun()
@@ -1128,27 +277,19 @@ if __name__ == '__main__':
1128
  if _flash:
1129
  st.success(_flash)
1130
 
1131
- # Mostrar último mensaje (opcional, arriba de todo)
1132
- #if st.session_state.get("just_generated"):
1133
- # if st.session_state["last_response"]:
1134
- # st.success(st.session_state["last_response"])
1135
- # st.session_state["just_generated"] = False
1136
-
1137
- # ... formulario y lógica de respuesta ...
1138
-
1139
  # 🔁 Historial con estilo chat y contenedor con scroll
1140
- if st.session_state.historial:
1141
  st.markdown("---")
1142
 
1143
  # 💾 Botón de descarga arriba del historial
1144
  lineas = []
1145
- for msg in reversed(st.session_state.historial):
1146
- if len(msg) == 3:
 
 
 
1147
  autor, texto, hora = msg
1148
  lineas.append(f"[{hora}] {autor}: {texto}")
1149
- else:
1150
- autor, texto = msg
1151
- lineas.append(f"{autor}: {texto}")
1152
  texto_chat = "\n\n".join(lineas)
1153
 
1154
  st.download_button(
@@ -1175,11 +316,11 @@ if __name__ == '__main__':
1175
  unsafe_allow_html=True
1176
  )
1177
 
1178
- for msg in reversed(st.session_state.historial):
1179
- if len(msg) == 3:
1180
- autor, texto, _ = msg
1181
  else:
1182
- autor, texto = msg
1183
 
1184
  if autor == "Tú":
1185
  st.markdown(
@@ -1225,7 +366,3 @@ if __name__ == '__main__':
1225
  )
1226
 
1227
  st.markdown("</div>", unsafe_allow_html=True)
1228
-
1229
- #***************************************************************************
1230
- # FIN
1231
- #***************************************************************************
 
1
+ # -*- coding: utf-8 -*-
2
+ """Mori Inferencia Técnica (estable, UTF-8, con opción RAG ON/OFF)"""
3
+ #=====================================================================================
4
+ # Importing Libraries ===============================================================
5
+ #=====================================================================================
6
+ import os, warnings, json, random, uuid, csv
7
  import numpy as np
 
8
  import streamlit as st
9
  import datetime as dt
10
  from pathlib import Path
11
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
12
+ from Mori_TechnicalPrompts import answer_with_mori_rag, answer_with_mori_plain
13
+ import torch
14
  from huggingface_hub import hf_hub_download, login
15
  from sentence_transformers import SentenceTransformer # RAG embeddings
 
16
  #***************************************************************************
17
  #Setting up variables
18
  #***************************************************************************
 
24
  #***************************************************************************
25
  REPO_ID = "tecuhtli/Mori_FAISS_Full"
26
 
27
+
28
  #***************************************************************************
29
  # Sidebar controls for generation params
30
  #***************************************************************************
31
 
32
  def sidebar_params():
 
33
  with st.sidebar:
34
  st.title("🎮 Personalidad (FLAN-T5)")
35
 
36
  ss = st.session_state
 
 
 
 
 
 
37
 
38
+ # Estado inicial
39
+ ss.setdefault("show_llm_controls", False)
40
+ ss.setdefault("persona", "Mori Exacto")
41
  ss.setdefault("mode", "beam") # 'beam' | 'sampling'
42
  ss.setdefault("max_new", 128)
43
  ss.setdefault("min_tok", 16)
 
47
  ss.setdefault("temperature", 0.7)
48
  ss.setdefault("top_p", 0.9)
49
  ss.setdefault("repetition_penalty", 1.0)
 
50
 
51
  # ----------------------------
52
  # Personalidad (presets)
 
55
  c1, c2 = st.columns(2)
56
 
57
  with c1:
58
+ if st.button("Exacto 🧐", use_container_width=True):
59
+ ss.update({"persona": "exacto"})
 
 
 
 
 
 
 
 
 
 
 
60
  st.rerun()
61
 
62
  with c2:
63
+ if st.button("Creativo 😃", use_container_width=True):
64
+ ss.update({"persona": "creativo"})
 
 
 
 
 
 
 
 
 
65
  st.rerun()
66
 
67
  st.caption(f"Personalidad actual: **{ss.persona}**")
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  st.markdown("---")
70
+ st.title("👀 RAG")
71
  ss.setdefault("use_rag", True)
72
+ ss.setdefault("rag_k", 1)
73
+ ss.use_rag = st.checkbox(
74
+ "Usar RAG (FAISS + One-Shot)",
75
+ value=ss.use_rag,
76
+ help="Recupera evidencias de la base FAISS de Mori en Hugging Face y las usa en el prompt."
77
+ )
78
+
79
  st.markdown("---")
80
  st.title("🧾 Vista previa del Prompt")
81
+
82
+ if "last_prompt" in ss and ss["last_prompt"]:
83
  with st.expander("Mostrar prompt generado"):
84
  st.text_area(
85
  "Prompt actual:",
86
+ ss["last_prompt"],
87
  height=200,
88
  disabled=True
89
  )
90
  else:
91
+ st.caption("👉 Aún no se ha generado ningún prompt.")
92
 
93
  # ----------------------------
94
  # Construir diccionario de parámetros
 
101
  "no_repeat_ngram_size": int(ss.no_repeat),
102
  "repetition_penalty": float(ss.repetition_penalty),
103
  }
 
 
 
 
 
 
 
 
 
 
104
 
105
  return params
106
 
 
 
 
107
  #***************************************************************************
108
  # Functions
109
  #***************************************************************************
110
 
111
+ # Function to clean the question field (por si luego lo quieres usar en un botón)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  def limpiar_input():
113
  st.session_state["entrada"] = ""
114
 
 
117
  return Path("Models") / folder_name
118
 
119
  # Function to save user interaction
120
+ def saving_interaction(question, response, user_id, use_of_rag, bot_personality):
121
+ """
122
+ Guarda la interacción en CSV y JSONL para análisis posterior.
123
+ """
 
 
 
 
124
  timestamp = dt.datetime.now().isoformat()
125
  stats_dir = Path("Statistics")
126
  stats_dir.mkdir(parents=True, exist_ok=True)
 
128
  archivo_csv = stats_dir / "conversaciones_log.csv"
129
  existe_csv = archivo_csv.exists()
130
 
131
+ # CSV
132
  with open(archivo_csv, mode="a", encoding="utf-8", newline="") as f_csv:
133
  writer = csv.writer(f_csv)
134
  if not existe_csv:
135
+ writer.writerow(["timestamp", "user_id", "pregunta", "respuesta", "rag", "personality"])
136
+ writer.writerow([timestamp, user_id, question, response, use_of_rag, bot_personality])
137
 
138
+ # JSONL
139
  archivo_jsonl = stats_dir / "conversaciones_log.jsonl"
140
  with open(archivo_jsonl, mode="a", encoding="utf-8") as f_jsonl:
141
  registro = {
142
  "timestamp": timestamp,
143
  "user_id": user_id,
 
144
  "pregunta": question,
145
+ "respuesta": response,
146
+ "uso_rag": use_of_rag,
147
+ "personality": bot_personality
148
+ }
149
  f_jsonl.write(json.dumps(registro, ensure_ascii=False) + "\n")
150
 
151
  # Function to load models within the huggingface repositories space
152
  @st.cache_resource
153
+ def load_remote_model(repo_id: str, token: str = None):
154
+ tokenizer = AutoTokenizer.from_pretrained(repo_id, token=token)
155
+ model = AutoModelForSeq2SeqLM.from_pretrained(repo_id, token=token)
 
156
  return model, tokenizer
 
157
  #-------------------------------------------------------------------------
158
+ # Seeds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  #-------------------------------------------------------------------------
160
 
161
  def set_seeds(seed: int = 42):
162
+ random.seed(seed)
163
+ np.random.seed(seed)
164
+ torch.manual_seed(seed)
165
+ if torch.cuda.is_available():
166
+ torch.cuda.manual_seed_all(seed)
167
  torch.backends.cudnn.deterministic = True
168
  torch.backends.cudnn.benchmark = False
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  #***************************************************************************
171
  # MAIN
172
  #***************************************************************************
173
 
174
+ if __name__ == "__main__":
175
+ # Semilla para reproducibilidad
176
+ set_seeds(42)
177
 
178
  # --- Estado que debe persistir en todos los reruns ---
179
  ss = st.session_state
 
181
  ss.setdefault("last_prompt", "")
182
  ss.setdefault("last_response", "")
183
  ss.setdefault("just_generated", False)
184
+
185
  # Sidebar (control total)
186
  GEN_PARAMS = sidebar_params()
187
+ GEN_PARAMS["persona"] = ss.persona # por si acaso
 
 
 
 
188
 
189
  # Assigning a new ID to the current user
190
+ if "user_id" not in ss:
191
+ ss["user_id"] = str(uuid.uuid4())[:8]
192
 
 
 
 
193
 
 
 
 
 
 
194
  # Modelo Técnico
195
+ MODEL_REPO_ID = "tecuhtli/mori-tecnico-model"
196
+ model, tokenizer = load_remote_model(MODEL_REPO_ID, HF_TOKEN)
197
+
198
+ # Presentación de Mori
199
+ st.title("🤖 Mori - Tu Asistente Personal ⌨️")
200
 
201
+ st.caption("🙋🏽‍ Puedes preguntarme conceptos sobre machine learning, estadística, visualización, BI, limpieza de datos y más.")
202
+ st.caption("🙇🏽‍ Por el momento, solo puedo contestar preguntas simples como:")
 
203
 
204
+ st.caption(" 🔹 **Definiciones** — Ejemplo: *¿Qué es machine learning?*")
205
+ st.caption(" 🔹 **Procedimientos** Ejemplo: *¿Cómo limpiar datos?*")
206
+ st.caption(" 🔹 **Funcionalidad** — Ejemplo: *¿Para qué sirve un autoencoder?*")
207
+
208
+ st.markdown("<br>", unsafe_allow_html=True)
209
+
210
+ st.caption("🦾 Aún estoy aprendiendo. Puedes ver mi desarrollo aquí:")
211
+ st.caption("[hazutecuhtli.github.io](https://github.com/hazutecuhtli/Mori_Development)")
212
 
 
 
 
 
 
 
213
  st.markdown("<br>", unsafe_allow_html=True)
214
+
215
+ st.caption("✏️ Escribe tu pregunta abajo.")
216
 
217
  # 🔁 Limpieza segura antes del formulario
218
+ if ss.pop("_clear_entrada", False):
219
+ if "entrada" in ss:
220
+ del ss["entrada"]
221
 
222
  # 🧠 Flash de respuesta (la guardamos, pero la mostraremos después del form)
223
+ _flash = ss.pop("_flash_response", None)
 
224
 
225
+ # Formulario principal
226
  with st.form("formulario_mori"):
227
  user_question = st.text_area("📝 Escribe tu pregunta aquí", key="entrada", height=100)
228
  submitted = st.form_submit_button("Responder")
 
231
  if not user_question:
232
  st.info("Mori: ¿Podrías repetir eso? No entendí bien 😅")
233
  else:
234
+ use_rag = st.session_state.get("use_rag", False)
235
+
236
+ persona = GEN_PARAMS.get("persona", ss.persona)
237
+
238
+ if use_rag:
239
+ use_of_rag = 'Con RAG'
240
+ response, prompt = answer_with_mori_rag(
241
+ tokenizer, model, user_question,
242
+ modo=persona,
243
+ verbose=False
244
+ )
245
+ else:
246
+ use_of_rag = 'Sin RAG'
247
+ response, prompt = answer_with_mori_plain(
248
+ tokenizer, model, user_question,
249
+ modo=persona
250
+ )
251
+
252
+ ss["last_prompt"] = prompt
253
+ ss["just_generated"] = True
254
 
255
  # 🧠 Guarda historial
256
  hora_actual = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
257
+ ss.historial.append(("Tú", user_question, hora_actual))
258
 
259
  hora_actual = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
260
+ ss.historial.append(("Mori", response, hora_actual, use_of_rag, persona))
261
 
262
  # 💾 Guarda conversación
263
+ saving_interaction(user_question, response, ss["user_id"], use_of_rag, persona)
264
 
265
  # 🟩 Guarda respuesta para mostrar después del rerun
266
+ ss["_flash_response"] = response
267
 
268
  # 🧼 Limpieza del textarea en el próximo ciclo
269
+ ss["_clear_entrada"] = True
270
 
271
  # ♻️ Forzar refresh (sidebar verá el nuevo prompt)
272
  st.rerun()
 
277
  if _flash:
278
  st.success(_flash)
279
 
 
 
 
 
 
 
 
 
280
  # 🔁 Historial con estilo chat y contenedor con scroll
281
+ if ss.historial:
282
  st.markdown("---")
283
 
284
  # 💾 Botón de descarga arriba del historial
285
  lineas = []
286
+ for msg in reversed(ss.historial):
287
+ if len(msg) == 5:
288
+ autor, texto, hora, rag, bot_per = msg
289
+ lineas.append(f"[{hora}] {autor}: {texto} RAG:{rag} Persoality:{bot_per}")
290
+ else:
291
  autor, texto, hora = msg
292
  lineas.append(f"[{hora}] {autor}: {texto}")
 
 
 
293
  texto_chat = "\n\n".join(lineas)
294
 
295
  st.download_button(
 
316
  unsafe_allow_html=True
317
  )
318
 
319
+ for msg in reversed(ss.historial):
320
+ if len(msg) == 5:
321
+ autor, texto, hora, rag, bot_per = msg
322
  else:
323
+ autor, texto, hora = msg
324
 
325
  if autor == "Tú":
326
  st.markdown(
 
366
  )
367
 
368
  st.markdown("</div>", unsafe_allow_html=True)