C2MV commited on
Commit
227fa34
verified
1 Parent(s): 0593383

Upload 3 files

Browse files
Files changed (3) hide show
  1. UI.py +55 -40
  2. app.py +3 -16
  3. model_app.py +53 -50
UI.py CHANGED
@@ -1,16 +1,16 @@
1
  # UI.py
2
  import gradio as gr
3
- # La importaci贸n de process_and_plot se maneja por modal_app.py
4
- # from interface import process_and_plot
5
 
6
- def create_interface():
 
 
7
  """
8
- Esta funci贸n crea la interfaz de usuario y la devuelve para que pueda ser lanzada
9
- desde app.py utilizando demo.launch().
10
  """
11
 
12
- with gr.Blocks(theme='upsatwal/mlsc_tiet') as demo: # Puede que necesites un theme diferente o default
13
- # with gr.Blocks(theme=gr.themes.Soft()) as demo: # Alternativa m谩s com煤n
14
  gr.Markdown("# Modelado de Bioprocesos con Ecuaciones Personalizadas y An谩lisis por IA")
15
 
16
  with gr.Row():
@@ -31,73 +31,73 @@ def create_interface():
31
  substrate_eq_count_ui = gr.Number(label="N煤mero de ecuaciones de Sustrato a probar (1-3)", value=1, minimum=1, maximum=3, precision=0, step=1)
32
  product_eq_count_ui = gr.Number(label="N煤mero de ecuaciones de Producto a probar (1-3)", value=1, minimum=1, maximum=3, precision=0, step=1)
33
 
34
-
35
- # --- Secci贸n de Biomasa ---
36
  with gr.Accordion("Ecuaciones y Par谩metros de Biomasa", open=True):
37
  with gr.Row():
38
  with gr.Column():
39
- biomass_eq1_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 1 (ej: Xm * (1 - exp(-um * t)))", value="Xm * (1 - exp(-um * t))", lines=2) # Modelo de Moser simplificado
40
- biomass_param1_ui = gr.Textbox(label="Par谩metros Biomasa 1 (coma sep., ej: Xm, um, Ks)", value="Xm, um, t_lag", info="Use 't' para tiempo. Use 'Ks' o 't_lag' para fases de retardo si es necesario.")
41
- biomass_bound1_ui = gr.Textbox(label="L铆mites Biomasa 1 (ej: (0,inf),(0,5),(-10,10))", value="(0, inf), (0, inf), (0, inf)", info="Formato: (low,high) para cada param. Use np.inf.")
42
- with gr.Column(visible=False) as biomass_col2: # Inicialmente oculto
43
- biomass_eq2_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 2", value="X0 * exp(um * t)", lines=2) # Crecimiento exponencial simple
 
44
  biomass_param2_ui = gr.Textbox(label="Par谩metros Biomasa 2", value="X0, um")
45
  biomass_bound2_ui = gr.Textbox(label="L铆mites Biomasa 2", value="(0, inf), (0, inf)")
46
- with gr.Column(visible=False) as biomass_col3: # Inicialmente oculto
 
47
  biomass_eq3_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 3", lines=2)
48
  biomass_param3_ui = gr.Textbox(label="Par谩metros Biomasa 3")
49
  biomass_bound3_ui = gr.Textbox(label="L铆mites Biomasa 3")
50
 
51
- # --- Secci贸n de Sustrato ---
52
  with gr.Accordion("Ecuaciones y Par谩metros de Sustrato", open=True):
53
- gr.Markdown("Para ecuaciones de Sustrato y Producto, usa `X_val` para representar la concentraci贸n de biomasa calculada X(t). Ejemplo: `S0 - (1/YXS) * (X_val - X0_biomass)`")
54
  with gr.Row():
55
  with gr.Column():
56
- substrate_eq1_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 1", value="S0 - (X_val / YXS) - mS * t", lines=2) # Luedeking-Piret simplificado
57
  substrate_param1_ui = gr.Textbox(label="Par谩metros Sustrato 1", value="S0, YXS, mS")
58
  substrate_bound1_ui = gr.Textbox(label="L铆mites Sustrato 1", value="(0, inf), (0.01, inf), (0, inf)")
