ivanoctaviogaitansantos commited on
Commit
b8c7f93
·
verified ·
1 Parent(s): 52d89b5

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +264 -0
app.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import base64
4
+ import io
5
+ import json
6
+ import re
7
+ from PIL import Image
8
+ from sambanova import SambaNova
9
+ from contextlib import redirect_stdout
10
+
11
+ # =========================
12
+ # CONFIGURACIÓN
13
+ # =========================
14
+ API_KEY = os.getenv("SAMBANOVA_API_KEY")
15
+
16
+ if not API_KEY:
17
+ raise ValueError("❌ Error: Configura la variable SAMBANOVA_API_KEY")
18
+
19
+ client = SambaNova(
20
+ api_key=API_KEY,
21
+ base_url="https://api.sambanova.ai/v1",
22
+ )
23
+
24
+ # =========================
25
+ # MODELOS
26
+ # =========================
27
+ MODELS = {
28
+ "vision": "Llama-4-Maverick-17B-128E-Instruct",
29
+ "code": "DeepSeek-R1-Distill-Llama-70B",
30
+ "general_precise": "gpt-oss-120b",
31
+ "general_creative": "Qwen3-32B",
32
+ }
33
+
34
+ # =========================
35
+ # CLASIFICACIÓN LOCAL
36
+ # =========================
37
+ def classify_task_local(message, has_image):
38
+ if has_image:
39
+ return "vision"
40
+
41
+ msg = message.lower().strip()
42
+ if re.search(r'\b(imagen|foto|describe|ver|colores|visual|ocr|objeto)\b', msg):
43
+ return "vision"
44
+ if re.search(r'\b(código|python|js|java|debug|función|error|clase|algoritmo)\b', msg):
45
+ return "code"
46
+ if re.search(r'\b(historia|cuento|poema|escribe|creativo|inventa|relato|personaje)\b', msg):
47
+ return "general_creative"
48
+ return "general_precise"
49
+
50
+ # =========================
51
+ # HERRAMIENTAS
52
+ # =========================
53
+ TOOLS = [
54
+ {
55
+ "type": "function",
56
+ "function": {
57
+ "name": "execute_python",
58
+ "description": "Ejecuta código Python en sandbox seguro.",
59
+ "parameters": {
60
+ "type": "object",
61
+ "properties": {"code": {"type": "string"}},
62
+ "required": ["code"]
63
+ }
64
+ }
65
+ }
66
+ ]
67
+
68
+ def execute_tool(tool_call):
69
+ name = tool_call.function.name
70
+ try:
71
+ args = json.loads(tool_call.function.arguments)
72
+ except json.JSONDecodeError:
73
+ return "❌ Argumentos inválidos."
74
+
75
+ if name == "execute_python":
76
+ code = args.get("code", "")
77
+ if not code.strip():
78
+ return "❌ Código vacío."
79
+ output = io.StringIO()
80
+ try:
81
+ with redirect_stdout(output):
82
+ exec(code, {"__builtins__": {}}, {})
83
+ result = output.getvalue().strip()
84
+ return f"✅ {result}" if result else "✅ Ejecutado sin salida."
85
+ except Exception as e:
86
+ return f"❌ Error: {str(e)}"
87
+ return f"❌ Tool no implementada: {name}"
88
+
89
+ # =========================
90
+ # ESTILO VISUAL DEL MODELO
91
+ # =========================
92
+ def model_badge(model_name):
93
+ colors = {
94
+ "gpt": "background-color:#3182ce;color:white;",
95
+ "Qwen3": "background-color:#38a169;color:white;",
96
+ "DeepSeek": "background-color:#e53e3e;color:white;",
97
+ "Llama": "background-color:#805ad5;color:white;",
98
+ }
99
+ for key, style in colors.items():
100
+ if key.lower() in model_name.lower():
101
+ return f'<span style="{style}padding:2px 6px;border-radius:6px;font-size:0.8em;">{model_name}</span>'
102
+ return f'<span style="background-color:#718096;color:white;padding:2px 6px;border-radius:6px;font-size:0.8em;">{model_name}</span>'
103
+
104
+ # =========================
105
+ # CHAT PRINCIPAL - CORREGIDO
106
+ # =========================
107
+ def chat_with_batuto(system_prompt, message, image, history):
108
+ if not message.strip():
109
+ return history, "", None
110
+
111
+ has_image = image is not None
112
+ task_type = classify_task_local(message, has_image)
113
+ selected_model = MODELS[task_type]
114
+ model_name = selected_model.split('-')[0]
115
+
116
+ # Construir mensajes para la API
117
+ messages = [{"role": "system", "content": system_prompt or "Eres BATUTO/ANDROIDE_90. Responde natural en español."}]
118
+
119
+ # Convertir historial de Gradio a formato de API
120
+ for entry in history:
121
+ if isinstance(entry, list) and len(entry) == 2:
122
+ user_msg, bot_msg = entry
123
+ messages.append({"role": "user", "content": str(user_msg)})
124
+ messages.append({"role": "assistant", "content": str(bot_msg)})
125
+
126
+ # Agregar mensaje actual
127
+ if selected_model == "Llama-4-Maverick-17B-128E-Instruct" and has_image:
128
+ buffered = io.BytesIO()
129
+ image.save(buffered, format="PNG", optimize=True)
130
+ b64_img = base64.b64encode(buffered.getvalue()).decode()
131
+ content = [
132
+ {"type": "text", "text": message},
133
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64_img}"}}
134
+ ]
135
+ messages.append({"role": "user", "content": content})
136
+ else:
137
+ messages.append({"role": "user", "content": message})
138
+
139
+ try:
140
+ tools_param = TOOLS if task_type != "vision" else None
141
+ api_call = client.chat.completions.create(
142
+ model=selected_model,
143
+ messages=messages,
144
+ tools=tools_param,
145
+ tool_choice="auto" if tools_param else None,
146
+ temperature=0.15,
147
+ top_p=0.1,
148
+ max_tokens=1024,
149
+ )
150
+
151
+ msg_out = api_call.choices[0].message
152
+ badge = model_badge(selected_model)
153
+
154
+ # Procesar respuesta
155
+ if msg_out.content:
156
+ reply = f"{badge} {msg_out.content}"
157
+ elif hasattr(msg_out, 'tool_calls') and msg_out.tool_calls:
158
+ tool_results = []
159
+ for tool_call in msg_out.tool_calls:
160
+ result = execute_tool(tool_call)
161
+ tool_results.append(f"🔧 {tool_call.function.name}: {result}")
162
+ reply = f"{badge} " + "\n".join(tool_results)
163
+ else:
164
+ reply = f"{badge} Respuesta vacía."
165
+
166
+ # Actualizar historial en formato Gradio (tuplas)
167
+ history.append((message, reply))
168
+ return history, "", None
169
+
170
+ except Exception as e:
171
+ import traceback
172
+ error = f"❌ [{model_name}] {str(e)}"
173
+ print(f"Error completo: {traceback.format_exc()}")
174
+ history.append((message, error))
175
+ return history, "", None
176
+
177
+ # =========================
178
+ # INTERFAZ DE GRADIO - CORREGIDA
179
+ # =========================
180
+ def clear_inputs():
181
+ return [], "", None
182
+
183
+ with gr.Blocks(
184
+ title="🤖 BATUTO/ANDROIDE_90 Pro",
185
+ theme=gr.themes.Soft(primary_hue="blue"),
186
+ css="""
187
+ .gradio-container {max-width: 1000px !important; margin: auto;}
188
+ .header {text-align: center; padding: 15px; background: linear-gradient(135deg,#667eea 0%,#764ba2 100%); color: white; border-radius: 8px;}
189
+ .chatbot {min-height: 480px;}
190
+ """,
191
+ ) as demo:
192
+
193
+ with gr.Column(elem_classes="header"):
194
+ gr.Markdown("""
195
+ # 🤖 BATUTO/ANDROIDE_90 Pro
196
+ **Modelos SambaNova optimizados con selección automática y visualización**
197
+ """)
198
+
199
+ with gr.Tabs():
200
+ with gr.TabItem("💬 Chat"):
201
+ system_prompt = gr.Textbox(
202
+ lines=3,
203
+ value="Eres BATUTO/ANDROIDE_90. Responde de manera natural y precisa en español.",
204
+ label="Prompt del sistema"
205
+ )
206
+ # Cambiar a type por defecto (sin "messages")
207
+ chatbot = gr.Chatbot(
208
+ height=480,
209
+ show_copy_button=True,
210
+ elem_classes="chatbot"
211
+ )
212
+ msg = gr.Textbox(placeholder="Escribe tu mensaje...", label="Mensaje")
213
+ img = gr.Image(type="pil", label="Imagen opcional")
214
+
215
+ send = gr.Button("🚀 Enviar", variant="primary")
216
+ clear = gr.Button("🧹 Limpiar")
217
+
218
+ send.click(
219
+ chat_with_batuto,
220
+ [system_prompt, msg, img, chatbot],
221
+ [chatbot, msg, img]
222
+ )
223
+ msg.submit(
224
+ chat_with_batuto,
225
+ [system_prompt, msg, img, chatbot],
226
+ [chatbot, msg, img]
227
+ )
228
+ clear.click(clear_inputs, None, [chatbot, msg, img])
229
+
230
+ with gr.TabItem("⚙️ Ejecutor de Código"):
231
+ gr.Markdown("### Ejecutor Independiente de Python")
232
+ code_input = gr.Code(
233
+ language="python",
234
+ lines=8,
235
+ value='print("¡Hola desde BATUTO!")\nresultado = 2 + 2\nprint(f"2 + 2 = {resultado}")',
236
+ label="Código Python"
237
+ )
238
+ exec_output = gr.Textbox(
239
+ lines=8,
240
+ label="Resultado de la ejecución",
241
+ interactive=False
242
+ )
243
+
244
+ def execute_independent(code):
245
+ if not code.strip():
246
+ return "❌ Código vacío."
247
+
248
+ output = io.StringIO()
249
+ try:
250
+ with redirect_stdout(output):
251
+ exec(code, {"__builtins__": {}}, {})
252
+ result = output.getvalue().strip()
253
+ return f"✅ Ejecutado correctamente:\n{result}" if result else "✅ Código ejecutado sin salida."
254
+ except Exception as e:
255
+ return f"❌ Error:\n{str(e)}"
256
+
257
+ exec_button = gr.Button("▶️ Ejecutar Código", variant="primary")
258
+ exec_button.click(execute_independent, code_input, exec_output)
259
+
260
+ gr.Markdown("**Estado:** ✅ Modelos SambaNova activos | Visualización multimodal habilitada")
261
+
262
+ # Lanzar la app
263
+ if __name__ == "__main__":
264
+ demo.launch(share=True, show_error=True)