BATUTO-ART commited on
Commit
69a13cb
·
verified ·
1 Parent(s): 982f448

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -237
app.py CHANGED
@@ -1,288 +1,245 @@
1
  import os
2
- import io
3
- import base64
4
- import json
5
- import requests
6
- import tempfile
7
- from PIL import Image
8
  import gradio as gr
9
- from huggingface_hub import InferenceClient
 
10
 
11
- # ===========================================
12
  # CONFIG
13
- # ===========================================
14
- API_KEY = os.getenv("REVE_API_KEY")
15
- HF_TOKEN = os.getenv("HF_TOKEN")
16
- BASE_URL = "https://api.sambanova.ai/v1"
 
17
 
18
- DEBUG = False # Ponlo True si quieres ver el payload exacto enviado
 
 
 
19
 
 
 
 
20
 
21
- # ===========================================
22
- # TUS MODELOS (RESPETADOS 1:1)
23
- # ===========================================
24
  MODELS = {
25
- "general_fast": {
26
- "name": "Meta-Llama-3.1-8B-Instruct",
27
- "role": "🔄 Respuestas rápidas y generales",
28
- "description": "Conversación ligera y eficiente.",
29
- "supports_images": False
30
  },
31
- "general_smart": {
32
- "name": "Meta-Llama-3.3-70B-Instruct",
33
- "role": "🧠 Razonamiento profundo",
34
- "description": "Análisis detallado y avanzado.",
35
- "supports_images": False
36
  },
37
- "coding_expert": {
38
- "name": "DeepSeek-V3.1",
39
- "role": "💻 Programación y debugging",
40
- "description": "Ideal para desarrollo.",
41
- "supports_images": False
42
  },
43
- "coding_alt": {
44
- "name": "DeepSeek-V3-0324",
45
- "role": " Código rápido",
46
- "description": "Alternativa veloz.",
47
- "supports_images": False
48
  },
49
- "massive_brain": {
50
- "name": "gpt-oss-120b",
51
- "role": "🏛️ Sabiduría masiva",
52
- "description": "Problemas pesados y complejos.",
53
- "supports_images": False
54
  },
55
- "specialized_1": {
56
- "name": "DeepSeek-V3.1-Terminus",
57
- "role": "🎯 Especialista técnico",
58
- "description": "Tareas científicas y avanzadas.",
59
- "supports_images": False
60
  },
61
- "specialized_2": {
62
- "name": "Llama-3.3-Swallow-70B-Instruct-v0.4",
63
- "role": "🔥 Sin censura",
64
- "description": "Modelo sin restricciones.",
65
- "supports_images": False
66
  },
67
- "multilingual": {
68
- "name": "Qwen3-32B",
69
- "role": "🌍 Multilingüe",
70
- "description": "Múltiples idiomas.",
71
- "supports_images": False
72
  },
73
- "vision_expert": {
74
- "name": "Llama-4-Maverick-17B-128E-Instruct",
75
- "role": "👁️ Visión avanzada",
76
- "description": "Análisis de imágenes.",
77
- "supports_images": True
78
  },
79
- "boudoir_specialist": {
80
- "name": "ALLaM-7B-Instruct-preview",
81
- "role": "🎭 Especialista en Fotografía Íntima Profesional",
82
- "description": "Experto en prompts para fotografía boudoir.",
83
- "supports_images": False,
84
- "specialties": [
85
- "Fotografía Boudoir",
86
- "Desnudo Artístico",
87
- "Moda Sensual",
88
- "Lencería y moda íntima"
89
- ],
90
- "technical_expertise": [
91
- "Iluminación suave",
92
- "Composición elegante",
93
- "Dirección de poses",
94
- "Edición fina",
95
- "Escenografía íntima"
96
- ],
97
- "ethical_principles": [
98
- "Consentimiento explícito",
99
- "Positividad corporal"
100
- ]
101
- }
102
- }
103
-
104
 