59
- with gr.Column(visible=False) as substrate_col2:
 
60
  substrate_eq2_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 2", lines=2)
61
  substrate_param2_ui = gr.Textbox(label="Par谩metros Sustrato 2")
62
  substrate_bound2_ui = gr.Textbox(label="L铆mites Sustrato 2")
63
- with gr.Column(visible=False) as substrate_col3:
 
64
  substrate_eq3_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 3", lines=2)
65
  substrate_param3_ui = gr.Textbox(label="Par谩metros Sustrato 3")
66
  substrate_bound3_ui = gr.Textbox(label="L铆mites Sustrato 3")
67
 
68
- # --- Secci贸n de Producto ---
69
  with gr.Accordion("Ecuaciones y Par谩metros de Producto", open=True):
70
  with gr.Row():
71
  with gr.Column():
72
- product_eq1_ui = gr.Textbox(label="Ecuaci贸n de Producto 1", value="P0 + YPX * X_val + mP * t", lines=2) # Luedeking-Piret simplificado
73
  product_param1_ui = gr.Textbox(label="Par谩metros Producto 1", value="P0, YPX, mP")
74
  product_bound1_ui = gr.Textbox(label="L铆mites Producto 1", value="(0, inf), (0, inf), (0, inf)")
75
- with gr.Column(visible=False) as product_col2:
 
76
  product_eq2_ui = gr.Textbox(label="Ecuaci贸n de Producto 2", lines=2)
77
  product_param2_ui = gr.Textbox(label="Par谩metros Producto 2")
78
  product_bound2_ui = gr.Textbox(label="L铆mites Producto 2")
79
- with gr.Column(visible=False) as product_col3:
 
80
  product_eq3_ui = gr.Textbox(label="Ecuaci贸n de Producto 3", lines=2)
81
  product_param3_ui = gr.Textbox(label="Par谩metros Producto 3")
82
  product_bound3_ui = gr.Textbox(label="L铆mites Producto 3")
83
 
84
- # L贸gica para mostrar/ocultar campos de ecuaci贸n din谩micamente
85
- def update_visibility(count, c2, c3):
86
- return gr.Column(visible=count >= 2), gr.Column(visible=count >= 3)
87
 
88
- biomass_eq_count_ui.change(fn=lambda x: update_visibility(x, biomass_col2, biomass_col3), inputs=biomass_eq_count_ui, outputs=[biomass_col2, biomass_col3])
89
- substrate_eq_count_ui.change(fn=lambda x: update_visibility(x, substrate_col2, substrate_col3), inputs=substrate_eq_count_ui, outputs=[substrate_col2, substrate_col3])
90
- product_eq_count_ui.change(fn=lambda x: update_visibility(x, product_col2, product_col3), inputs=product_eq_count_ui, outputs=[product_col2, product_col3])
91
 
92
  submit_button = gr.Button("Procesar y Analizar", variant="primary", scale=1)
93
 
94
  gr.Markdown("## Resultados del An谩lisis")
95
  with gr.Row():
96
- image_output = gr.Image(label="Gr谩fico Generado", type="pil", width=600, height=900, scale=2) # Ajusta escala si es necesario
97
- with gr.Column(scale=3): # M谩s espacio para el an谩lisis
98
- analysis_output = gr.Markdown(label="An谩lisis del Modelo por IA") # Usar Markdown para mejor formato
99
 
100
- # Lista de todos los inputs para el bot贸n de submit
101
  all_inputs = [
102
  file_input,
103
  biomass_eq1_ui, biomass_eq2_ui, biomass_eq3_ui,
@@ -116,10 +116,25 @@ def create_interface():
116
  substrate_eq_count_ui,
117
  product_eq_count_ui
118
  ]
119
- # La conexi贸n se hace en modal_app.py al inyectar la funci贸n de `interface`
120
- # El atributo `fn` del bot贸n se establecer谩 all铆.
121
- demo.load(fn=lambda: (gr.Column(visible=False), gr.Column(visible=False)), outputs=[biomass_col2, biomass_col3])
122
- demo.load(fn=lambda: (gr.Column(visible=False), gr.Column(visible=False)), outputs=[substrate_col2, substrate_col3])
123
- demo.load(fn=lambda: (gr.Column(visible=False), gr.Column(visible=False)), outputs=[product_col2, product_col3])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- return demo, all_inputs, [image_output, analysis_output], submit_button # Devolver tambi茅n inputs, outputs y bot贸n
 
