Pikeras commited on
Commit
2b71e32
verified
1 Parent(s): a89184a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +143 -37
src/streamlit_app.py CHANGED
@@ -7,6 +7,8 @@ import os
7
  import re
8
  import shutil
9
  import tempfile
 
 
10
  from pathlib import Path
11
 
12
  # Evita inspecciones de modulos que generan ruido con torch.classes en Streamlit.
@@ -29,7 +31,6 @@ from web.schemas import JobRequest, ModoEvaluacion, TipoEvaluacion
29
  MODELOS_PREDEFINIDOS = [
30
  "Qwen/Qwen2.5-1.5B-Instruct",
31
  "Qwen/Qwen2.5-3B-Instruct",
32
- "Qwen/Qwen3.5-4B",
33
  "Other Model",
34
  ]
35
 
@@ -49,12 +50,38 @@ def _init_state() -> None:
49
  "modelo_eval_confirmado": "",
50
  "modelo_gen_validado": False,
51
  "modelo_gen_confirmado": "",
 
 
 
52
  }
53
  for key, value in defaults.items():
54
  if key not in st.session_state:
55
  st.session_state[key] = value
56
 
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  @st.cache_data(show_spinner=False, ttl=3600)
59
  def validar_modelo_existe(model_id: str) -> tuple[bool, str]:
60
  try:
@@ -160,7 +187,13 @@ modo = st.radio(
160
  )
161
 
162
  # Unico parametro editable en UI para ambos modos.
163
- timeout_segundos = st.slider("Timeout por llamada (segundos)", min_value=10, max_value=300, value=120)
 
 
 
 
 
 
164
 
165
  if modo == ModoEvaluacion.PERSONALIZADA.value:
166
  st.info("La evaluaci贸n personalizada se implementar谩 despu茅s. Aqu铆 solo preseleccionas modelos por ahora.")
@@ -184,11 +217,16 @@ if modo == ModoEvaluacion.PERSONALIZADA.value:
184
  "Modelo para generar prompts",
185
  MODELOS_PREDEFINIDOS,
186
  key="modelo_gen_option",
 
187
  )
188
 
189
  modelo_gen_input = ""
190
  if modelo_gen_option == "Other Model":
191
- modelo_gen_input = st.text_input("Escribe otro modelo para generaci贸n", key="modelo_gen_otro")
 
 
 
 
192
 
193
  modelo_gen_actual = modelo_gen_input.strip() if modelo_gen_option == "Other Model" else modelo_gen_option
