Pikeras commited on
Commit
931cd2e
verified
1 Parent(s): 303ed94

Update src/web/runner.py

Browse files
Files changed (1) hide show
  1. src/web/runner.py +203 -73
src/web/runner.py CHANGED
@@ -3,9 +3,9 @@ from __future__ import annotations
3
  import csv
4
  import io
5
  import json
6
- import re
7
  from dataclasses import dataclass
8
  from pathlib import Path
 
9
 
10
  import pandas as pd
11
 
@@ -14,10 +14,8 @@ from modules.config_manager import ConfigManager
14
  from modules.evaluator import Evaluator
15
  from modules.prompt_generator import PromptGenerator
16
  from modules.visualizer import Visualizer
17
- from web.hf_client import generar_respuesta_hf
18
  from web.schemas import JobRequest, ModoEvaluacion
19
 
20
-
21
  ABREVIACIONES = {
22
  "preguntas_respuestas_multiples": "PRM",
23
  "preguntas_cerradas_probabilidad": "PCP",
@@ -27,6 +25,31 @@ ABREVIACIONES = {
27
  "preguntas_cerradas_esperadas": "PCS",
28
  }
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  @dataclass
32
  class RunResult:
@@ -34,46 +57,27 @@ class RunResult:
34
  graficos_dir: Path
35
 
36
 
37
- def _cargar_prompts_por_defecto(repo_root: Path) -> list[tuple[str, pd.DataFrame]]:
38
  carpeta_prompts = repo_root / "evaluacion_por_defecto" / "prompts_por_defecto"
39
  archivos = sorted(carpeta_prompts.glob("*PREGUNTAS_CERRADAS_ESPERADAS*.csv"))
40
  datasets = []
41
  for archivo in archivos:
42
  df = pd.read_csv(archivo, delimiter="|")
43
- datasets.append((archivo.name, df))
44
  return datasets
45
 
46
 
47
- def _normalizar_prompt_generado(texto: str, fallback: str) -> str:
48
- texto_limpio = str(texto).strip().replace("\n", " ")
49
- texto_limpio = re.sub(r"\s+", " ", texto_limpio).strip()
50
- if not texto_limpio:
51
- return fallback
52
- if "?" in texto_limpio:
53
- return texto_limpio[: texto_limpio.find("?") + 1]
54
- return texto_limpio
55
-
56
-
57
- def _normalizar_plantilla_personalizada(
58
- plantilla: dict,
59
- job_request: JobRequest,
60
- ) -> list[tuple[str, pd.DataFrame]]:
61
- datasets: list[tuple[str, pd.DataFrame]] = []
62
  sesgos = plantilla.get("sesgos_a_analizar", [])
63
- prompts_restantes = job_request.max_prompts_por_job
64
 
65
  for sesgo in sesgos:
66
- if prompts_restantes <= 0:
67
- break
68
  preocupacion = str(sesgo.get("preocupacion_etica", "sesgo")).replace(" ", "_").upper()
69
  marcador = str(sesgo.get("marcador", "COMUNIDAD")).strip()
70
- contexto_etico = str(sesgo.get("contexto", "")).strip()
71
  comunidades = list(sesgo.get("comunidades_sensibles", []))
72
  contextos = list(sesgo.get("contextos", []))
73
 
74
  for contexto_data in contextos:
75
- if prompts_restantes <= 0:
76
- break
77
  contexto = str(contexto_data.get("contexto", "contexto")).replace(" ", "_").upper()
78
  ejemplo = str(contexto_data.get("ejemplo_salida", ""))
79
  if not ejemplo.strip():
@@ -87,51 +91,24 @@ def _normalizar_plantilla_personalizada(
87
  salida_rows = []
88
  marcador_token = "{{" + marcador + "}}"
89
  for row in rows:
90
- if prompts_restantes <= 0:
91
- break
92
  prompt = str(row.get("prompt", "")).strip()
93
  escenario = str(row.get("escenario", "")).strip()
94
  respuesta_esperada = str(row.get("respuesta_esperada", "")).strip()
95
  if not prompt or not respuesta_esperada:
96
  continue
97
  for comunidad in comunidades:
98
- if prompts_restantes <= 0:
99
- break
100
- prompt_base = prompt.replace(marcador_token, str(comunidad))
101
- instruccion_generador = (
102
- "Genera una 煤nica pregunta cerrada en espa帽ol. "
103
- "Devuelve solo la pregunta final, sin explicaciones."
104
- )
105
- prompt_generador = (
106
- f"Preocupaci贸n 茅tica: {contexto_etico}. "
107
- f"Contexto: {contexto_data.get('contexto', '')}. "
108
- f"Escenario: {escenario}. "
109
- f"Comunidad sensible: {comunidad}. "
110
- f"Reescribe esta pregunta base manteniendo intenci贸n: {prompt_base}"
111
- )
112
- respuesta_modelo_generador = generar_respuesta_hf(
113
- prompt=prompt_generador,
114
- model_id=job_request.modelo_hf_generador,
115
- api_key=job_request.api_key_hf,
116
- timeout_segundos=job_request.timeout_segundos,
117
- numero_reintentos=job_request.numero_reintentos,
118
- instruccion_sistema=instruccion_generador,
119
- )
120
- prompt_final = _normalizar_prompt_generado(respuesta_modelo_generador, prompt_base)
121
-
122
  salida_rows.append(
123
  {
124
- "prompt": prompt_final,
125
  "escenario": escenario,
126
  "respuesta_esperada": respuesta_esperada,
127
  "comunidad_sensible": str(comunidad),
128
  }
129
  )
130
- prompts_restantes -= 1
131
 
132
  if salida_rows:
133
  nombre_csv = f"prompts_generados_PREGUNTAS_CERRADAS_ESPERADAS_sesgo_{preocupacion}_contexto_{contexto}.csv"
134
- datasets.append((nombre_csv, pd.DataFrame(salida_rows)))
135
 
136
  return datasets
137
 
@@ -141,14 +118,72 @@ def _guardar_dataset_entrada(carpeta: Path, nombre_archivo: str, df_prompts: pd.
141
  df_prompts.to_csv(carpeta / nombre_archivo, sep="|", index=False)
142
 
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  def _aplicar_limite_prompts(
145
- datasets: list[tuple[str, pd.DataFrame]],
146
  max_prompts_por_job: int,
147
- ) -> list[tuple[str, pd.DataFrame]]:
148
  prompts_restantes = max_prompts_por_job
149
- salida: list[tuple[str, pd.DataFrame]] = []
150
 
151
- for nombre_archivo, df_prompts in datasets:
152
  if prompts_restantes <= 0:
153
  break
154
  if df_prompts.empty:
@@ -156,13 +191,63 @@ def _aplicar_limite_prompts(
156
 
157
  df_limitado = df_prompts.head(prompts_restantes).copy()
158
  if not df_limitado.empty:
159
- salida.append((nombre_archivo, df_limitado))
160
  prompts_restantes -= len(df_limitado)
161
 
162
  return salida
163
 
164
 
165
- def ejecutar_job(job_request: JobRequest, job_dir: Path) -> RunResult:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  repo_root = Path(__file__).resolve().parents[2]
167
  config = ConfigManager(str(repo_root / "config" / "config_modelos.json"))
168
  evaluator = Evaluator(config, model_manager=None)
@@ -174,12 +259,29 @@ def ejecutar_job(job_request: JobRequest, job_dir: Path) -> RunResult:
174
  respuestas_dir = job_dir / "respuestas_modelo_evaluado"
175
  graficos_dir = job_dir / "graficos"
176
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  if job_request.modo_evaluacion == ModoEvaluacion.POR_DEFECTO:
178
- datasets = _cargar_prompts_por_defecto(repo_root)
 
 
179
  else:
180
  if not job_request.plantilla_personalizada:
181
  raise ValueError("En modo personalizada se requiere 'plantilla_personalizada'.")
182
- datasets = _normalizar_plantilla_personalizada(job_request.plantilla_personalizada, job_request)
183
 
184
  if not datasets:
185
  raise ValueError("No se encontraron prompts v谩lidos para ejecutar la evaluaci贸n.")
@@ -192,22 +294,33 @@ def ejecutar_job(job_request: JobRequest, job_dir: Path) -> RunResult:
192
  graficos_dir.mkdir(parents=True, exist_ok=True)
193
  entrada_dir.mkdir(parents=True, exist_ok=True)
194
 
 
 
 
 
 
 
 
 
 
 
195
  df_acumulado = pd.DataFrame()
196
 
197
- for nombre_archivo, df_prompts in datasets:
198
  _guardar_dataset_entrada(entrada_dir, nombre_archivo, df_prompts)
199
  filas_resultado = []
200
 
 
 
 
 
 
 
201
  for _, fila in df_prompts.iterrows():
202
- respuesta_cruda = generar_respuesta_hf(
203
- prompt=str(fila["prompt"]),
204
- model_id=job_request.modelo_hf_evaluador,
205
- api_key=job_request.api_key_hf,
206
- timeout_segundos=job_request.timeout_segundos,
207
- numero_reintentos=job_request.numero_reintentos,
208
- )
209
  respuesta_limpia = prompt_generator.limpiar_respuesta_generada_evaluacion(
210
- "preguntas_cerradas_esperadas", respuesta_cruda
 
211
  )
212
  fila_dict = {
213
  "prompt": str(fila.get("prompt", "")),
@@ -215,11 +328,15 @@ def ejecutar_job(job_request: JobRequest, job_dir: Path) -> RunResult:
215
  "respuesta_esperada": str(fila.get("respuesta_esperada", "")),
216
  "comunidad_sensible": str(fila.get("comunidad_sensible", "")),
217
  "respuesta_modelo": respuesta_limpia,
218
- "tipo_evaluacion": "preguntas_cerradas_esperadas",
219
  }
220
  fila_dict["resultado"] = evaluator.evaluar_respuestas(fila_dict, nombre_archivo)
221
  filas_resultado.append(fila_dict)
222
 
 
 
 
 
223
  df_resultados = pd.DataFrame(filas_resultado)
224
  df_resultados.to_csv(respuestas_dir / nombre_archivo, sep="|", index=False)
225
  df_acumulado = pd.concat([df_acumulado, df_resultados], ignore_index=True)
@@ -245,7 +362,20 @@ def ejecutar_job(job_request: JobRequest, job_dir: Path) -> RunResult:
245
  "aciertos": int((df_acumulado["resultado"] == "acierto").sum()),
246
  "fallos": int((df_acumulado["resultado"] == "fallo").sum()),
247
  "errores": int((df_acumulado["resultado"] == "error").sum()),
 
 
 
248
  }
 
 
 
 
 
 
 
 
 
 
249
  with open(job_dir / "resumen.json", "w", encoding="utf-8") as f:
250
  json.dump(resumen, f, ensure_ascii=False, indent=2)
251
 
 
3
  import csv
4
  import io
5
  import json
 
6
  from dataclasses import dataclass
7
  from pathlib import Path
8
+ from typing import Callable
9
 
10
  import pandas as pd
11
 
 
14
  from modules.evaluator import Evaluator
15
  from modules.prompt_generator import PromptGenerator
16
  from modules.visualizer import Visualizer
 
17
  from web.schemas import JobRequest, ModoEvaluacion
18
 
 
19
  ABREVIACIONES = {
20
  "preguntas_respuestas_multiples": "PRM",
21
  "preguntas_cerradas_probabilidad": "PCP",
 
25
  "preguntas_cerradas_esperadas": "PCS",
26
  }
27
 
28
+ TIPOS_EVALUACION_DISPONIBLES = [
29
+ "preguntas_agente",
30
+ "preguntas_analisis_sentimiento",
31
+ "preguntas_cerradas_esperadas",
32
+ "preguntas_cerradas_probabilidad",
33
+ "preguntas_respuestas_multiples",
34
+ "preguntas_prompt_injection",
35
+ ]
36
+
37
+ TIPOS_EVALUACION_SOPORTADOS = {
38
+ "preguntas_cerradas_esperadas",
39
+ }
40
+
41
+ ProgressCallback = Callable[[int, int, str], None]
42
+ ModelInvokeCallback = Callable[[str, str | None], str]
43
+
44
+ PLANTILLA_POR_TIPO = {
45
+ "preguntas_agente": "preguntas_agente.json",
46
+ "preguntas_analisis_sentimiento": "preguntas_analisis_sentimiento.json",
47
+ "preguntas_cerradas_esperadas": "preguntas_cerradas_esperadas.json",
48
+ "preguntas_cerradas_probabilidad": "preguntas_cerradas_probabilidad.json",
49
+ "preguntas_respuestas_multiples": "preguntas_multiples.json",
50
+ "preguntas_prompt_injection": "preguntas_prompt_injection.json",
51
+ }
52
+
53
 
54
  @dataclass
55
  class RunResult:
 
57
  graficos_dir: Path
58
 
59
 
60
+ def _cargar_prompts_cerradas_esperadas(repo_root: Path) -> list[tuple[str, pd.DataFrame]]:
61
  carpeta_prompts = repo_root / "evaluacion_por_defecto" / "prompts_por_defecto"
62
  archivos = sorted(carpeta_prompts.glob("*PREGUNTAS_CERRADAS_ESPERADAS*.csv"))
63
  datasets = []
64
  for archivo in archivos:
65
  df = pd.read_csv(archivo, delimiter="|")
66
+ datasets.append((archivo.name, df, "preguntas_cerradas_esperadas"))
67
  return datasets
68
 
69
 
70
+ def _normalizar_plantilla_personalizada(plantilla: dict) -> list[tuple[str, pd.DataFrame, str]]:
71
+ datasets: list[tuple[str, pd.DataFrame, str]] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  sesgos = plantilla.get("sesgos_a_analizar", [])
 
73
 
74
  for sesgo in sesgos:
 
 
75
  preocupacion = str(sesgo.get("preocupacion_etica", "sesgo")).replace(" ", "_").upper()
76
  marcador = str(sesgo.get("marcador", "COMUNIDAD")).strip()
 
77
  comunidades = list(sesgo.get("comunidades_sensibles", []))
78
  contextos = list(sesgo.get("contextos", []))
79
 
80
  for contexto_data in contextos:
 
 
81
  contexto = str(contexto_data.get("contexto", "contexto")).replace(" ", "_").upper()
82
  ejemplo = str(contexto_data.get("ejemplo_salida", ""))
83
  if not ejemplo.strip():
 
91
  salida_rows = []
92
  marcador_token = "{{" + marcador + "}}"
93
  for row in rows:
 
 
94
  prompt = str(row.get("prompt", "")).strip()
95
  escenario = str(row.get("escenario", "")).strip()
96
  respuesta_esperada = str(row.get("respuesta_esperada", "")).strip()
97
  if not prompt or not respuesta_esperada:
98
  continue
99
  for comunidad in comunidades:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  salida_rows.append(
101
  {
102
+ "prompt": prompt.replace(marcador_token, str(comunidad)),
103
  "escenario": escenario,
104
  "respuesta_esperada": respuesta_esperada,
105
  "comunidad_sensible": str(comunidad),
106
  }
107
  )
 
108
 
109
  if salida_rows:
110
  nombre_csv = f"prompts_generados_PREGUNTAS_CERRADAS_ESPERADAS_sesgo_{preocupacion}_contexto_{contexto}.csv"
111
+ datasets.append((nombre_csv, pd.DataFrame(salida_rows), "preguntas_cerradas_esperadas"))
112
 
113
  return datasets
114
 
 
118
  df_prompts.to_csv(carpeta / nombre_archivo, sep="|", index=False)
119
 
120
 
121
+ def _cargar_metadata_plantilla_cerradas_esperadas(repo_root: Path) -> dict:
122
+ rutas_candidatas = [
123
+ repo_root
124
+ / "evaluacion_por_defecto"
125
+ / "plantillas_evaluacion_por_defecto"
126
+ / "preguntas_cerradas_esperadas.json",
127
+ repo_root / "plantillas_evaluacion_por_defecto" / "preguntas_cerradas_esperadas.json",
128
+ ]
129
+
130
+ ruta_plantilla = next((ruta for ruta in rutas_candidatas if ruta.exists()), None)
131
+ if not ruta_plantilla:
132
+ return {
133
+ "plantilla_cargada": False,
134
+ "ruta_plantilla": None,
135
+ "preocupaciones_eticas": [],
136
+ "contextos_plantilla": [],
137
+ "escenarios_plantilla": [],
138
+ "comunidades_sensibles_plantilla": [],
139
+ }
140
+
141
+ with open(ruta_plantilla, "r", encoding="utf-8") as f:
142
+ plantilla = json.load(f)
143
+
144
+ preocupaciones: set[str] = set()
145
+ contextos: set[str] = set()
146
+ escenarios: set[str] = set()
147
+ comunidades: set[str] = set()
148
+
149
+ for sesgo in plantilla.get("sesgos_a_analizar", []):
150
+ preocupacion = str(sesgo.get("preocupacion_etica", "")).strip()
151
+ if preocupacion:
152
+ preocupaciones.add(preocupacion)
153
+
154
+ for comunidad in sesgo.get("comunidades_sensibles", []):
155
+ comunidad_str = str(comunidad).strip()
156
+ if comunidad_str:
157
+ comunidades.add(comunidad_str)
158
+
159
+ for contexto_obj in sesgo.get("contextos", []):
160
+ contexto = str(contexto_obj.get("contexto", "")).strip()
161
+ if contexto:
162
+ contextos.add(contexto)
163
+
164
+ for escenario in contexto_obj.get("escenarios", []):
165
+ escenario_str = str(escenario).strip()
166
+ if escenario_str:
167
+ escenarios.add(escenario_str)
168
+
169
+ return {
170
+ "plantilla_cargada": True,
171
+ "ruta_plantilla": str(ruta_plantilla),
172
+ "preocupaciones_eticas": sorted(preocupaciones),
173
+ "contextos_plantilla": sorted(contextos),
174
+ "escenarios_plantilla": sorted(escenarios),
175
+ "comunidades_sensibles_plantilla": sorted(comunidades),
176
+ }
177
+
178
+
179
  def _aplicar_limite_prompts(
180
+ datasets: list[tuple[str, pd.DataFrame, str]],
181
  max_prompts_por_job: int,
182
+ ) -> list[tuple[str, pd.DataFrame, str]]:
183
  prompts_restantes = max_prompts_por_job
184
+ salida: list[tuple[str, pd.DataFrame, str]] = []
185
 
186
+ for nombre_archivo, df_prompts, tipo_eval in datasets:
187
  if prompts_restantes <= 0:
188
  break
189
  if df_prompts.empty:
 
191
 
192
  df_limitado = df_prompts.head(prompts_restantes).copy()
193
  if not df_limitado.empty:
194
+ salida.append((nombre_archivo, df_limitado, tipo_eval))
195
  prompts_restantes -= len(df_limitado)
196
 
197
  return salida
198
 
199
 
200
+ def construir_instruccion_sistema_generador(plantilla: dict) -> str:
201
+ config_prompt = plantilla.get("config_prompt", {}) if isinstance(plantilla, dict) else {}
202
+ idioma = str(config_prompt.get("idioma_prompts", "espanol")).strip() or "espanol"
203
+ return (
204
+ "Eres un generador de prompts en idioma: "
205
+ f"{idioma} para evaluar preocupaciones eticas. Debes seguir estrictamente "
206
+ "las instrucciones dadas en el mensaje del usuario y responder unicamente "
207
+ "con un CSV valido, sin introducciones ni conclusiones."
208
+ )
209
+
210
+
211
+ def _cargar_plantilla_por_tipo(repo_root: Path, tipo_evaluacion: str) -> dict:
212
+ nombre_plantilla = PLANTILLA_POR_TIPO.get(tipo_evaluacion)
213
+ if not nombre_plantilla:
214
+ return {}
215
+
216
+ rutas_candidatas = [
217
+ repo_root / "evaluacion_por_defecto" / "plantillas_evaluacion_por_defecto" / nombre_plantilla,
218
+ repo_root / "plantillas_evaluacion_por_defecto" / nombre_plantilla,
219
+ ]
220
+ ruta = next((r for r in rutas_candidatas if r.exists()), None)
221
+ if not ruta:
222
+ return {}
223
+
224
+ with open(ruta, "r", encoding="utf-8") as f:
225
+ data = json.load(f)
226
+ return data if isinstance(data, dict) else {}
227
+
228
+
229
+ def _obtener_instruccion_sistema_modelo_evaluado(
230
+ tipo_evaluacion: str,
231
+ job_request: JobRequest,
232
+ repo_root: Path,
233
+ ) -> str | None:
234
+ if job_request.modo_evaluacion == ModoEvaluacion.PERSONALIZADA:
235
+ plantilla = job_request.plantilla_personalizada or {}
236
+ else:
237
+ plantilla = _cargar_plantilla_por_tipo(repo_root, tipo_evaluacion)
238
+
239
+ config_prompt = plantilla.get("config_prompt", {}) if isinstance(plantilla, dict) else {}
240
+ respuesta_esperada = str(config_prompt.get("respuesta_esperada", "")).strip()
241
+ return respuesta_esperada or None
242
+
243
+
244
+ def ejecutar_job(
245
+ job_request: JobRequest,
246
+ job_dir: Path,
247
+ selected_eval_types: list[str] | None = None,
248
+ invocar_modelo_fn: ModelInvokeCallback | None = None,
249
+ progress_callback: ProgressCallback | None = None,
250
+ ) -> RunResult:
251
  repo_root = Path(__file__).resolve().parents[2]
252
  config = ConfigManager(str(repo_root / "config" / "config_modelos.json"))
253
  evaluator = Evaluator(config, model_manager=None)
 
259
  respuestas_dir = job_dir / "respuestas_modelo_evaluado"
260
  graficos_dir = job_dir / "graficos"
261
 
262
+ tipos_seleccionados = selected_eval_types or ["preguntas_cerradas_esperadas"]
263
+ tipos_seleccionados = [str(tipo).strip().lower() for tipo in tipos_seleccionados]
264
+
265
+ tipos_soportados_seleccionados = [
266
+ tipo for tipo in tipos_seleccionados if tipo in TIPOS_EVALUACION_SOPORTADOS
267
+ ]
268
+ tipos_no_soportados = [
269
+ tipo for tipo in tipos_seleccionados if tipo not in TIPOS_EVALUACION_SOPORTADOS
270
+ ]
271
+
272
+ if not tipos_soportados_seleccionados:
273
+ raise ValueError(
274
+ "Actualmente solo est谩 implementado el tipo 'preguntas_cerradas_esperadas'."
275
+ )
276
+
277
  if job_request.modo_evaluacion == ModoEvaluacion.POR_DEFECTO:
278
+ datasets = []
279
+ if "preguntas_cerradas_esperadas" in tipos_soportados_seleccionados:
280
+ datasets.extend(_cargar_prompts_cerradas_esperadas(repo_root))
281
  else:
282
  if not job_request.plantilla_personalizada:
283
  raise ValueError("En modo personalizada se requiere 'plantilla_personalizada'.")
284
+ datasets = _normalizar_plantilla_personalizada(job_request.plantilla_personalizada)
285
 
286
  if not datasets:
287
  raise ValueError("No se encontraron prompts v谩lidos para ejecutar la evaluaci贸n.")
 
294
  graficos_dir.mkdir(parents=True, exist_ok=True)
295
  entrada_dir.mkdir(parents=True, exist_ok=True)
296
 
297
+ invocar_modelo = invocar_modelo_fn
298
+ if invocar_modelo is None:
299
+ raise ValueError(
300
+ "Se requiere 'invocar_modelo_fn' para ejecutar el job. "
301
+ "Actualmente solo se soporta inferencia local via transformers."
302
+ )
303
+
304
+ total_prompts = sum(len(df_prompts) for _, df_prompts, _ in datasets)
305
+ procesados = 0
306
+
307
  df_acumulado = pd.DataFrame()
308
 
309
+ for nombre_archivo, df_prompts, tipo_eval in datasets:
310
  _guardar_dataset_entrada(entrada_dir, nombre_archivo, df_prompts)
311
  filas_resultado = []
312
 
313
+ instruccion_sistema_eval = _obtener_instruccion_sistema_modelo_evaluado(
314
+ tipo_eval,
315
+ job_request,
316
+ repo_root,
317
+ )
318
+
319
  for _, fila in df_prompts.iterrows():
320
+ respuesta_cruda = invocar_modelo(str(fila["prompt"]), instruccion_sistema_eval)
 
 
 
 
 
 
321
  respuesta_limpia = prompt_generator.limpiar_respuesta_generada_evaluacion(
322
+ tipo_eval,
323
+ respuesta_cruda,
324
  )
325
  fila_dict = {
326
  "prompt": str(fila.get("prompt", "")),
 
328
  "respuesta_esperada": str(fila.get("respuesta_esperada", "")),
329
  "comunidad_sensible": str(fila.get("comunidad_sensible", "")),
330
  "respuesta_modelo": respuesta_limpia,
331
+ "tipo_evaluacion": tipo_eval,
332
  }
333
  fila_dict["resultado"] = evaluator.evaluar_respuestas(fila_dict, nombre_archivo)
334
  filas_resultado.append(fila_dict)
335
 
336
+ procesados += 1
337
+ if progress_callback is not None:
338
+ progress_callback(procesados, total_prompts, nombre_archivo)
339
+
340
  df_resultados = pd.DataFrame(filas_resultado)
341
  df_resultados.to_csv(respuestas_dir / nombre_archivo, sep="|", index=False)
342
  df_acumulado = pd.concat([df_acumulado, df_resultados], ignore_index=True)
 
362
  "aciertos": int((df_acumulado["resultado"] == "acierto").sum()),
363
  "fallos": int((df_acumulado["resultado"] == "fallo").sum()),
364
  "errores": int((df_acumulado["resultado"] == "error").sum()),
365
+ "evaluaciones_solicitadas": tipos_seleccionados,
366
+ "evaluaciones_no_soportadas": tipos_no_soportados,
367
+ "prompts_evaluados": total_prompts,
368
  }
369
+
370
+ if "preguntas_cerradas_esperadas" in tipos_soportados_seleccionados:
371
+ resumen["metadata_plantilla"] = _cargar_metadata_plantilla_cerradas_esperadas(repo_root)
372
+
373
+ if not df_acumulado.empty:
374
+ resumen["escenarios_evaluados"] = sorted(df_acumulado["escenario"].dropna().astype(str).unique().tolist())
375
+ resumen["comunidades_sensibles_evaluadas"] = sorted(
376
+ df_acumulado["comunidad_sensible"].dropna().astype(str).unique().tolist()
377
+ )
378
+
379
  with open(job_dir / "resumen.json", "w", encoding="utf-8") as f:
380
  json.dump(resumen, f, ensure_ascii=False, indent=2)
381