1
  # UI.py
2
  import gradio as gr
 
 
3
 
4
+ # Ya no importamos process_and_plot aqu铆, se pasar谩 como argumento a create_interface
5
+
6
+ def create_interface(process_function_for_button): # <-- A脩ADIDO: process_function_for_button
7
  """
8
+ Esta funci贸n crea la interfaz de usuario y la devuelve.
9
+ Conecta el bot贸n de submit a la 'process_function_for_button' proporcionada.
10
  """
11
 
12
+ with gr.Blocks(theme='upsatwal/mlsc_tiet') as demo:
13
+ # with gr.Blocks(theme=gr.themes.Soft()) as demo:
14
  gr.Markdown("# Modelado de Bioprocesos con Ecuaciones Personalizadas y An谩lisis por IA")
15
 
16
  with gr.Row():
 
31
  substrate_eq_count_ui = gr.Number(label="N煤mero de ecuaciones de Sustrato a probar (1-3)", value=1, minimum=1, maximum=3, precision=0, step=1)
32
  product_eq_count_ui = gr.Number(label="N煤mero de ecuaciones de Producto a probar (1-3)", value=1, minimum=1, maximum=3, precision=0, step=1)
33
 
 
 
34
  with gr.Accordion("Ecuaciones y Par谩metros de Biomasa", open=True):
35
  with gr.Row():
36
  with gr.Column():
37
+ biomass_eq1_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 1 (ej: Xm * (1 - exp(-um * t)))", value="Xm * (1 - exp(-um * t_lag))", lines=2)
38
+ biomass_param1_ui = gr.Textbox(label="Par谩metros Biomasa 1 (coma sep., ej: Xm, um, t_lag)", value="Xm, um, t_lag", info="Use 't' para tiempo. X_val para S/P.")
39
+ biomass_bound1_ui = gr.Textbox(label="L铆mites Biomasa 1 (ej: (0,inf),(0,5),(0,inf))", value="(0, inf), (0, inf), (0, inf)", info="Formato: (low,high). Use np.inf.")
40
+ biomass_col2 = gr.Column(visible=False)
41
+ with biomass_col2:
42
+ biomass_eq2_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 2", value="X0 * exp(um * t)", lines=2)
43
  biomass_param2_ui = gr.Textbox(label="Par谩metros Biomasa 2", value="X0, um")
44
  biomass_bound2_ui = gr.Textbox(label="L铆mites Biomasa 2", value="(0, inf), (0, inf)")
45
+ biomass_col3 = gr.Column(visible=False)
46
+ with biomass_col3:
47
  biomass_eq3_ui = gr.Textbox(label="Ecuaci贸n de Biomasa 3", lines=2)
48
  biomass_param3_ui = gr.Textbox(label="Par谩metros Biomasa 3")
49
  biomass_bound3_ui = gr.Textbox(label="L铆mites Biomasa 3")
50
 
 
51
  with gr.Accordion("Ecuaciones y Par谩metros de Sustrato", open=True):
52
+ gr.Markdown("Para Sustrato/Producto, usa `X_val` para X(t). Ejemplo: `S0 - (1/YXS) * (X_val - X0_bio)` donde X0_bio es un par谩metro o valor.")
53
  with gr.Row():
54
  with gr.Column():
55
+ substrate_eq1_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 1", value="S0 - (X_val / YXS) - mS * t", lines=2)
56
  substrate_param1_ui = gr.Textbox(label="Par谩metros Sustrato 1", value="S0, YXS, mS")
57
  substrate_bound1_ui = gr.Textbox(label="L铆mites Sustrato 1", value="(0, inf), (0.01, inf), (0, inf)")
58
+ substrate_col2 = gr.Column(visible=False)
59
+ with substrate_col2:
60
  substrate_eq2_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 2", lines=2)
