C2MV commited on
Commit
b9fbc5e
·
verified ·
1 Parent(s): be9c158

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +128 -109
app.py CHANGED
@@ -1,13 +1,13 @@
1
  """
2
- CÓDIGO COMPLETO Y CORREGIDO - VERSIÓN 7.7 (Agente con Estrategia de Fallback Configurable)
3
- - MEJORA MAYOR: Se ha implementado un "Plan B" configurable para el ModelSelectionAgent.
4
- - NUEVA FUNCIONALIDAD: El usuario ahora puede elegir la métrica de ranking (R² o RMSE) y el número
5
- de "Top N" modelos a seleccionar si el filtrado estricto inicial falla.
6
- - UI MEJORADA: Se han añadido controles (Radio y Slider) en la interfaz para configurar
7
- estos nuevos parámetros del agente.
8
- - ROBUSTEZ: El pipeline de refinamiento ahora es más flexible y siempre proporciona una
9
- selección de los modelos más prometedores según los criterios del usuario.
10
- - MANTIENE: El flujo de dos etapas y la arquitectura general.
11
  """
12
 
13
  import gradio as gr
@@ -16,14 +16,21 @@ import pandas as pd
16
  import json
17
  import tempfile
18
  import os
 
19
  from datetime import datetime
20
  import plotly.graph_objects as go
21
  import logging
22
  import numpy as np
 
23
 
24
  # --- CONFIGURACIÓN Y CLIENTES ---
25
- logging.basicConfig(level=logging.INFO)
26
- logger = logging.getLogger(__name__)
 
 
 
 
 
27
 
28
  try: biotech_client = Client("C2MV/BiotechU4"); logger.info("✅ Cliente BiotechU4 inicializado.")
29
  except: biotech_client = None
@@ -35,12 +42,9 @@ except: analysis_client = None
35
  # ============================================================================
36
 
37
  class LoggingAgent:
38
- """Agente 4: Registra todas las acciones para transparencia total."""
39
  def __init__(self): self.log_entries, self.start_time = [], datetime.now(); logger.info("🕵️ LoggingAgent activado.")
40
  def register(self, agent_name, action, details=""):
41
- entry = f"**{datetime.now().strftime('%H:%M:%S')} | {agent_name}:** {action}"
42
- if details: entry += f"\n> *Detalles: {details}*"
43
- self.log_entries.append(entry)
44
  def get_report(self):
45
  if not self.log_entries: return "### 🕵️ Informe de Actividad\n\nNo se registraron actividades."
46
  return "### 🕵️ Informe de Actividad de Agentes\n\n---\n\n" + "\n\n---\n\n".join(self.log_entries) + f"\n\n---\n\n**Tiempo total: {(datetime.now() - self.start_time).total_seconds():.2f} s.**"
@@ -48,7 +52,6 @@ class LoggingAgent:
48
 
49
 
50
  class StructureValidationAgent:
51
- """Agente 1: Valida la estructura del archivo y el formato de laboratorio."""
52
  def __init__(self, log_agent: LoggingAgent): self.log_agent = log_agent
53
  def validate(self, file_obj):
54
  try:
@@ -60,6 +63,51 @@ class StructureValidationAgent:
60
  return True, "Formato de archivo básico validado."
61
 
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  class ModelSelectionAgent:
64
  """Agente 3 (CON FALLBACK CONFIGURABLE): Identifica los mejores modelos, con un plan B personalizable."""
65
  def __init__(self, log_agent: LoggingAgent): self.log_agent = log_agent
@@ -70,15 +118,15 @@ class ModelSelectionAgent:
70
  if str(col).lower() == name.lower(): return col
71
  return None
72
 
73
- def identify_best_models(self, results_df, component, r2_threshold, rmse_threshold, ranking_metric, top_n):
74
  self.log_agent.register("ModelSelectionAgent", f"Iniciando identificación para: '{component}'.")
75
 
76
- # 1. Normalizar nombres de columnas clave
77
  model_col = self._find_column(results_df.columns, ['model', 'modelo'])
78
  if not model_col: return [], "Error: No se encontró la columna de nombres de modelos ('Model')."
79
  df_norm = results_df.rename(columns={model_col: 'Model'})
80
 
81
- # 2. Identificar y procesar columnas de métricas
82
  r2_target_col, rmse_target_col = None, None
83
  if component != 'all':
84
  r2_target_col = self._find_column(df_norm.columns, [f'r2_{component}'])
@@ -98,49 +146,45 @@ class ModelSelectionAgent:
98
  model_performance = df_norm.groupby('Model').agg({r2_target_col: 'mean', rmse_target_col: 'mean'}).reset_index()
99
 
100
  # 4. Intento 1: Filtrado Estricto
101
- good_models_df = model_performance[
102
- (model_performance[r2_target_col] >= r2_threshold) &
103
- (model_performance[rmse_target_col] <= rmse_threshold)
104
- ]
105
 
106
  if not good_models_df.empty:
107
  best_models_list = sorted([str(model).lower() for model in good_models_df['Model'].tolist()])
108
  reasoning = f"Agente identificó **{len(best_models_list)}** modelo(s) que cumplen tus criterios: `{', '.join(best_models_list)}`."
109
- self.log_agent.register("ModelSelectionAgent", "Éxito en filtrado primario.", reasoning)
110
  return best_models_list, reasoning
111
  else:
112
- # 5. Intento 2: Plan B - Ranking Estratégico
113
- self.log_agent.register("ModelSelectionAgent", "Filtro primario falló. Activando fallback: 'Top N Ranking'.", f"Criterio: {ranking_metric}, Top: {top_n}")
114
 