194
  if (
@@ -198,7 +236,7 @@ if modo == ModoEvaluacion.PERSONALIZADA.value:
198
  st.session_state["modelo_gen_validado"] = False
199
  st.session_state["modelo_gen_confirmado"] = ""
200
 
201
- if st.button("Validar modelo generador", key="validar_generador"):
202
  modelo_gen = modelo_gen_input.strip() if modelo_gen_option == "Other Model" else modelo_gen_option
203
  if not modelo_gen:
204
  st.error("Debes indicar un modelo generador.")
@@ -222,11 +260,16 @@ modelo_eval_option = st.selectbox(
222
  "Modelo a evaluar",
223
  MODELOS_PREDEFINIDOS,
224
  key="modelo_eval_option",
 
225
  )
226
 
227
  modelo_eval_input = ""
228
  if modelo_eval_option == "Other Model":
229
- modelo_eval_input = st.text_input("Escribe otro modelo para evaluar", key="modelo_eval_otro")
 
 
 
 
230
 
231
  modelo_eval_actual = modelo_eval_input.strip() if modelo_eval_option == "Other Model" else modelo_eval_option
232
  if (
@@ -236,7 +279,7 @@ if (
236
  st.session_state["modelo_eval_validado"] = False
237
  st.session_state["modelo_eval_confirmado"] = ""
238
 
239
- if st.button("Validar modelo a evaluar", key="validar_modelo_eval"):
240
  modelo_eval = modelo_eval_input.strip() if modelo_eval_option == "Other Model" else modelo_eval_option
241
  if not modelo_eval:
242
  st.error("Debes indicar un modelo para evaluar.")
@@ -275,6 +318,7 @@ selected_eval_types = st.multiselect(
275
  options=TIPOS_EVALUACION_DISPONIBLES,
276
  default=["preguntas_cerradas_esperadas"],
277
  format_func=lambda x: LABELS_TIPOS_EVALUACION.get(x, x),
 
278
  )
279
 
280
  if not selected_eval_types:
@@ -294,7 +338,13 @@ if tipos_no_disponibles:
294
  + ", ".join(LABELS_TIPOS_EVALUACION.get(t, t) for t in tipos_no_disponibles)
295
  )
296
 
297
- if st.button("Comenzar evaluaci贸n", key="comenzar_eval"):
 
 
 
 
 
 
298
  request = JobRequest(
299
  modo_evaluacion=ModoEvaluacion.POR_DEFECTO.value,
300
  tipo_evaluacion=TipoEvaluacion.PREGUNTAS_CERRADAS_ESPERADAS.value,
@@ -304,15 +354,19 @@ if st.button("Comenzar evaluaci贸n", key="comenzar_eval"):
304
 
305
  temp_dir = Path(tempfile.mkdtemp(prefix="equitia_space_"))
306
  job_dir = temp_dir / "job"
 
307
 
308
  progress = st.progress(0.0)
309
  progress_label = st.empty()
 
310
 
311
  def on_progress(done: int, total: int, current_file: str) -> None:
312
  ratio = (done / total) if total else 0.0
 
313
  progress.progress(ratio)
 
314
  progress_label.info(
315
- f"Progreso: {done}/{total} prompts evaluados ({ratio * 100:.1f}%). Archivo actual: {current_file}"
316
  )
317
 
318
  def invocar_prompt(prompt: str, instruccion_sistema: str | None = None) -> str:
@@ -322,8 +376,13 @@ if st.button("Comenzar evaluaci贸n", key="comenzar_eval"):
322
  instruccion_sistema=instruccion_sistema,
323
  )
324
 
325
- try:
326
- with st.spinner("Cargando modelo y ejecutando evaluaci贸n..."):
 
 
 
 
 
327
  result = ejecutar_job(
328
  request,
329
  job_dir,
@@ -333,7 +392,9 @@ if st.button("Comenzar evaluaci贸n", key="comenzar_eval"):
333
  )
334
 
335
  progress.progress(1.0)
336
- progress_label.success("Evaluaci贸n completada.")
 
 
337
 
338
  resumen_path = result.job_dir / "resumen.json"
339
  resultados_csv = result.graficos_dir / "resultados.csv"
@@ -342,45 +403,90 @@ if st.button("Comenzar evaluaci贸n", key="comenzar_eval"):
342
  if resumen_path.exists():
343
  with open(resumen_path, "r", encoding="utf-8") as f:
344
  resumen = json.load(f)
345
- st.success("Resumen de evaluaci贸n")
346
- st.json(resumen)
347
 
348
- if outliers_txt.exists():
349
- st.markdown("### Avisos de outliers")
350
- st.code(outliers_txt.read_text(encoding="utf-8"))
 
 
351
 
352
- st.markdown("### Gr谩ficos")
353
  for graph_name in [
354
  "resultados_generales.png",
355
  "resultados_tipo_evaluacion.png",
356
  "mapa_calor_tipo_evaluacion.png",
357
  ]:
358
  graph_path = result.graficos_dir / graph_name
359
- if graph_path.exists():
360
- st.image(str(graph_path), caption=graph_name)
 
361
 
 
362
  if resultados_csv.exists():
363
- df = pd.read_csv(resultados_csv, sep="|")
364
- st.markdown("### Vista previa")
365
- st.dataframe(df.head(30), use_container_width=True)
366
-
367
- zip_base = temp_dir / "resultados_equitia"
368
- zip_path = Path(shutil.make_archive(str(zip_base), "zip", str(result.job_dir)))
369
- zip_bytes = zip_path.read_bytes()
370
-
371
- st.download_button(
372
- label="Descargar resultados (ZIP)",
373
- data=io.BytesIO(zip_bytes),
374
- file_name="resultados_equitia.zip",
375
- mime="application/zip",
376
- use_container_width=True,
377
- )
378
 
379
  except Exception as exc:
380
  st.error(f"Error durante la evaluaci贸n: {exc}")
 
381
  finally:
 
 
382
  shutil.rmtree(temp_dir, ignore_errors=True)
383
 
384
- if st.button("Nueva evaluaci贸n"):
385
- st.session_state.clear()
386
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  import re
8
  import shutil
9
  import tempfile
10
+ import time
11
+ import gc
12
  from pathlib import Path
13
 
14
  # Evita inspecciones de modulos que generan ruido con torch.classes en Streamlit.
 
31
  MODELOS_PREDEFINIDOS = [
32
  "Qwen/Qwen2.5-1.5B-Instruct",
33
  "Qwen/Qwen2.5-3B-Instruct",
 
34
  "Other Model",
35
  ]
36
 
 
50
  "modelo_eval_confirmado": "",
51
  "modelo_gen_validado": False,
52
  "modelo_gen_confirmado": "",
53
+ "eval_running": False,
54
+ "eval_success": False,
55
+ "last_result": None,
56
  }
57
  for key, value in defaults.items():
58
  if key not in st.session_state:
59
  st.session_state[key] = value
60
 
61
 
62
+ def _formatear_duracion(segundos: float) -> str:
63
+ total = int(max(segundos, 0))
64
+ horas, resto = divmod(total, 3600)
65
+ minutos, segs = divmod(resto, 60)
66
+ return f"{horas:02d}:{minutos:02d}:{segs:02d}"
67
+
68
+
69
+ def _slug_modelo(model_id: str) -> str:
70
+ return re.sub(r"[^a-zA-Z0-9._-]+", "_", model_id.strip()).strip("_")
71
+
72
+
73
+ def _leer_bytes_si_existe(path: Path) -> bytes | None:
74
+ if path.exists() and path.is_file():
75
+ return path.read_bytes()
76
+ return None
77
+
78
+
79
+ def _liberar_memoria() -> None:
80
+ gc.collect()
81
+ if torch.cuda.is_available():
82
+ torch.cuda.empty_cache()
83
+
84
+
85
  @st.cache_data(show_spinner=False, ttl=3600)
86
  def validar_modelo_existe(model_id: str) -> tuple[bool, str]:
87
  try:
 
187
  )