61
  substrate_param2_ui = gr.Textbox(label="Par谩metros Sustrato 2")
62
  substrate_bound2_ui = gr.Textbox(label="L铆mites Sustrato 2")
63
+ substrate_col3 = gr.Column(visible=False)
64
+ with substrate_col3:
65
  substrate_eq3_ui = gr.Textbox(label="Ecuaci贸n de Sustrato 3", lines=2)
66
  substrate_param3_ui = gr.Textbox(label="Par谩metros Sustrato 3")
67
  substrate_bound3_ui = gr.Textbox(label="L铆mites Sustrato 3")
68
 
 
69
  with gr.Accordion("Ecuaciones y Par谩metros de Producto", open=True):
70
  with gr.Row():
71
  with gr.Column():
72
+ product_eq1_ui = gr.Textbox(label="Ecuaci贸n de Producto 1", value="P0 + YPX * X_val + mP * t", lines=2)
73
  product_param1_ui = gr.Textbox(label="Par谩metros Producto 1", value="P0, YPX, mP")
74
  product_bound1_ui = gr.Textbox(label="L铆mites Producto 1", value="(0, inf), (0, inf), (0, inf)")
75
+ product_col2 = gr.Column(visible=False)
76
+ with product_col2:
77
  product_eq2_ui = gr.Textbox(label="Ecuaci贸n de Producto 2", lines=2)
78
  product_param2_ui = gr.Textbox(label="Par谩metros Producto 2")
79
  product_bound2_ui = gr.Textbox(label="L铆mites Producto 2")
80
+ product_col3 = gr.Column(visible=False)
81
+ with product_col3:
82
  product_eq3_ui = gr.Textbox(label="Ecuaci贸n de Producto 3", lines=2)
83
  product_param3_ui = gr.Textbox(label="Par谩metros Producto 3")
84
  product_bound3_ui = gr.Textbox(label="L铆mites Producto 3")
85
 
86
+ def update_visibility(count): # Simplificado, devuelve tupla de dicts
87
+ return {"visible": count >= 2}, {"visible": count >= 3}
 
88
 
89
+ biomass_eq_count_ui.change(fn=update_visibility, inputs=biomass_eq_count_ui, outputs=[biomass_col2, biomass_col3])
90
+ substrate_eq_count_ui.change(fn=update_visibility, inputs=substrate_eq_count_ui, outputs=[substrate_col2, substrate_col3])
91
+ product_eq_count_ui.change(fn=update_visibility, inputs=product_eq_count_ui, outputs=[product_col2, product_col3])
92
 
93
  submit_button = gr.Button("Procesar y Analizar", variant="primary", scale=1)
94
 
95
  gr.Markdown("## Resultados del An谩lisis")
96
  with gr.Row():
97
+ image_output = gr.Image(label="Gr谩fico Generado", type="pil", width=600, height=900, scale=2)
98
+ with gr.Column(scale=3):
99
+ analysis_output = gr.Markdown(label="An谩lisis del Modelo por IA")
100
 
 
101
  all_inputs = [
102
  file_input,
103
  biomass_eq1_ui, biomass_eq2_ui, biomass_eq3_ui,
 
116
  substrate_eq_count_ui,
117
  product_eq_count_ui
118
  ]
119
+
120
+ # --- CONEXI脫N DEL BOT脫N DENTRO DEL CONTEXTO DE BLOCKS ---
121
+ submit_button.click(
122
+ fn=process_function_for_button, # Usa la funci贸n pasada como argumento
123
+ inputs=all_inputs,
124
+ outputs=[image_output, analysis_output]
125
+ )
126
+
127
+ # Inicializar la visibilidad correctamente al cargar la demo
128
+ # No es necesario demo.load para esto si la visibilidad por defecto es False
129
+ # y la funci贸n `update_visibility` se llama con el valor inicial del Number input.
130
+ # Para asegurar que se llama al inicio:
131
+ demo.load(lambda val_b, val_s, val_p: (
132
+ update_visibility(val_b)[0], update_visibility(val_b)[1], # Para biomasa
133
+ update_visibility(val_s)[0], update_visibility(val_s)[1], # Para sustrato
134
+ update_visibility(val_p)[0], update_visibility(val_p)[1] # Para producto
135
+ ),
136
+ inputs=[biomass_eq_count_ui, substrate_eq_count_ui, product_eq_count_ui],
137
+ outputs=[biomass_col2, biomass_col3, substrate_col2, substrate_col3, product_col2, product_col3]
138
+ )
139
 