115
- # Determinar la columna y el orden para el ranking
116
- if "rmse" in ranking_metric.lower():
117
- sort_col = rmse_target_col
118
- is_ascending = True
119
- metric_name = "RMSE"
120
- else: # Por defecto, R²
121
- sort_col = r2_target_col
122
- is_ascending = False
123
- metric_name = "R²"
124
-
125
- # Ordenar y seleccionar el Top N
126
- sorted_performance = model_performance.sort_values(by=sort_col, ascending=is_ascending)
 
 
127
  top_n_df = sorted_performance.head(top_n)
128
-
129
- if top_n_df.empty:
130
- return [], "No se encontraron modelos para realizar el ranking del Plan B."
131
 
132
  best_models_list = sorted([str(model).lower() for model in top_n_df['Model'].tolist()])
133
  reasoning = (f"**Advertencia:** Ningún modelo cumplió con los criterios iniciales.\n\n"
134
  f"Como plan B, el agente ha seleccionado los **Top {len(best_models_list)}** modelos con el mejor **{metric_name} promedio**: `{', '.join(best_models_list)}`.")
135
- self.log_agent.register("ModelSelectionAgent", "Fallback completado.", reasoning)
136
  return best_models_list, reasoning
137
 
138
  # --- INICIALIZACIÓN DE AGENTES GLOBALES ---
139
- log_agent = LoggingAgent(); validation_agent = StructureValidationAgent(log_agent); model_selection_agent = ModelSelectionAgent(log_agent)
 
 
140
 
141
- # --- FUNCIONES AUXILIARES Y DEL PIPELINE ---
142
- def create_dummy_plot(title="Esperando resultados...", text="Sube un archivo y ejecuta el pipeline"):
143
- fig = go.Figure(go.Scatter(x=[], y=[])); fig.update_layout(title=title, template="plotly_white", height=500, annotations=[dict(text=text, showarrow=False)])
144
  return fig
145
 
146
  def detect_experiments(file_obj):
@@ -151,71 +195,64 @@ def detect_experiments(file_obj):
151
  return gr.update(choices=exp_names, value=exp_names, interactive=True)
152
  except Exception as e: return gr.update(choices=[], value=[], interactive=False, placeholder=f"Error: {e}")
153
 
154
- # --- ETAPA 1: ANÁLISIS BASE ---
155
  def run_base_analysis(file, models, exp_names_selected, component, use_de, maxfev, progress=gr.Progress()):
156
  log_agent.clear(); progress(0, desc="🚀 Iniciando Análisis Base...")
157
  if not file or not models or not exp_names_selected:
158
  return create_dummy_plot(), None, "❌ Por favor, sube un archivo y selecciona modelos/experimentos.", gr.update(interactive=False), {}, None, None, log_agent.get_report()
159
-
160
  log_agent.register("Pipeline (Etapa 1)", "Iniciando Análisis Base."); progress(0.2, desc="Validando archivo...")
161
  is_valid, msg = validation_agent.validate(file)
162
  if not is_valid: return create_dummy_plot(), None, msg, gr.update(interactive=False), {}, None, None, log_agent.get_report()
163
-
164
  progress(0.5, desc="Ejecutando análisis biotecnológico...");
165
  if not biotech_client: return create_dummy_plot(), None, "❌ Cliente BiotechU4 no disponible.", gr.update(interactive=False), {}, None, None, log_agent.get_report()
166
-
167
  try:
168
- exp_names_str = ",".join(exp_names_selected)
169
- models_lower = [str(m).lower() for m in models]
170
  plot_info, df_data, status = biotech_client.predict(file=handle_file(file.name), models=models_lower, component=component, use_de=use_de, maxfev=maxfev, exp_names=exp_names_str, api_name="/run_analysis_wrapper")
171
  if "Error" in status: raise Exception(status)
172
  except Exception as e:
173
  return create_dummy_plot(), None, f"❌ Error en Análisis Base: {e}", gr.update(interactive=False), {}, None, None, log_agent.get_report()
174
-
175
  progress(1, desc="🎉 Análisis Base Completado")
176
- final_status = f"✅ Análisis Base completado. \n➡️ Ahora puedes aplicar el filtro de IA y generar el informe final."
177
  results_df_obj = {'data': df_data['data'], 'headers': df_data['headers']}
178
  fig = go.Figure(json.loads(plot_info['plot'])) if plot_info and 'plot' in plot_info else create_dummy_plot()
179
- original_params = {'models': models, 'exp_names': exp_names_selected, 'component': component, 'use_de': use_de, 'maxfev': maxfev}
180
-
181
  return fig, df_data, final_status, gr.update(interactive=True), results_df_obj, file.name, original_params, log_agent.get_report()
182
 
183
- # --- ETAPA 2: REFINAMIENTO Y REPORTE IA ---
184
- def refine_and_generate_report(baseline_results, file_path, original_params, r2_threshold, rmse_threshold, ranking_metric, top_n, ia_model, detail_level, language, additional_specs, max_output_tokens, use_personal_key, personal_api_key, progress=gr.Progress()):
185
- progress(0, desc="🚀 Iniciando Refinamiento con IA..."); log_agent.register("Pipeline (Etapa 2)", "Iniciando Refinamiento y Generación de Informe.")
186
  if not baseline_results or not file_path or not original_params:
187
  return gr.update(), None, None, None, "❌ No hay resultados base para refinar.", None, log_agent.get_report()
188
 
189
- progress(0.1, desc="Agente 3 identificando mejores modelos...")
 
 
 
 
190
  results_df = pd.DataFrame(baseline_results['data'], columns=baseline_results['headers'])
