Upload 3 files
Browse files- UI.py +55 -40
- app.py +3 -16
- 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 |
-
|
|
|
|
|
7 |
"""
|
8 |
-
Esta funci贸n crea la interfaz de usuario y la devuelve
|
9 |
-
|
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,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 *
|
40 |
-
biomass_param1_ui = gr.Textbox(label="Par谩metros Biomasa 1 (coma sep., ej: Xm, um,
|
41 |
-
biomass_bound1_ui = gr.Textbox(label="L铆mites Biomasa 1 (ej: (0,inf),(0,5),(
|
42 |
-
|
43 |
-
|
|
|
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 |
-
|
|
|
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
|
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)
|
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 |
-
|
|
|
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 |
-
|
|
|
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)
|
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 |
-
|
|
|
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 |
-
|
|
|
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 |
-
#
|
85 |
-
|
86 |
-
return gr.Column(visible=count >= 2), gr.Column(visible=count >= 3)
|
87 |
|
88 |
-
biomass_eq_count_ui.change(fn=
|
89 |
-
substrate_eq_count_ui.change(fn=
|
90 |
-
product_eq_count_ui.change(fn=
|
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)
|
97 |
-
with gr.Column(scale=3):
|
98 |
-
analysis_output = gr.Markdown(label="An谩lisis del Modelo por IA")
|
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 |
-
|
120 |
-
#
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
|
125 |
-
return demo
|
|
|
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 #
|
8 |
|
9 |
def main():
|
10 |
-
#
|
11 |
-
demo
|
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
|
|
|
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"
|
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",
|
27 |
"HF_HUB_CACHE": "/cache/huggingface/hub",
|
28 |
-
"TRANSFORMERS_CACHE": "/cache/huggingface/hub",
|
29 |
-
"MPLCONFIGDIR": "/tmp/matplotlib_cache"
|
30 |
})
|
31 |
-
.run_commands(
|
32 |
-
"apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/lists/*",
|
33 |
-
"mkdir -p /cache/huggingface/hub /tmp/matplotlib_cache"
|
34 |
)
|
35 |
)
|
36 |
|
37 |
-
# --- Funci贸n Modal para
|
38 |
@stub.function(
|
39 |
image=app_image,
|
40 |
-
gpu="any",
|
41 |
-
secrets=[
|
42 |
-
|
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",
|
64 |
-
device_map="auto",
|
65 |
cache_dir="/cache/huggingface/hub",
|
66 |
token=hf_token,
|
67 |
-
# low_cpu_mem_usage=True # Puede ayudar con modelos muy grandes
|
68 |
)
|
69 |
-
|
70 |
-
#
|
71 |
-
|
72 |
-
|
|
|
|
|
|
|
|
|
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,
|
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()
|
100 |
def serve_gradio_app_asgi():
|
101 |
# Estas importaciones ocurren DENTRO del contenedor Modal
|
102 |
import gradio as gr
|
103 |
-
sys.path
|
|
|
|
|
104 |
|
105 |
-
|
106 |
-
|
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
|
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
|
120 |
-
|
|
|
121 |
|
122 |
-
|
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 |
-
|
137 |
-
|
138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|