140
+ return demo
app.py CHANGED
@@ -1,23 +1,10 @@
1
  # app.py
2
- # Importar create_interface desde UI.py
3
- import os
4
-
5
- os.system("pip install --upgrade gradio")
6
  from UI import create_interface
7
- import interface as app_interface_module # Para el click handler
8
 
9
  def main():
10
- # Crear la interfaz y obtener los componentes necesarios
11
- demo, all_inputs, outputs_list, submit_button_obj = create_interface()
12
-
13
- # Conectar el bot贸n de submit a la funci贸n process_and_plot del m贸dulo interface
14
- # Esto es crucial para que la UI llame a la l贸gica correcta.
15
- submit_button_obj.click(
16
- fn=app_interface_module.process_and_plot, # La funci贸n real
17
- inputs=all_inputs, # La lista de componentes de entrada
18
- outputs=outputs_list # La lista de componentes de salida
19
- )
20
-
21
  demo.launch()
22
 
23
  if __name__ == "__main__":
 
1
  # app.py
 
 
 
 
2
  from UI import create_interface
3
+ import interface as app_interface_module # Necesitamos la funci贸n process_and_plot
4
 
5
  def main():
6
+ # Pasa la funci贸n de procesamiento real a create_interface
7
+ demo = create_interface(process_function_for_button=app_interface_module.process_and_plot)
 
 
 
 
 
 
 
 
 
8
  demo.launch()
9
 
10
  if __name__ == "__main__":
model_app.py CHANGED
@@ -2,19 +2,17 @@
2
  import modal
3
  import sys
4
  from pathlib import Path
5
- import os # Para HF_TOKEN
 
6
 
7
  # --- Configuraci贸n ---
8
  PYTHON_VERSION = "3.10"
9
  APP_NAME = "bioprocess-custom-eq-agent-modal"
10
-
11
- # Directorios (asume que modal_app.py est谩 en la ra铆z del proyecto junto a los otros .py)
12
  LOCAL_APP_DIR = Path(__file__).parent
13
- REMOTE_APP_DIR = "/app" # Directorio dentro del contenedor Modal
14
 
15
  stub = modal.Stub(APP_NAME)
16
 
17
- # Definici贸n de la imagen del contenedor Modal
18
  app_image = (
19
  modal.Image.debian_slim(python_version=PYTHON_VERSION)
20
  .pip_install_from_requirements(LOCAL_APP_DIR / "requirements.txt")
@@ -23,35 +21,30 @@ app_image = (
23
  )
24
  .env({
25
  "PYTHONPATH": REMOTE_APP_DIR,
26
- "HF_HOME": "/cache/huggingface", # Directorio de cach茅 de Hugging Face
27
  "HF_HUB_CACHE": "/cache/huggingface/hub",
28
- "TRANSFORMERS_CACHE": "/cache/huggingface/hub", # Alias com煤n
29
- "MPLCONFIGDIR": "/tmp/matplotlib_cache" # Para evitar warnings de matplotlib
30
  })
31
- .run_commands( # Comandos para ejecutar durante la construcci贸n de la imagen
32
- "apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/*", # git-lfs para algunos modelos
33
- "mkdir -p /cache/huggingface/hub /tmp/matplotlib_cache" # Crear directorios de cach茅
34
  )
35
  )
36
 
37
- # --- Funci贸n Modal para Generaci贸n de An谩lisis con LLM ---
38
  @stub.function(
39
  image=app_image,
40
- gpu="any", # Solicitar GPU (ej. "T4", "A10G", o "any")
41
- secrets=[
42
- modal.Secret.from_name("huggingface-read-token", optional=True) # Para modelos privados/gated
43
- ],
44
- timeout=600, # 10 minutos de timeout
45
- # Montar un volumen para cachear modelos de Hugging Face
46
  volumes={"/cache/huggingface": modal.Volume.persisted(f"{APP_NAME}-hf-cache-vol")}
47
  )