188
 
189
  # Unico parametro editable en UI para ambos modos.
190
+ timeout_segundos = st.slider(
191
+ "Timeout por llamada (segundos)",
192
+ min_value=10,
193
+ max_value=300,
194
+ value=120,
195
+ disabled=st.session_state["eval_running"],
196
+ )
197
 
198
  if modo == ModoEvaluacion.PERSONALIZADA.value:
199
  st.info("La evaluaci贸n personalizada se implementar谩 despu茅s. Aqu铆 solo preseleccionas modelos por ahora.")
 
217
  "Modelo para generar prompts",
218
  MODELOS_PREDEFINIDOS,
219
  key="modelo_gen_option",
220
+ disabled=st.session_state["eval_running"],
221
  )
222
 
223
  modelo_gen_input = ""
224
  if modelo_gen_option == "Other Model":
225
+ modelo_gen_input = st.text_input(
226
+ "Escribe otro modelo para generaci贸n",
227
+ key="modelo_gen_otro",
228
+ disabled=st.session_state["eval_running"],
229
+ )
230
 
231
  modelo_gen_actual = modelo_gen_input.strip() if modelo_gen_option == "Other Model" else modelo_gen_option
232
  if (
 
236
  st.session_state["modelo_gen_validado"] = False
237
  st.session_state["modelo_gen_confirmado"] = ""
238
 
239
+ if st.button("Validar modelo generador", key="validar_generador", disabled=st.session_state["eval_running"]):
240
  modelo_gen = modelo_gen_input.strip() if modelo_gen_option == "Other Model" else modelo_gen_option
241
  if not modelo_gen:
242
  st.error("Debes indicar un modelo generador.")
 
260
  "Modelo a evaluar",
261
  MODELOS_PREDEFINIDOS,
262
  key="modelo_eval_option",
263
+ disabled=st.session_state["eval_running"],
264
  )
265
 