105
- # ===========================================
106
- # HELPERS
107
- # ===========================================
108
- def encode_image(img):
109
- if img is None:
110
- return None
111
- buffer = io.BytesIO()
112
- img.save(buffer, format="PNG")
113
- return base64.b64encode(buffer.getvalue()).decode("utf-8")
114
-
115
-
116
- def _safe_content(c):
117
- """Convierte cualquier cosa a algo aceptable por SambaNova."""
118
- if c is None:
119
- return ""
120
- if isinstance(c, str):
121
- return c
122
- if isinstance(c, dict):
123
- return c
124
- if isinstance(c, list):
125
- return c
126
- return str(c)
127
-
128
-
129
- def validate_messages(messages):
130
- """
131
- Corrige TODO lo que pueda romper SambaNova:
132
- - Asegura diccionario {role, content}
133
- - Limpia historial de Gradio
134
- - Evita contenidos no serializables
135
- """
136
- out = []
137
-
138
- for m in messages:
139
-
140
- # Caso válido
141
- if isinstance(m, dict) and "role" in m and "content" in m:
142
- out.append({
143
- "role": m["role"],
144
- "content": _safe_content(m["content"])
145
- })
146
- continue
147
-
148
- # Tuplas del historial (user, assistant)
149
- if isinstance(m, (list, tuple)) and len(m) == 2:
150
- u, a = m
151
- out.append({"role": "user", "content": _safe_content(u)})
152
- out.append({"role": "assistant", "content": _safe_content(a)})
153
- continue
154
-
155
- # Todo lo demás
156
- out.append({"role": "user", "content": _safe_content(m)})
157
-
158
- return out
159
-
160
-
161
- # ===========================================
162
- # LLAMADA A SAMBANOVA (LA BUENA)
163
- # ===========================================
164
- def call_sambanova(model, messages):
165
-
166
- messages = validate_messages(messages)
167
-
168
- payload = {
169
- "model": model,
170
- "messages": messages,
171
- "stream": False
172
- }
173
-
174
- if DEBUG:
175
- return json.dumps(payload, indent=2)
176
 
177
- headers = {
178
- "Authorization": f"Bearer {API_KEY}",
179
- "Content-Type": "application/json"
180
- }
 
 
 
 
 
 
 
 
 
181
 
182
- r = requests.post(
183
- f"{BASE_URL}/chat/completions",
184
- json=payload,
185
- headers=headers,
186
- timeout=60
187
- )
188
 
189
- try:
190
- data = r.json()
191
- return data["choices"][0]["message"]["content"]
192
- except:
193
- return "Respuesta inesperada:\n" + r.text
 
 
 
 
194
 
 
 
 
 
 
195
 
196
- # ===========================================
197
- # CHAT PRINCIPAL (CORREGIDO)
198
- # ===========================================
199
- def chat_logic(user_text, user_image, model_selection, history):
 
200
 
201
- if history is None:
202
- history = []
 
 
 
 
203
 
204
- model = MODELS[model_selection]
205
- supports_images = model["supports_images"]
 
206
 
 
207
  messages = []
208
 
209
  # SYSTEM
210
- messages.append({"role": "system", "content": model["role"]})
211
-
212
- # HISTORIAL
213
- for u, a in history:
214
- messages.append({"role": "user", "content": _safe_content(u)})
215
- messages.append({"role": "assistant", "content": _safe_content(a)})
216
 