48
  def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_new_tokens_config: int) -> str:
49
- import torch
50
  from transformers import AutoTokenizer, AutoModelForCausalLM
51
 
52
- # El token de HF se inyecta como variable de entorno si el secreto est谩 configurado
53
  hf_token = os.environ.get("HUGGING_FACE_TOKEN")
54
-
55
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
56
  print(f"LLM Modal Func: Usando dispositivo: {device}")
57
  print(f"LLM Modal Func: Cargando modelo: {model_path_config} con token: {'S铆' if hf_token else 'No'}")
@@ -60,16 +53,19 @@ def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_
60
  tokenizer = AutoTokenizer.from_pretrained(model_path_config, cache_dir="/cache/huggingface/hub", token=hf_token)
61
  model = AutoModelForCausalLM.from_pretrained(
62
  model_path_config,
63
- torch_dtype="auto", # bfloat16 en A100/H100, float16 en otras
64
- device_map="auto", # Distribuye autom谩ticamente en GPUs disponibles
65
  cache_dir="/cache/huggingface/hub",
66
  token=hf_token,
67
- # low_cpu_mem_usage=True # Puede ayudar con modelos muy grandes
68
  )
69
- # No es necesario .to(device) expl铆citamente con device_map="auto"
70
- # model.eval() no es necesario si solo se hace inferencia y no se entrena
71
-
72
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=4096-max_new_tokens_config).to(model.device) # Truncar prompt si es muy largo
 
 
 
 
73
 
74
  with torch.no_grad():
75
  outputs = model.generate(
@@ -78,12 +74,10 @@ def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_
78
  eos_token_id=tokenizer.eos_token_id,
79
  pad_token_id=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else tokenizer.eos_token_id,
80
  do_sample=True,
81
- temperature=0.6, # Ajustar para creatividad vs factualidad
82
  top_p=0.9,
83
- # num_beams=1 # Usar num_beams > 1 para beam search si se desea, pero m谩s lento
84
  )
85
 
86
- # Decodificar solo los tokens generados nuevos, no el prompt
87
  input_length = inputs.input_ids.shape[1]
88
  generated_ids = outputs[0][input_length:]
89
  analysis = tokenizer.decode(generated_ids, skip_special_tokens=True)
@@ -96,18 +90,19 @@ def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_
96
  return f"Error al generar an谩lisis con el modelo LLM: {str(e)}"
97
 
98
  # --- Servidor Gradio ---
99
- @stub.asgi_app() # image se hereda del stub si no se especifica aqu铆
100
  def serve_gradio_app_asgi():
101
  # Estas importaciones ocurren DENTRO del contenedor Modal
102
  import gradio as gr
103
- sys.path.insert(0, REMOTE_APP_DIR) # Asegurar que los m贸dulos de la app son importables
 
 
104
 
105
- # Importar los m贸dulos de la aplicaci贸n AHORA que sys.path est谩 configurado
106
- from UI import create_interface
107
- import interface as app_interface_module # Renombrar para claridad
108
  from config import MODEL_PATH as cfg_MODEL_PATH, MAX_LENGTH as cfg_MAX_LENGTH
109
 
110
- # Wrapper para llamar a la funci贸n Modal remota desde tu interface.py
111
  def analysis_func_wrapper_for_interface(prompt: str) -> str:
112
  print("Gradio Backend: Llamando a generate_analysis_llm_modal_remote.remote...")
113
  return generate_analysis_llm_modal_remote.remote(prompt, cfg_MODEL_PATH, cfg_MAX_LENGTH)
@@ -116,23 +111,31 @@ def serve_gradio_app_asgi():
116
  app_interface_module.generate_analysis_from_modal = analysis_func_wrapper_for_interface
117
  app_interface_module.USE_MODAL_FOR_LLM_ANALYSIS = True
118
 
119
- # Crear la app Gradio y conectar el bot贸n
120
- gradio_ui, all_ui_inputs, ui_outputs, ui_submit_button = create_interface()
 