191
- best_models, reasoning = model_selection_agent.identify_best_models(results_df, original_params['component'], r2_threshold, rmse_threshold, ranking_metric, top_n)
192
 
193
  if not best_models:
194
  return gr.update(), baseline_results, None, None, f"🤖 Análisis del Agente:\n{reasoning}", None, log_agent.get_report()
195
 
196
- progress(0.3, desc="Re-ejecutando análisis con los mejores modelos...")
197
  try:
198
  exp_names_str = ",".join(original_params['exp_names'])
199
- final_plot_info, final_df_data, final_status = biotech_client.predict(
200
- file=handle_file(file_path), models=best_models, component=original_params['component'], use_de=original_params['use_de'],
201
- maxfev=original_params['maxfev'], exp_names=exp_names_str, api_name="/run_analysis_wrapper"
202
- )
203
  if "Error" in final_status: raise Exception(final_status)
204
- log_agent.register("BiotechU4 Client", "Re-análisis final completado.", f"Modelos usados: {best_models}")
205
  except Exception as e:
206
  return gr.update(), None, None, None, f"❌ Error en el re-análisis final: {e}", None, log_agent.get_report()
207
 
208
- progress(0.6, desc="Creando puente de datos y generando informe IA..."); temp_csv_file = None
209
  try:
210
  final_results_df = pd.DataFrame(final_df_data['data'], columns=final_df_data['headers'])
211
  with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', delete=False, encoding='utf-8') as temp_f:
212
  final_results_df.to_csv(temp_f.name, index=False); temp_csv_file = temp_f.name
213
-
214
  current_analysis_client = analysis_client
215
  if use_personal_key and personal_api_key: current_analysis_client = Client("C2MV/Project-HF-2025-2", hf_token=personal_api_key)
216
  chunk_update_dict = current_analysis_client.predict(files=[handle_file(temp_csv_file)], api_name="/update_chunk_column_selector")
217
  selected_chunk_column = chunk_update_dict['choices'][0][0]
218
- result = current_analysis_client.predict(files=[handle_file(temp_csv_file)], chunk_column=selected_chunk_column, qwen_model=ia_model, detail_level=detail_level, language=language, additional_specs=additional_specs, max_output_tokens=max_output_tokens, api_name="/process_files_and_analyze")
219
  _, analysis_report, implementation_code, token_usage = result
220
  except Exception as e:
221
  return gr.update(), final_df_data, None, None, f"❌ Error generando informe IA: {e}", None, log_agent.get_report()