217
- # MENSAJE DEL USUARIO
218
- if user_image and supports_images:
219
- encoded = encode_image(user_image)
220
- user_content = [
221
- {"type": "text", "text": user_text or ""},
222
- {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{encoded}"}}
223
- ]
224
- messages.append({"role": "user", "content": user_content})
225
 
226
- else:
227
- messages.append({"role": "user", "content": _safe_content(user_text)})
228
 
229
- reply = call_sambanova(model["name"], messages)
 
230
 
231
- history.append((user_text or "[Imagen]", reply))
232
- return history, history
233
 
 
 
234
 
235
- # ===========================================
236
- # GENERACIÓN HF
237
- # ===========================================
238
- def generate_image(prompt):
239
- if not HF_TOKEN:
240
- return None, "❌ Falta HF_TOKEN", gr.update(visible=False)
241
 
242
- try:
243
- client = InferenceClient(token=HF_TOKEN)
244
- img = client.text_to_image(prompt, model="stabilityai/stable-diffusion-xl-base-1.0")
245
 
246
- with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
247
- img.save(tmp)
248
- return img, "OK", gr.update(value=tmp.name, visible=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
- except Exception as e:
251
- return None, f"❌ Error: {e}", gr.update(visible=False)
252
 
 
 
 
253
 
254
- # ===========================================
255
- # UI
256
- # ===========================================
257
  def create_ui():
 
 
 
 
 
 
 
 
 
258
 
259
- with gr.Blocks() as demo:
260
-
261
- gr.Markdown("# 🤖 Chat Multimodal SambaNova")
 
 
262
 
263
- with gr.Tab("Chat"):
264
- model_sel = gr.Dropdown(choices=list(MODELS.keys()), value="general_fast", label="Modelo")
265
- chat = gr.Chatbot(height=420)
266
- img = gr.Image(type="pil")
267
- txt = gr.Textbox(label="Mensaje")
268
- send = gr.Button("Enviar")
269
 
270
- send.click(
271
- chat_logic,
272
- inputs=[txt, img, model_sel, chat],
273
- outputs=[chat, chat]
274
- )
275
 
276
- with gr.Tab("Imágenes"):
277
- p = gr.Textbox(label="Prompt")
278
- b = gr.Button("Generar")
279
- out = gr.Image()
280
- d = gr.DownloadButton("Descargar", visible=False)
281
- b.click(generate_image, inputs=[p], outputs=[out, txt, d])
282
 
283
  return demo
284
 
285
-
286
  demo = create_ui()
287
 
288
  if __name__ == "__main__":
 
1
  import os
 
 
 
 
 
 
2
  import gradio as gr
3
+ import base64
4
+ from openai import OpenAI
5
 
6
+ # ============================================================
7
  # CONFIG
8
+ # ============================================================
9
+
10
+ API_KEY = os.getenv("OPENROUTER_API_KEY")
11
+ if not API_KEY:
12
+ raise ValueError("Falta la variable OPENROUTER_API_KEY.")
13
 
14
+ client = OpenAI(
15
+ base_url="https://openrouter.ai/api/v1",
16
+ api_key=API_KEY
17
+ )
18
 
19
+ # ============================================================
20
+ # TODOS TUS MODELOS COMPLETOS
21
+ # ============================================================
22
 
 
 
 
23
  MODELS = {
24
+ # -------- META LLAMA --------
25
+ "llama_3.1_70b": {
26
+ "name": "meta-llama/llama-3.1-70b-instruct",
27
+ "role": "Respondes con precisión técnica y claridad."
 
28
  },
29
+ "llama_3.1_405b": {
30
+ "name": "meta-llama/llama-3.1-405b-instruct",
31
+ "role": "Eres experto en programación, ciencia y análisis avanzado."
 
 
32
  },
33
+
34
+ # -------- LLAMA 3.2 VISION --------
35
+ "llama_3.2_11b_vision": {
36
+ "name": "meta-llama/llama-3.2-11b-vision-instruct",
37
+ "role": "Eres un modelo experto en análisis visual detallado."
38
  },
39
+ "llama_3.2_90b_vision": {
40
+ "name": "meta-llama/llama-3.2-90b-vision-instruct",
41
+ "role": "Eres un analista visual avanzado altamente preciso."
 
 
42
  },
43
+
44
+ # -------- QWEN --------
45
+ "qwen_72b": {
46
+ "name": "qwen/qwen2.5-72b-instruct",
47
+ "role": "Respondes de forma profesional, directa y clara."
48
  },
49
+ "qwen_110b": {
50
+ "name": "qwen/qwen2.5-110b-instruct",
51
+ "role": "Asistente experto en razonamiento estructurado."
 
 
52
  },
53
+
54
+ # -------- GPT / OPENAI --------
55
+ "gpt_4.1": {
56
+ "name": "openai/gpt-4.1",
57
+ "role": "Asistente avanzado para cualquier tarea general."
58
  },
59
+ "gpt_4.1_mini": {
60
+ "name": "openai/gpt-4.1-mini",
61
+ "role": "Modelo rápido y eficiente, ideal para respuestas concisas."
 
 
62
  },
63
+ "gpt_4o_mini": {
64
+ "name": "openai/gpt-4o-mini",
65
+ "role": "Asistente veloz con buena comprensión general."
 
 
66
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ # -------- CLAUDE --------
69
+ "claude_3.5_sonnet": {
70
+ "name": "anthropic/claude-3.5-sonnet",
71
+ "role": "Especialista en redacción, precisión y análisis profundo."
72
+ },
73
+ "claude_3.5_haiku": {
74
+ "name": "anthropic/claude-3.5-haiku",
75
+ "role": "Modelo rápido con buena comprensión general."
76
+ },
77
+ "claude_3_opus": {
78
+ "name": "anthropic/claude-3-opus",
79
+ "role": "Máxima capacidad de análisis y lenguaje."
80
+ },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ # -------- GOOGLE --------
83
+ "gemini_flash": {
84
+ "name": "google/gemini-flash-1.5",
85
+ "role": "Especialista en escenarios visuales y respuestas rápidas."
86
+ },
87
+ "gemini_pro": {
88
+ "name": "google/gemini-pro-1.5",
89
+ "role": "Razonador general robusto y flexible."
90
+ },
91
+ "gemini_thinking": {
92
+ "name": "google/gemini-1.5-thinking",
93
+ "role": "Modelo de razonamiento profundo y detallado."
94
+ },
95
 
96
+ # -------- DEEPSEEK --------
97
+ "deepseek_r1": {
98
+ "name": "deepseek/deepseek-r1",
99
+ "role": "Razonamiento profundo y cadena de pensamiento estructurada."
100
+ },
 
101
 
102
+ # -------- MISTRAL --------
103
+ "mistral_large": {
104
+ "name": "mistral/mistral-large-latest",
105
+ "role": "Asistente técnico avanzado y preciso."
106
+ },
107
+ "mixtral_8x7b": {
108
+ "name": "mistral/mixtral-8x7b-instruct",
109
+ "role": "Modelo eficiente para tareas complejas sin alto costo."
110
+ },
111
 
112
+ # -------- REKA --------
113
+ "reka_core": {
114
+ "name": "reka/core",
115
+ "role": "Asistente racional y estructurado."
116
+ },
117
 
118
+ # -------- SAMBANOVA --------
119
+ "samba_allam_7b": {
120
+ "name": "sambanova/ALLAM-1-7B",
121
+ "role": "Asistente optimizado para rendimiento y claridad."
122
+ },
123
 
124
+ # -------- FLUX --------
125
+ "flux_pro": {
126
+ "name": "black-forest-labs/flux-1.1-pro",
127
+ "role": "Experto en generación y análisis de imágenes."
128
+ }
129
+ }
130
 
131
+ # ============================================================
132
+ # BUILDER DE MENSAJES (CORRIGE EL ERROR)
133
+ # ============================================================
134
 
135
+ def build_messages(system_prompt, user_input, history):
136
  messages = []
137
 
138
  # SYSTEM
139
+ if system_prompt:
140
+ messages.append({"role": "system", "content": system_prompt})
 
 
 
 
141
 
142
+ # HISTORIAL NORMALIZADO
143
+ for item in history:
144
+ if isinstance(item, tuple) and len(item) == 2:
 
 
 
 
 
145
 
146
+ user_msg, assistant_msg = item
 
147
 
148
+ if isinstance(user_msg, str) and user_msg.strip():
149
+ messages.append({"role": "user", "content": user_msg})
150
 
151
+ if isinstance(assistant_msg, str) and assistant_msg.strip():
152
+ messages.append({"role": "assistant", "content": assistant_msg})
153
 
154
+ # MENSAJE ACTUAL
155
+ messages.append({"role": "user", "content": user_input})
156
 
157
+ return messages
 
 
 
 
 
158
 
159
+ # ============================================================
160
+ # IMAGE base64
161
+ # ============================================================
162
 
163
+ def encode_image_to_base64(image):
164
+ if image is None:
165
+ return None
166
+ import io
167
+ import PIL.Image
168
+ buf = io.BytesIO()
169
+ image.save(buf, format="PNG")
170
+ return base64.b64encode(buf.getvalue()).decode("utf-8")
171
+
172
+ # ============================================================
173
+ # GENERADOR
174
+ # ============================================================
175
+
176
+ def generate(model_key, prompt, image, history):
177
+ selected = MODELS.get(model_key)
178
+ if not selected:
179
+ return "Error: Modelo no encontrado."
180
+
181
+ system_prompt = selected["role"]
182
+ messages = build_messages(system_prompt, prompt, history)
183
+
184
+ # Imagen opcional
185
+ if image:
186
+ img_b64 = encode_image_to_base64(image)
187
+
188
+ messages.append({
189
+ "role": "user",
190
+ "content": [
191
+ {"type": "input_image", "image_url": f"data:image/png;base64,{img_b64}"},
192
+ {"type": "text", "text": prompt}
193
+ ]
194
+ })
195
+
196
+ # Request
197
+ response = client.chat.completions.create(
198
+ model=selected["name"],
199
+ messages=messages,
200
+ temperature=0.7
201
+ )
202
 
203
+ return response.choices[0].message.content
 
204
 
205
+ # ============================================================
206
+ # UI GRADIO
207
+ # ============================================================
208
 
 
 
 
209
  def create_ui():
210
+ with gr.Blocks(theme="soft") as demo:
211
+ gr.Markdown("## Multi-Modelo OpenRouter (Todos tus modelos activados)")
212
+
213
+ with gr.Row():
214
+ model_dropdown = gr.Dropdown(
215
+ choices=list(MODELS.keys()),
216
+ value="llama_3.1_70b",
217
+ label="Modelo"
218
+ )
219
 
220
+ prompt = gr.Textbox(
221
+ label="Mensaje",
222
+ placeholder="Escribe tu mensaje…",
223
+ lines=4
224
+ )
225
 
226
+ image_input = gr.Image(type="pil", label="Imagen opcional")
227
+ chatbot = gr.Chatbot()
228
+ send = gr.Button("Enviar")
 
 
 
229
 
230
+ def respond(m, t, img, hist):
231
+ output = generate(m, t, img, hist)
232
+ hist.append((t, output))
233
+ return hist, hist
 
234
 
235
+ send.click(
236
+ respond,
237
+ inputs=[model_dropdown, prompt, image_input, chatbot],
238
+ outputs=[chatbot, chatbot]
239
+ )
 
240
 
241
  return demo
242
 
 
243
  demo = create_ui()
244
 
245
  if __name__ == "__main__":