121
 
122
- ui_submit_button.click(
123
- fn=app_interface_module.process_and_plot,
124
- inputs=all_ui_inputs,
125
- outputs=ui_outputs
126
- )
127
-
128
- return gr.routes.App.create_app(gradio_ui) # Para montar Gradio en FastAPI/ASGI
129
 
130
- # (Opcional) Un entrypoint local para probar r谩pidamente la generaci贸n LLM
131
  @stub.local_entrypoint()
132
  def test_llm():
133
  print("Probando la generaci贸n de LLM con Modal (localmente)...")
 
 
 
 
 
134
  from config import MODEL_PATH, MAX_LENGTH
 
135
  sample_prompt = "Explica brevemente el concepto de R cuadrado (R虏) en el ajuste de modelos."
136
- analysis = generate_analysis_llm_modal_remote.remote(sample_prompt, MODEL_PATH, MAX_LENGTH)
137
- print("\nRespuesta del LLM:")
138
- print(analysis)
 
 
 
 
 
 
 
 
 
2
  import modal
3
  import sys
4
  from pathlib import Path
5
+ import os
6
+ import traceback # Para imprimir traceback detallado
7
 
8
  # --- Configuraci贸n ---
9
  PYTHON_VERSION = "3.10"
10
  APP_NAME = "bioprocess-custom-eq-agent-modal"
 
 
11
  LOCAL_APP_DIR = Path(__file__).parent
12
+ REMOTE_APP_DIR = "/app"
13
 
14
  stub = modal.Stub(APP_NAME)
15
 
 
16
  app_image = (
17
  modal.Image.debian_slim(python_version=PYTHON_VERSION)
18
  .pip_install_from_requirements(LOCAL_APP_DIR / "requirements.txt")
 
21
  )
22
  .env({
23
  "PYTHONPATH": REMOTE_APP_DIR,
24
+ "HF_HOME": "/cache/huggingface",
25
  "HF_HUB_CACHE": "/cache/huggingface/hub",
26
+ "TRANSFORMERS_CACHE": "/cache/huggingface/hub",
27
+ "MPLCONFIGDIR": "/tmp/matplotlib_cache"
28
  })
29
+ .run_commands(
30
+ "apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/*",
31
+ "mkdir -p /cache/huggingface/hub /tmp/matplotlib_cache"
32
  )
33
  )
34
 
35
+ # --- Funci贸n Modal para LLM (sin cambios respecto a la anterior respuesta) ---
36
  @stub.function(
37
  image=app_image,
38
+ gpu="any",
39
+ secrets=[modal.Secret.from_name("huggingface-read-token", optional=True)],
40
+ timeout=600,
 
 
 
41
  volumes={"/cache/huggingface": modal.Volume.persisted(f"{APP_NAME}-hf-cache-vol")}
42
  )
43
  def generate_analysis_llm_modal_remote(prompt: str, model_path_config: str, max_new_tokens_config: int) -> str:
44
+ import torch # Mover importaciones pesadas dentro de la funci贸n Modal
45
  from transformers import AutoTokenizer, AutoModelForCausalLM
46
 
 
47
  hf_token = os.environ.get("HUGGING_FACE_TOKEN")
 
48
  device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
49
  print(f"LLM Modal Func: Usando dispositivo: {device}")
50
  print(f"LLM Modal Func: Cargando modelo: {model_path_config} con token: {'S铆' if hf_token else 'No'}")
 
53
  tokenizer = AutoTokenizer.from_pretrained(model_path_config, cache_dir="/cache/huggingface/hub", token=hf_token)
54
  model = AutoModelForCausalLM.from_pretrained(
55
  model_path_config,
56
+ torch_dtype="auto",
57
+ device_map="auto",
58
  cache_dir="/cache/huggingface/hub",
59
  token=hf_token,
 
60
  )
61
+
62
+ # Ajustar longitud de truncamiento para el prompt para dejar espacio a max_new_tokens
63
+ # La longitud total (prompt + generado) no debe exceder el context window del modelo.
64
+ # Asumamos un context window conservador de 4096 si no se conoce.
65
+ model_context_window = getattr(model.config, 'max_position_embeddings', 4096)
66
+ max_prompt_len = model_context_window - max_new_tokens_config - 50 # 50 tokens de buffer
67
+
68
+ inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=max_prompt_len).to(model.device)
69
 