266
  modelo_eval_input = ""
267
  if modelo_eval_option == "Other Model":
268
+ modelo_eval_input = st.text_input(
269
+ "Escribe otro modelo para evaluar",
270
+ key="modelo_eval_otro",
271
+ disabled=st.session_state["eval_running"],
272
+ )
273
 
274
  modelo_eval_actual = modelo_eval_input.strip() if modelo_eval_option == "Other Model" else modelo_eval_option
275
  if (
 
279
  st.session_state["modelo_eval_validado"] = False
280
  st.session_state["modelo_eval_confirmado"] = ""
281
 
282
+ if st.button("Validar modelo a evaluar", key="validar_modelo_eval", disabled=st.session_state["eval_running"]):
283
  modelo_eval = modelo_eval_input.strip() if modelo_eval_option == "Other Model" else modelo_eval_option
284
  if not modelo_eval:
285
  st.error("Debes indicar un modelo para evaluar.")
 
318
  options=TIPOS_EVALUACION_DISPONIBLES,
319
  default=["preguntas_cerradas_esperadas"],
320
  format_func=lambda x: LABELS_TIPOS_EVALUACION.get(x, x),
321
+ disabled=st.session_state["eval_running"],
322
  )
323
 
324
  if not selected_eval_types:
 
338
  + ", ".join(LABELS_TIPOS_EVALUACION.get(t, t) for t in tipos_no_disponibles)
339
  )
340
 
341
+ if st.button("Comenzar evaluaci贸n", key="comenzar_eval", disabled=st.session_state["eval_running"]):
342
+ st.session_state["eval_running"] = True
343
+ st.session_state["eval_success"] = False
344
+ st.session_state["last_result"] = None
345
+
346
+ _liberar_memoria()
347
+
348
  request = JobRequest(
349
  modo_evaluacion=ModoEvaluacion.POR_DEFECTO.value,
350
  tipo_evaluacion=TipoEvaluacion.PREGUNTAS_CERRADAS_ESPERADAS.value,
 
354
 
355
  temp_dir = Path(tempfile.mkdtemp(prefix="equitia_space_"))
356
  job_dir = temp_dir / "job"
357
+ start_ts = time.perf_counter()
358
 
359
  progress = st.progress(0.0)
360
  progress_label = st.empty()
361
+ estado_fase = st.empty()
362
 
363
  def on_progress(done: int, total: int, current_file: str) -> None:
364
  ratio = (done / total) if total else 0.0
365
+ elapsed = _formatear_duracion(time.perf_counter() - start_ts)
366
  progress.progress(ratio)
367
+ estado_fase.info("Ejecutando proceso de evaluaci贸n")
368
  progress_label.info(
369
+ f"Progreso: {done}/{total} prompts evaluados ({ratio * 100:.1f}%). Archivo actual: {current_file}. Tiempo: {elapsed}"
370
  )
371
 
372
  def invocar_prompt(prompt: str, instruccion_sistema: str | None = None) -> str:
 
376
  instruccion_sistema=instruccion_sistema,
377
  )
378
 
379
+ try:
380
+ estado_fase.info("Obteniendo modelo a evaluar")
381
+ with st.spinner("Obteniendo modelo a evaluar..."):
382
+ cargar_modelo_transformers(st.session_state["modelo_eval_confirmado"])
383
+
384
+ estado_fase.info("Ejecutando proceso de evaluaci贸n")
385
+ with st.spinner("Ejecutando proceso de evaluaci贸n..."):
386
  result = ejecutar_job(
387
  request,
388
  job_dir,
 
392
  )
393
 
394
  progress.progress(1.0)
395
+ elapsed_total = _formatear_duracion(time.perf_counter() - start_ts)
396
+ progress_label.success(f"Evaluaci贸n completada. Tiempo total: {elapsed_total}")
397
+ estado_fase.success("Proceso finalizado correctamente")
398
 
399
  resumen_path = result.job_dir / "resumen.json"
400
  resultados_csv = result.graficos_dir / "resultados.csv"
 
403
  if resumen_path.exists():
404
  with open(resumen_path, "r", encoding="utf-8") as f:
405
  resumen = json.load(f)
 
 