@@ -230,10 +267,9 @@ def refine_and_generate_report(baseline_results, file_path, original_params, r2_
230
 
231
  final_status = f"✅ Refinamiento y reporte completados.\n{reasoning}\nInforme IA generado con {token_usage}."
232
  final_fig = go.Figure(json.loads(final_plot_info['plot'])) if final_plot_info and 'plot' in final_plot_info else create_dummy_plot()
233
-
234
  return final_fig, final_df_data, analysis_report, implementation_code, final_status, final_report_path, log_agent.get_report()
235
 
236
- # --- FUNCIÓN PARA CREAR ARCHIVO DE EJEMPLO Y UI ---
237
  def create_dummy_excel_file():
238
  examples_dir = "examples"; os.makedirs(examples_dir, exist_ok=True); file_path = os.path.join(examples_dir, "archivo.xlsx")
239
  if not os.path.exists(file_path):
@@ -254,82 +290,65 @@ theme = gr.themes.Soft(primary_hue="blue", secondary_hue="indigo", neutral_hue="
254
  if __name__ == "__main__":
255
  create_dummy_excel_file()
256
  with gr.Blocks(theme=theme, title="BioTech Analysis & Report Generator") as demo:
257
- gr.Markdown("# 🧬 BioTech Analysis & Report Generator v7.7")
258
  gr.Markdown("### Un pipeline inteligente de dos etapas: Análisis Base y Refinamiento con IA.")
259
  baseline_results_state = gr.State(value=None); file_path_state = gr.State(value=None); original_params_state = gr.State(value=None)
260
-
261
  with gr.Row():
262
  with gr.Column(scale=1):
263
  gr.Markdown("### 1. Carga y Configuración del Análisis Base")
264
- file_input = gr.File(label="📁 Archivo de Datos (Excel con experimentos)", file_types=[".xlsx", ".xls"])
265
- gr.Examples(examples=["examples/archivo.xlsx"], inputs=[file_input])
266
- exp_names_input = gr.CheckboxGroup(label="🔬 Experimentos a Analizar", info="Se detectarán al subir un archivo.", interactive=False)
267
- models_input = gr.CheckboxGroup(choices=BIOTECH_MODELS, value=BIOTECH_MODELS, label="📊 Modelos a Evaluar", info="Selecciona todos los modelos para una evaluación completa.")
268
  component_input = gr.Dropdown(['all', 'biomass', 'substrate', 'product'], value='all', label="📈 Componente a Analizar/Filtrar")
269
- with gr.Accordion("Parámetros Avanzados del Análisis", open=False):
270
  use_de_input = gr.Checkbox(label="🧮 Usar Evolución Diferencial", value=False)
271
  maxfev_input = gr.Slider(label="🔄 Máx. Iteraciones", minimum=10000, maximum=100000, value=50000, step=1000)
272
  run_base_analysis_btn = gr.Button("1. Ejecutar Análisis Base", variant="secondary")
273
 
274
  with gr.Group():
275
  gr.Markdown("### 2. Refinamiento con IA")
276
- gr.Markdown("#### Criterios de Selección Primarios")
277
- r2_threshold_slider = gr.Slider(minimum=0.0, maximum=0.99, value=0.5, step=0.01, label="R² Mínimo", info="Los modelos deben superar este umbral.")
278
- rmse_threshold_input = gr.Number(value=1.0, label="RMSE Máximo", info="Los modelos deben estar por debajo de este umbral.")
 
 
 
 
 
279
 
280
- # --- NUEVOS CONTROLES PARA EL PLAN B ---
281
- gr.Markdown("#### Criterios de Selección Avanzada (Plan B)")
282
- ranking_metric_input = gr.Radio(
283
- choices=["R² (más alto es mejor)", "RMSE (más bajo es mejor)"],
284
- value="R² (más alto es mejor)",
285
- label="Métrica de Ranking",
286
- info="Si ningún modelo cumple los criterios primarios, se usará esta métrica para elegir los mejores."
287
- )
288
- top_n_input = gr.Slider(
289
- minimum=1, maximum=5, value=3, step=1,
290
- label="Top N Modelos a Seleccionar",
291
- info="Número de modelos a seleccionar en el Plan B."
292
- )
293
-
294
  with gr.Accordion("Parámetros del Informe de IA Final", open=False):
295
  ia_model_input = gr.Dropdown(choices=IA_MODELS, value=IA_MODELS[0], label="🤖 Modelo de IA para Informe")
296
  detail_level_input = gr.Radio(['detailed', 'summarized'], value='detailed', label="📋 Nivel de Detalle")
297
  language_input = gr.Dropdown(['es', 'en'], value='es', label="🌐 Idioma")
298
- max_output_tokens_input = gr.Slider(minimum=1000, maximum=32000, value=4000, step=100, label="🔢 Máx. Tokens")
299
- additional_specs_input = gr.Textbox(label="📝 Especificaciones Adicionales", lines=2)
300
  use_personal_key_input = gr.Checkbox(label="Usar Token HF Personal", value=False)
301
  personal_api_key_input = gr.Textbox(label="Token HF", type="password", visible=False)
302
  refine_with_ia_btn = gr.Button("2. 🤖 Aplicar Filtro y Generar Informe IA", variant="primary", interactive=False)
303
-
304
  with gr.Column(scale=2):
305
  gr.Markdown("### 3. Resultados")
306
- status_output = gr.Textbox(label="📊 Registro de Estado del Proceso", lines=5, interactive=False)
307
  with gr.Tabs():
308
  with gr.TabItem("📊 Visualización"): plot_output = gr.Plot()
309
  with gr.TabItem("📋 Tabla de Modelado"): table_output = gr.Dataframe()
310
- with gr.TabItem("📝 Informe IA"): analysis_output = gr.Markdown("El informe de IA aparecerá aquí después de aplicar el filtro.")
311
  with gr.TabItem("💻 Código"): code_output = gr.Code(language="python")
312
- with gr.TabItem("🕵️ Registro de Agentes IA"): agent_log_output = gr.Markdown()
313
- download_link_markdown = gr.Markdown("*El enlace de descarga aparecerá aquí al finalizar.*")
314
- report_output = gr.File(label="📥 Descargar Informe Final (.md)", interactive=False)
315
  report_path_state = gr.State(value=None)
316
 
317
- # --- Lógica de la UI ---
318
  file_input.upload(fn=detect_experiments, inputs=file_input, outputs=exp_names_input)
319
  use_personal_key_input.change(lambda x: gr.update(visible=x), inputs=use_personal_key_input, outputs=personal_api_key_input)
320
-
321
  run_base_analysis_btn.click(
322
  fn=run_base_analysis,
323
  inputs=[file_input, models_input, exp_names_input, component_input, use_de_input, maxfev_input],
324
  outputs=[plot_output, table_output, status_output, refine_with_ia_btn, baseline_results_state, file_path_state, original_params_state, agent_log_output]
325
  )
326
-
327
  refine_with_ia_btn.click(
328
  fn=refine_and_generate_report,
329
- inputs=[baseline_results_state, file_path_state, original_params_state, r2_threshold_slider, rmse_threshold_input, ranking_metric_input, top_n_input, ia_model_input, detail_level_input, language_input, additional_specs_input, max_output_tokens_input, use_personal_key_input, personal_api_key_input],
330
  outputs=[plot_output, table_output, analysis_output, code_output, status_output, report_path_state, agent_log_output]
331
  )
332
-
333
  def update_dl_link(path):
334
  if path and os.path.exists(path): return f"**¡Informe listo!** 👉 [**Descargar '{os.path.basename(path)}'**](/file={path})"
335
  return "*No se generó ningún archivo para descargar.*"
 
1
  """
2
+ CÓDIGO COMPLETO Y CORREGIDO - VERSIÓN 7.8 (Agente de Instrucciones de Lenguaje Natural)
3
+ - MEJORA MAYOR: Se ha creado un nuevo `InstructionParsingAgent` que interpreta las instrucciones
4
+ en lenguaje natural del usuario desde el cuadro de "Especificaciones Adicionales".
5
+ - FUNCIONALIDAD AVANZADA: El usuario puede especificar qué métricas usar (R2, RMSE, o ambas) y
6
+ cuántos "Top N" modelos seleccionar, simplemente escribiéndolo.
7
+ - MODELSELECTIONAGENT MEJORADO: La lógica del "Plan B" ahora es dinámica y se basa en las
8
+ instrucciones parseadas, calculando un score combinado si se especifican múltiples métricas.
9
+ - UI SIMPLIFICADA: Se han eliminado los controles estáticos de ranking, reemplazados por el
10
+ cuadro de texto de instrucciones, haciendo la interfaz más limpia y potente.
11
  """
12
 
13
  import gradio as gr
 
16
  import json
17
  import tempfile
18
  import os
19
+ import re
20
  from datetime import datetime
21
  import plotly.graph_objects as go
22
  import logging
23
  import numpy as np
24
+ from smolagents import CodeAgent, InferenceClientModel
25
 
26
  # --- CONFIGURACIÓN Y CLIENTES ---
27
+ logging.basicConfig(level=logging.INFO); logger = logging.getLogger(__name__)
28
+
29
+ # --- INICIALIZACIÓN DE MODELO PARA AGENTES ---
30
+ try:
31
+ hf_engine = InferenceClientModel(model_id="deepseek-ai/DeepSeek-V2-Lite-Instruct")
32
+ logger.info("✅ Modelo de lenguaje (DeepSeek-V2-Lite) inicializado para agentes.")
33
+ except Exception: hf_engine = None; logger.error("❌ No se pudo inicializar el modelo de lenguaje para agentes.")
34
 
35
  try: biotech_client = Client("C2MV/BiotechU4"); logger.info("✅ Cliente BiotechU4 inicializado.")
36
  except: biotech_client = None
 
42
  # ============================================================================
43
 
44
  class LoggingAgent:
 
45
  def __init__(self): self.log_entries, self.start_time = [], datetime.now(); logger.info("🕵️ LoggingAgent activado.")
46
  def register(self, agent_name, action, details=""):
47
+ entry = f"**{datetime.now().strftime('%H:%M:%S')} | {agent_name}:** {action}"; self.log_entries.append(entry + (f"\n> *Detalles: {details}*" if details else ""))
 
 
48
  def get_report(self):
49
  if not self.log_entries: return "### 🕵️ Informe de Actividad\n\nNo se registraron actividades."
50
  return "### 🕵️ Informe de Actividad de Agentes\n\n---\n\n" + "\n\n---\n\n".join(self.log_entries) + f"\n\n---\n\n**Tiempo total: {(datetime.now() - self.start_time).total_seconds():.2f} s.**"
 
52
 
53
 
54
  class StructureValidationAgent:
 
55
  def __init__(self, log_agent: LoggingAgent): self.log_agent = log_agent
56
  def validate(self, file_obj):
57
  try:
 
63
  return True, "Formato de archivo básico validado."
64
 
65
 
66
+ class InstructionParsingAgent:
67
+ """Agente que convierte el lenguaje natural del usuario en un plan de acción estructurado."""
68
+ def __init__(self, log_agent: LoggingAgent, llm_engine):
69
+ self.log_agent = log_agent
70
+ self.agent = CodeAgent(tools=[], model=llm_engine) if llm_engine else None
71
+
72
+ def parse(self, text: str):
73
+ default_instructions = {'metrics': ['R2'], 'top_n': 3}
74
+ if not self.agent:
75
+ self.log_agent.register("InstructionParsingAgent", "LLM no disponible, usando defaults.")
76
+ return default_instructions
77
+
78
+ prompt = f"""
79
+ Analyze the user's instruction for a data analysis task. Extract the metrics they want to use for ranking and the number of top models to select.
80
+ The possible metrics are "R2", "RMSE".
81
+ Your output MUST be ONLY a valid JSON object with two keys: "metrics" (a list of strings) and "top_n" (an integer).
82
+
83
+ - If the user mentions "R2" or "R-cuadrado", include "R2" in the metrics list.
84
+ - If the user mentions "RMSE", include "RMSE" in the metrics list.
85
+ - If the user mentions a number like "top 3", "los 2 mejores", or just a digit, set "top_n" to that number.
86
+ - If no metrics are mentioned, default to ["R2"].
87
+ - If no number is mentioned, default to 3.
88
+
89
+ Example 1: "Usa el promedio de R2 y RMSE y elige el top 2" -> {{"metrics": ["R2", "RMSE"], "top_n": 2}}
90
+ Example 2: "dame los 3 mejores modelos segun el menor RMSE" -> {{"metrics": ["RMSE"], "top_n": 3}}
91
+ Example 3: "el mejor R2" -> {{"metrics": ["R2"], "top_n": 1}}
92
+ Example 4: "analizar los datos" -> {{"metrics": ["R2"], "top_n": 3}}
93
+
94
+ User instruction: "{text}"
95
+ JSON Output:
96
+ """
97
+ try:
98
+ response_str = self.agent.run(prompt)
99
+ json_str = response_str[response_str.find('{'):response_str.rfind('}')+1]
100
+ instructions = json.loads(json_str)
101
+ # Validar que el formato es correcto
102
+ if 'metrics' not in instructions or 'top_n' not in instructions:
103
+ raise ValueError("JSON de salida no contiene las claves esperadas.")
104
+ self.log_agent.register("InstructionParsingAgent", "Instrucciones del usuario parseadas con éxito.")
105
+ return instructions
106
+ except Exception as e:
107
+ self.log_agent.register("InstructionParsingAgent", "Error parseando instrucciones, usando defaults.", f"Error: {e}")
108
+ return default_instructions
109
+
110
+
111
  class ModelSelectionAgent:
112
  """Agente 3 (CON FALLBACK CONFIGURABLE): Identifica los mejores modelos, con un plan B personalizable."""
113
  def __init__(self, log_agent: LoggingAgent): self.log_agent = log_agent
 
118
  if str(col).lower() == name.lower(): return col
119
  return None
120
 
121
+ def identify_best_models(self, results_df, component, r2_threshold, rmse_threshold, instructions: dict):
122
  self.log_agent.register("ModelSelectionAgent", f"Iniciando identificación para: '{component}'.")
123
 
124
+ # 1. Normalizar columna del Modelo
125
  model_col = self._find_column(results_df.columns, ['model', 'modelo'])
126
  if not model_col: return [], "Error: No se encontró la columna de nombres de modelos ('Model')."
127
  df_norm = results_df.rename(columns={model_col: 'Model'})
128
 
129
+ # 2. Identificar columnas de métricas
130
  r2_target_col, rmse_target_col = None, None
131
  if component != 'all':
132
  r2_target_col = self._find_column(df_norm.columns, [f'r2_{component}'])
 
146
  model_performance = df_norm.groupby('Model').agg({r2_target_col: 'mean', rmse_target_col: 'mean'}).reset_index()
147
 
148
  # 4. Intento 1: Filtrado Estricto
149
+ good_models_df = model_performance[(model_performance[r2_target_col] >= r2_threshold) & (model_performance[rmse_target_col] <= rmse_threshold)]
 
 
 
150
 
151
  if not good_models_df.empty:
152
  best_models_list = sorted([str(model).lower() for model in good_models_df['Model'].tolist()])
153
  reasoning = f"Agente identificó **{len(best_models_list)}** modelo(s) que cumplen tus criterios: `{', '.join(best_models_list)}`."
 
154
  return best_models_list, reasoning
155
  else:
156
+ # 5. Intento 2: Plan B - Ranking Estratégico basado en instrucciones
157
+ self.log_agent.register("ModelSelectionAgent", "Filtro primario falló. Activando fallback: 'Ranking por Instrucciones'.", f"Plan: {instructions}")
158
 
159
+ use_r2 = 'R2' in instructions['metrics']
160
+ use_rmse = 'RMSE' in instructions['metrics']
161
+ top_n = instructions['top_n']
162
+
163
+ # Calcular el score de rendimiento
164
+ if use_r2 and use_rmse:
165
+ model_performance['Score'] = model_performance[r2_target_col] / (model_performance[rmse_target_col] + 1e-9)
166
+ sort_col, ascending, metric_name = 'Score', False, "R²/RMSE combinado"
167
+ elif use_rmse:
168
+ sort_col, ascending, metric_name = rmse_target_col, True, "RMSE"
169
+ else: # Por defecto R2
170
+ sort_col, ascending, metric_name = r2_target_col, False, "R²"
171
+
172
+ sorted_performance = model_performance.sort_values(by=sort_col, ascending=ascending)
173
  top_n_df = sorted_performance.head(top_n)
 
 
 
174
 
175
  best_models_list = sorted([str(model).lower() for model in top_n_df['Model'].tolist()])
176
  reasoning = (f"**Advertencia:** Ningún modelo cumplió con los criterios iniciales.\n\n"
177
  f"Como plan B, el agente ha seleccionado los **Top {len(best_models_list)}** modelos con el mejor **{metric_name} promedio**: `{', '.join(best_models_list)}`.")
 
178
  return best_models_list, reasoning
179
 
180
  # --- INICIALIZACIÓN DE AGENTES GLOBALES ---
181
+ log_agent = LoggingAgent(); validation_agent = StructureValidationAgent(log_agent)
182
+ instruction_parser_agent = InstructionParsingAgent(log_agent, hf_engine)
183
+ model_selection_agent = ModelSelectionAgent(log_agent)
184
 
185
+ # --- FUNCIONES DEL PIPELINE ---
186
+ def create_dummy_plot(title="Esperando resultados..."):
187
+ fig = go.Figure(go.Scatter(x=[], y=[])); fig.update_layout(title=title, template="plotly_white", height=500, annotations=[dict(text="Sube un archivo y ejecuta", showarrow=False)])
188
  return fig
189
 
190
  def detect_experiments(file_obj):
 
195
  return gr.update(choices=exp_names, value=exp_names, interactive=True)
196
  except Exception as e: return gr.update(choices=[], value=[], interactive=False, placeholder=f"Error: {e}")
197
 
198
+ # ... (ETAPA 1: run_base_analysis - sin cambios) ...
199
  def run_base_analysis(file, models, exp_names_selected, component, use_de, maxfev, progress=gr.Progress()):
200
  log_agent.clear(); progress(0, desc="🚀 Iniciando Análisis Base...")
201
  if not file or not models or not exp_names_selected:
202
  return create_dummy_plot(), None, "❌ Por favor, sube un archivo y selecciona modelos/experimentos.", gr.update(interactive=False), {}, None, None, log_agent.get_report()
 
203
  log_agent.register("Pipeline (Etapa 1)", "Iniciando Análisis Base."); progress(0.2, desc="Validando archivo...")
204
  is_valid, msg = validation_agent.validate(file)
205
  if not is_valid: return create_dummy_plot(), None, msg, gr.update(interactive=False), {}, None, None, log_agent.get_report()
 
206
  progress(0.5, desc="Ejecutando análisis biotecnológico...");
207
  if not biotech_client: return create_dummy_plot(), None, "❌ Cliente BiotechU4 no disponible.", gr.update(interactive=False), {}, None, None, log_agent.get_report()
 
208
  try:
209
+ exp_names_str = ",".join(exp_names_selected); models_lower = [str(m).lower() for m in models]
 
210
  plot_info, df_data, status = biotech_client.predict(file=handle_file(file.name), models=models_lower, component=component, use_de=use_de, maxfev=maxfev, exp_names=exp_names_str, api_name="/run_analysis_wrapper")
211
  if "Error" in status: raise Exception(status)
212
  except Exception as e:
213
  return create_dummy_plot(), None, f"❌ Error en Análisis Base: {e}", gr.update(interactive=False), {}, None, None, log_agent.get_report()
 
214
  progress(1, desc="🎉 Análisis Base Completado")
215
+ final_status = "✅ Análisis Base completado. \n➡️ Ahora puedes aplicar el filtro de IA y generar el informe final."
216
  results_df_obj = {'data': df_data['data'], 'headers': df_data['headers']}
217
  fig = go.Figure(json.loads(plot_info['plot'])) if plot_info and 'plot' in plot_info else create_dummy_plot()
218
+ original_params = {'exp_names': exp_names_selected, 'component': component, 'use_de': use_de, 'maxfev': maxfev}
 
219
  return fig, df_data, final_status, gr.update(interactive=True), results_df_obj, file.name, original_params, log_agent.get_report()
220
 
221
+ # --- ETAPA 2: REFINAMIENTO Y REPORTE IA (ACTUALIZADA) ---
222
+ def refine_and_generate_report(baseline_results, file_path, original_params, r2_threshold, rmse_threshold, instructions_text, ia_model, detail_level, language, max_output_tokens, use_personal_key, personal_api_key, progress=gr.Progress()):
223
+ progress(0, desc="🚀 Iniciando Refinamiento con IA..."); log_agent.register("Pipeline (Etapa 2)", "Iniciando Refinamiento.")
224
  if not baseline_results or not file_path or not original_params:
225
  return gr.update(), None, None, None, "❌ No hay resultados base para refinar.", None, log_agent.get_report()
226
 
227
+ progress(0.1, desc="Agente de Parseo interpretando instrucciones...")
228
+ instructions = instruction_parser_agent.parse(instructions_text)
229
+ log_agent.register("InstructionParsingAgent", "Instrucciones interpretadas.", f"Plan: {instructions}")
230
+
231
+ progress(0.2, desc="Agente de Selección identificando mejores modelos...")
232
  results_df = pd.DataFrame(baseline_results['data'], columns=baseline_results['headers'])
233
+ best_models, reasoning = model_selection_agent.identify_best_models(results_df, original_params['component'], r2_threshold, rmse_threshold, instructions)
234
 
235
  if not best_models:
236
  return gr.update(), baseline_results, None, None, f"🤖 Análisis del Agente:\n{reasoning}", None, log_agent.get_report()
237
 
238
+ progress(0.4, desc="Re-ejecutando análisis con los mejores modelos...");
239
  try:
240
  exp_names_str = ",".join(original_params['exp_names'])
241
+ final_plot_info, final_df_data, final_status = biotech_client.predict(file=handle_file(file_path), models=best_models, component=original_params['component'], use_de=original_params['use_de'], maxfev=original_params['maxfev'], exp_names=exp_names_str, api_name="/run_analysis_wrapper")
 
 
 
242
  if "Error" in final_status: raise Exception(final_status)
 
243
  except Exception as e:
244
  return gr.update(), None, None, None, f"❌ Error en el re-análisis final: {e}", None, log_agent.get_report()
245
 
246
+ progress(0.6, desc="Generando informe IA..."); temp_csv_file = None
247
  try:
248
  final_results_df = pd.DataFrame(final_df_data['data'], columns=final_df_data['headers'])
249
  with tempfile.NamedTemporaryFile(mode='w+', suffix='.csv', delete=False, encoding='utf-8') as temp_f:
250
  final_results_df.to_csv(temp_f.name, index=False); temp_csv_file = temp_f.name
 
251
  current_analysis_client = analysis_client
252
  if use_personal_key and personal_api_key: current_analysis_client = Client("C2MV/Project-HF-2025-2", hf_token=personal_api_key)
253
  chunk_update_dict = current_analysis_client.predict(files=[handle_file(temp_csv_file)], api_name="/update_chunk_column_selector")
254
  selected_chunk_column = chunk_update_dict['choices'][0][0]
255
+ result = current_analysis_client.predict(files=[handle_file(temp_csv_file)], chunk_column=selected_chunk_column, qwen_model=ia_model, detail_level=detail_level, language=language, additional_specs="", max_output_tokens=max_output_tokens, api_name="/process_files_and_analyze")
256
  _, analysis_report, implementation_code, token_usage = result
257
  except Exception as e:
258
  return gr.update(), final_df_data, None, None, f"❌ Error generando informe IA: {e}", None, log_agent.get_report()
 
267
 
268
  final_status = f"✅ Refinamiento y reporte completados.\n{reasoning}\nInforme IA generado con {token_usage}."
269
  final_fig = go.Figure(json.loads(final_plot_info['plot'])) if final_plot_info and 'plot' in final_plot_info else create_dummy_plot()
 
270
  return final_fig, final_df_data, analysis_report, implementation_code, final_status, final_report_path, log_agent.get_report()
271
 
272
+ # ... (create_dummy_excel_file y constantes sin cambios) ...
273
  def create_dummy_excel_file():
274
  examples_dir = "examples"; os.makedirs(examples_dir, exist_ok=True); file_path = os.path.join(examples_dir, "archivo.xlsx")
275
  if not os.path.exists(file_path):
 
290
  if __name__ == "__main__":
291
  create_dummy_excel_file()
292
  with gr.Blocks(theme=theme, title="BioTech Analysis & Report Generator") as demo:
293
+ gr.Markdown("# 🧬 BioTech Analysis & Report Generator v7.8")
294
  gr.Markdown("### Un pipeline inteligente de dos etapas: Análisis Base y Refinamiento con IA.")
295
  baseline_results_state = gr.State(value=None); file_path_state = gr.State(value=None); original_params_state = gr.State(value=None)
 
296
  with gr.Row():
297
  with gr.Column(scale=1):
298
  gr.Markdown("### 1. Carga y Configuración del Análisis Base")
299
+ file_input = gr.File(label="📁 Archivo de Datos", file_types=[".xlsx", ".xls"]); gr.Examples(examples=["examples/archivo.xlsx"], inputs=[file_input])
300
+ exp_names_input = gr.CheckboxGroup(label="🔬 Experimentos a Analizar", interactive=False)
301
+ models_input = gr.CheckboxGroup(choices=BIOTECH_MODELS, value=BIOTECH_MODELS, label="📊 Modelos a Evaluar")
 
302
  component_input = gr.Dropdown(['all', 'biomass', 'substrate', 'product'], value='all', label="📈 Componente a Analizar/Filtrar")
303
+ with gr.Accordion("Parámetros Avanzados", open=False):
304
  use_de_input = gr.Checkbox(label="🧮 Usar Evolución Diferencial", value=False)
305
  maxfev_input = gr.Slider(label="🔄 Máx. Iteraciones", minimum=10000, maximum=100000, value=50000, step=1000)
306
  run_base_analysis_btn = gr.Button("1. Ejecutar Análisis Base", variant="secondary")
307
 
308
  with gr.Group():
309
  gr.Markdown("### 2. Refinamiento con IA")
310
+ gr.Markdown("#### Criterios de Selección Primarios (Filtro Estricto)")
311
+ r2_threshold_slider = gr.Slider(minimum=0.0, maximum=0.99, value=0.9, step=0.01, label="R² Mínimo")
312
+ rmse_threshold_input = gr.Number(value=0.5, label="RMSE Máximo")
313
+
314
+ # --- NUEVO INPUT DE INSTRUCCIONES ---
315
+ additional_specs_input = gr.Textbox(label="📝 Instrucciones para Selección Avanzada (Plan B)",
316
+ placeholder="Ej: Usa R2 y RMSE y dame el top 2",
317
+ info="Si ningún modelo cumple los criterios de arriba, el agente seguirá estas instrucciones.")
318
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  with gr.Accordion("Parámetros del Informe de IA Final", open=False):
320
  ia_model_input = gr.Dropdown(choices=IA_MODELS, value=IA_MODELS[0], label="🤖 Modelo de IA para Informe")
321
  detail_level_input = gr.Radio(['detailed', 'summarized'], value='detailed', label="📋 Nivel de Detalle")
322
  language_input = gr.Dropdown(['es', 'en'], value='es', label="🌐 Idioma")
323
+ max_output_tokens_input = gr.Slider(minimum=1000, maximum=32000, value=8000, step=100, label="🔢 Máx. Tokens")
 
324
  use_personal_key_input = gr.Checkbox(label="Usar Token HF Personal", value=False)
325
  personal_api_key_input = gr.Textbox(label="Token HF", type="password", visible=False)
326
  refine_with_ia_btn = gr.Button("2. 🤖 Aplicar Filtro y Generar Informe IA", variant="primary", interactive=False)
 
327
  with gr.Column(scale=2):
328
  gr.Markdown("### 3. Resultados")
329
+ status_output = gr.Textbox(label="📊 Registro de Estado", lines=5, interactive=False)
330
  with gr.Tabs():
331
  with gr.TabItem("📊 Visualización"): plot_output = gr.Plot()
332
  with gr.TabItem("📋 Tabla de Modelado"): table_output = gr.Dataframe()
333
+ with gr.TabItem("📝 Informe IA"): analysis_output = gr.Markdown("El informe aparecerá aquí.")
334
  with gr.TabItem("💻 Código"): code_output = gr.Code(language="python")
335
+ with gr.TabItem("🕵️ Registro de Agentes"): agent_log_output = gr.Markdown()
336
+ download_link_markdown = gr.Markdown("*El enlace de descarga aparecerá aquí.*")
337
+ report_output = gr.File(label="📥 Descargar Informe", interactive=False)
338
  report_path_state = gr.State(value=None)
339
 
 
340
  file_input.upload(fn=detect_experiments, inputs=file_input, outputs=exp_names_input)
341
  use_personal_key_input.change(lambda x: gr.update(visible=x), inputs=use_personal_key_input, outputs=personal_api_key_input)
 
342
  run_base_analysis_btn.click(
343
  fn=run_base_analysis,
344
  inputs=[file_input, models_input, exp_names_input, component_input, use_de_input, maxfev_input],
345
  outputs=[plot_output, table_output, status_output, refine_with_ia_btn, baseline_results_state, file_path_state, original_params_state, agent_log_output]
346
  )
 
347
  refine_with_ia_btn.click(
348
  fn=refine_and_generate_report,
349
+ inputs=[baseline_results_state, file_path_state, original_params_state, r2_threshold_slider, rmse_threshold_input, additional_specs_input, ia_model_input, detail_level_input, language_input, max_output_tokens_input, use_personal_key_input, personal_api_key_input],
350
  outputs=[plot_output, table_output, analysis_output, code_output, status_output, report_path_state, agent_log_output]
351
  )
 
352
  def update_dl_link(path):
353
  if path and os.path.exists(path): return f"**¡Informe listo!** 👉 [**Descargar '{os.path.basename(path)}'**](/file={path})"
354
  return "*No se generó ningún archivo para descargar.*"