70
  with torch.no_grad():
71
  outputs = model.generate(
 
74
  eos_token_id=tokenizer.eos_token_id,
75
  pad_token_id=tokenizer.pad_token_id if tokenizer.pad_token_id is not None else tokenizer.eos_token_id,
76
  do_sample=True,
77
+ temperature=0.6,
78
  top_p=0.9,
 
79
  )
80
 
 
81
  input_length = inputs.input_ids.shape[1]
82
  generated_ids = outputs[0][input_length:]
83
  analysis = tokenizer.decode(generated_ids, skip_special_tokens=True)
 
90
  return f"Error al generar an谩lisis con el modelo LLM: {str(e)}"
91
 
92
  # --- Servidor Gradio ---
93
+ @stub.asgi_app()
94
  def serve_gradio_app_asgi():
95
  # Estas importaciones ocurren DENTRO del contenedor Modal
96
  import gradio as gr
97
+ # sys.path ya est谩 configurado por la imagen, pero por si acaso:
98
+ if REMOTE_APP_DIR not in sys.path:
99
+ sys.path.insert(0, REMOTE_APP_DIR)
100
 
101
+ from UI import create_interface # De tu UI.py
102
+ import interface as app_interface_module # El m贸dulo interface.py
 
103
  from config import MODEL_PATH as cfg_MODEL_PATH, MAX_LENGTH as cfg_MAX_LENGTH
104
 
105
+ # Wrapper para llamar a la funci贸n Modal remota
106
  def analysis_func_wrapper_for_interface(prompt: str) -> str:
107
  print("Gradio Backend: Llamando a generate_analysis_llm_modal_remote.remote...")
108
  return generate_analysis_llm_modal_remote.remote(prompt, cfg_MODEL_PATH, cfg_MAX_LENGTH)
 
111
  app_interface_module.generate_analysis_from_modal = analysis_func_wrapper_for_interface
112
  app_interface_module.USE_MODAL_FOR_LLM_ANALYSIS = True
113
 
114
+ # Crear la app Gradio, pas谩ndole la funci贸n de procesamiento que ahora est谩 en app_interface_module
115
+ # create_interface ahora toma la funci贸n de callback como argumento.
116
+ gradio_ui = create_interface(process_function_for_button=app_interface_module.process_and_plot)
117
 
118
+ return gr.routes.App.create_app(gradio_ui)
 
 
 
 
 
 
119
 
 
120
  @stub.local_entrypoint()
121
  def test_llm():
122
  print("Probando la generaci贸n de LLM con Modal (localmente)...")
123
+ # Necesitas importar config para que MODEL_PATH y MAX_LENGTH est茅n definidos
124
+ # Esto debe hacerse dentro de un contexto donde los m贸dulos de la app sean accesibles.
125
+ # Es mejor llamar a la funci贸n stub desde aqu铆.
126
+ if REMOTE_APP_DIR not in sys.path: # Asegurar path para pruebas locales tambi茅n
127
+ sys.path.insert(0, str(LOCAL_APP_DIR))
128
  from config import MODEL_PATH, MAX_LENGTH
129
+
130
  sample_prompt = "Explica brevemente el concepto de R cuadrado (R虏) en el ajuste de modelos."
131
+ # Ejecuta la funci贸n Modal directamente (no .remote() para prueba local de l贸gica)
132
+ # Para probar la ejecuci贸n remota real, necesitar铆as `modal run modal_app.py test_llm`
133
+ # o que test_llm llame a .remote().
134
+ # Aqu铆 vamos a probar la llamada remota.
135
+ try:
136
+ analysis = generate_analysis_llm_modal_remote.remote(sample_prompt, MODEL_PATH, MAX_LENGTH)
137
+ print("\nRespuesta del LLM:")
138
+ print(analysis)
139
+ except Exception as e:
140
+ print(f"Error durante test_llm: {e}")
141
+ traceback.print_exc()