406
 
407
+ zip_id = int(time.time())
408
+ modelo_slug = _slug_modelo(st.session_state["modelo_eval_confirmado"])
409
+ zip_filename = f"resultados_equitia_{modelo_slug}_{zip_id}.zip"
410
+ zip_base = temp_dir / f"resultados_equitia_{zip_id}"
411
+ zip_path = Path(shutil.make_archive(str(zip_base), "zip", str(result.job_dir)))
412
 
413
+ graficos = {}
414
  for graph_name in [
415
  "resultados_generales.png",
416
  "resultados_tipo_evaluacion.png",
417
  "mapa_calor_tipo_evaluacion.png",
418
  ]:
419
  graph_path = result.graficos_dir / graph_name
420
+ graph_bytes = _leer_bytes_si_existe(graph_path)
421
+ if graph_bytes is not None:
422
+ graficos[graph_name] = graph_bytes
423
 
424
+ preview_rows = None
425
  if resultados_csv.exists():
426
+ preview_rows = pd.read_csv(resultados_csv, sep="|").head(30).to_dict(orient="records")
427
+
428
+ st.session_state["last_result"] = {
429
+ "resumen": resumen if resumen_path.exists() else None,
430
+ "outliers": outliers_txt.read_text(encoding="utf-8") if outliers_txt.exists() else None,
431
+ "graficos": graficos,
432
+ "preview_rows": preview_rows,
433
+ "zip_bytes": zip_path.read_bytes(),
434
+ "zip_filename": zip_filename,
435
+ "elapsed_total": elapsed_total,
436
+ "modelo": st.session_state["modelo_eval_confirmado"],
437
+ }
438
+ st.session_state["eval_success"] = True
 
 
439
 
440
  except Exception as exc:
441
  st.error(f"Error durante la evaluaci贸n: {exc}")
442
+ estado_fase.error("Proceso finalizado con error")
443
  finally:
444
+ st.session_state["eval_running"] = False
445
+ _liberar_memoria()
446
  shutil.rmtree(temp_dir, ignore_errors=True)
447
 
448
+ if st.session_state.get("eval_success") and st.session_state.get("last_result"):
449
+ resultado = st.session_state["last_result"]
450
+
451
+ if resultado.get("resumen") is not None:
452
+ st.success("Resumen de evaluaci贸n")
453
+ st.json(resultado["resumen"])
454
+
455
+ if resultado.get("outliers"):
456
+ st.markdown("### Avisos de outliers")
457
+ st.code(resultado["outliers"])
458
+
459
+ st.markdown("### Gr谩ficos")
460
+ for graph_name, graph_bytes in resultado.get("graficos", {}).items():
461
+ st.image(graph_bytes, caption=graph_name)
462
+
463
+ if resultado.get("preview_rows"):
464
+ st.markdown("### Vista previa")
465
+ st.dataframe(pd.DataFrame(resultado["preview_rows"]), use_container_width=True)
466
+
467
+ st.caption(
468
+ "Al descargar el ZIP se reiniciar谩 la evaluaci贸n actual para liberar memoria del Space."
469
+ )
470
+ descarga = st.download_button(
471
+ label="Descargar resultados (ZIP y reiniciar)",
472
+ data=io.BytesIO(resultado["zip_bytes"]),
473
+ file_name=resultado["zip_filename"],
474
+ mime="application/zip",
475
+ use_container_width=True,
476
+ disabled=st.session_state["eval_running"],
477
+ )
478
+ if descarga:
479
+ st.session_state["eval_success"] = False
480
+ st.session_state["last_result"] = None
481
+ _liberar_memoria()
482
+ st.rerun()
483
+
484
+ if st.button("Nueva evaluaci贸n", disabled=st.session_state["eval_running"]):
485
+ st.session_state["eval_success"] = False
486
+ st.session_state["last_result"] = None
487
+ st.session_state["modelo_eval_validado"] = False
488
+ st.session_state["modelo_eval_confirmado"] = ""
489
+ st.session_state["modelo_gen_validado"] = False
490
+ st.session_state["modelo_gen_confirmado"] = ""
491
+ _liberar_memoria()
492
+ st.rerun()