Madras1 commited on
Commit
3d02fef
·
verified ·
1 Parent(s): f786efc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -59
app.py CHANGED
@@ -9,28 +9,31 @@ from groq import Groq
9
  from mistralai import Mistral
10
  import google.generativeai as genai
11
 
12
- # --- CONFIGURAÇÕES DE CLIENTES ---
13
-
14
- # 1. LOCAL (H200)
15
  LOCAL_MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"
16
  local_model = None
17
  local_tokenizer = None
18
 
19
- # 2. NUVEM (Carrega chaves do ambiente)
20
  groq_client = Groq(api_key=os.environ.get("GROQ_API_KEY")) if os.environ.get("GROQ_API_KEY") else None
21
  mistral_client = Mistral(api_key=os.environ.get("MISTRAL_API_KEY")) if os.environ.get("MISTRAL_API_KEY") else None
22
  if os.environ.get("GEMINI_API_KEY"):
23
  genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
24
 
25
- # --- HELPER: Imagem para Base64 ---
26
  def encode_image(image_path):
27
- with open(image_path, "rb") as image_file:
28
- return base64.b64encode(image_file.read()).decode('utf-8')
 
 
 
 
 
29
 
30
- # --- FUNÇÃO 1: LOCAL H200 ---
31
  @spaces.GPU(duration=60)
32
  def run_local_h200(messages):
33
- # Qwen local não suporta imagem fácil via API, bloqueia se tiver
34
  for m in messages:
35
  if isinstance(m['content'], list):
36
  return "⚠️ O modelo Qwen Local H200 suporta apenas texto. Use Gemini ou Pixtral para imagens."
@@ -43,32 +46,34 @@ def run_local_h200(messages):
43
  LOCAL_MODEL_ID, torch_dtype=torch.bfloat16, device_map="cuda"
44
  )
45
 
46
- # Processa histórico simples para texto
47
  text = local_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
48
  inputs = local_tokenizer([text], return_tensors="pt").to(local_model.device)
49
  outputs = local_model.generate(**inputs, max_new_tokens=2048, temperature=0.6, do_sample=True)
50
  return local_tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
51
 
52
- # --- FUNÇÃO 2: GROQ ---
53
  def run_groq(messages, model_id):
54
- # Groq não vê imagem
55
  for m in messages:
56
  if isinstance(m['content'], list):
57
  return "⚠️ Modelos Groq não veem imagens. Use Pixtral ou Gemini."
58
-
59
  if not groq_client: return "❌ Erro: Configure GROQ_API_KEY."
 
 
 
 
 
 
60
  try:
61
  completion = groq_client.chat.completions.create(
62
- model=model_id, messages=messages, temperature=0.7, max_tokens=4096
63
  )
64
  return completion.choices[0].message.content
65
  except Exception as e: return f"❌ Groq Error: {e}"
66
 
67
- # --- FUNÇÃO 3: MISTRAL (Pixtral) ---
68
  def run_mistral(messages, model_id):
69
  if not mistral_client: return "❌ Erro: Configure MISTRAL_API_KEY."
70
 
71
- # Formata mensagens para Mistral (texto ou imagem url)
72
  mistral_msgs = []
73
  for msg in messages:
74
  role = msg['role']
@@ -76,16 +81,19 @@ def run_mistral(messages, model_id):
76
 
77
  if isinstance(content, str):
78
  mistral_msgs.append({"role": role, "content": content})
79
- elif isinstance(content, list): # Multimodal
80
  new_content = []
81
  for item in content:
82
  if item['type'] == 'text':
83
  new_content.append({"type": "text", "text": item['text']})
84
- elif item['type'] == 'image':
85
- # Gradio 5 passa o path da imagem
86
- image_path = item['image']['path'] if isinstance(item['image'], dict) else item['image']
87
- base64_img = encode_image(image_path)
88
- new_content.append({"type": "image_url", "image_url": f"data:image/jpeg;base64,{base64_img}"})
 
 
 
89
  mistral_msgs.append({"role": role, "content": new_content})
90
 
91
  try:
@@ -93,88 +101,112 @@ def run_mistral(messages, model_id):
93
  return res.choices[0].message.content
94
  except Exception as e: return f"❌ Mistral Error: {e}"
95
 
96
- # --- FUNÇÃO 4: GEMINI ---
97
  def run_gemini(messages, model_id):
98
  if not os.environ.get("GEMINI_API_KEY"): return "❌ Erro: Configure GEMINI_API_KEY."
99
  try:
100
  model = genai.GenerativeModel(model_id)
101
  chat_history = []
102
 
103
- # Histórico (Gradio 5 Messages -> Gemini Parts)
104
  for msg in messages[:-1]:
105
  role = "user" if msg['role'] == "user" else "model"
106
  parts = []
107
  content = msg['content']
108
- if isinstance(content, str):
109
- parts.append(content)
110
  elif isinstance(content, list):
111
  for item in content:
112
  if item['type'] == 'text': parts.append(item['text'])
113
- elif item['type'] == 'image':
114
- path = item['image']['path'] if isinstance(item['image'], dict) else item['image']
115
- parts.append(Image.open(path))
116
  chat_history.append({"role": role, "parts": parts})
117
 
118
- # Mensagem Atual (Última)
119
- current_content = messages[-1]['content']
120
  current_parts = []
121
- if isinstance(current_content, str):
122
- current_parts.append(current_content)
123
- elif isinstance(current_content, list):
124
- for item in current_content:
125
  if item['type'] == 'text': current_parts.append(item['text'])
126
- elif item['type'] == 'image':
127
- path = item['image']['path'] if isinstance(item['image'], dict) else item['image']
128
- current_parts.append(Image.open(path))
129
 
130
  chat = model.start_chat(history=chat_history)
131
  response = chat.send_message(current_parts)
132
  return response.text
133
  except Exception as e: return f"❌ Gemini Error ({model_id}): {e}"
134
 
135
- # --- ROTEADOR ---
136
  def router(message, history, model_selector):
137
- # No Gradio 5 com type="messages", history vem atualizado e message é o payload novo
138
- # Mas para o chat funcionar fluido, vamos reconstruir o array completo
139
- # history é lista de dicts [{'role':'user', ...}, {'role':'assistant', ...}]
140
 
141
- # 1. Constrói o histórico completo incluindo a mensagem atual
142
- full_history = history + [message]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- # 2. Roteamento
 
 
 
 
 
 
 
 
 
 
145
  if "Gemini" in model_selector:
146
- if "3.0" in model_selector: tid = "gemini-3.0-pro-preview" # Aposta no beta
 
147
  elif "2.5 Pro" in model_selector: tid = "gemini-2.5-pro"
148
  elif "2.5 Flash" in model_selector: tid = "gemini-2.5-flash"
149
  elif "2.0 Flash" in model_selector: tid = "gemini-2.0-flash-exp"
150
- else: tid = "gemini-1.5-flash"
151
- return run_gemini(full_history, tid)
152
 
153
  elif "Mistral" in model_selector:
 
154
  if "Pixtral" in model_selector: tid = "pixtral-large-latest"
155
  elif "2512" in model_selector: tid = "mistral-large-2512"
156
  elif "Magistral" in model_selector: tid = "magistral-medium-latest"
157
  elif "Codestral" in model_selector: tid = "codestral-2508"
158
- else: tid = "mistral-large-latest"
159
- return run_mistral(full_history, tid)
160
 
161
  elif "Groq" in model_selector:
162
- return run_groq(full_history, "llama-3.3-70b-versatile")
163
 
164
  elif "H200" in model_selector:
165
- return run_local_h200(full_history)
166
 
167
  return "Modelo desconhecido."
168
 
169
- # --- INTERFACE GRADIO 5 ---
170
- with gr.Blocks(fill_height=True) as demo:
171
- gr.Markdown("# 🔀 APIDOST V7: The Vision Update")
172
 
173
  with gr.Row():
174
  model_dropdown = gr.Dropdown(
175
  choices=[
176
  "✨ Google: Gemini 3.0 Pro (Experimental)",
177
  "✨ Google: Gemini 2.5 Flash",
 
178
  "☁️ Groq: Llama 3.3 70B",
179
  "🇫🇷 Mistral: Pixtral Large (Vision) 🖼️",
180
  "🇫🇷 Mistral: Large 2512 (Dez/25)",
@@ -185,13 +217,13 @@ with gr.Blocks(fill_height=True) as demo:
185
  interactive=True
186
  )
187
 
188
- # type="messages" é vital para o Gradio 5 entender o formato novo
189
  chat = gr.ChatInterface(
190
  fn=router,
191
- type="messages",
192
  additional_inputs=[model_dropdown],
193
- multimodal=True,
194
  )
195
 
196
  if __name__ == "__main__":
197
- demo.launch()
 
 
9
  from mistralai import Mistral
10
  import google.generativeai as genai
11
 
12
+ # --- CONFIGURAÇÕES ---
13
+ # 1. LOCAL
 
14
  LOCAL_MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"
15
  local_model = None
16
  local_tokenizer = None
17
 
18
+ # 2. NUVEM
19
  groq_client = Groq(api_key=os.environ.get("GROQ_API_KEY")) if os.environ.get("GROQ_API_KEY") else None
20
  mistral_client = Mistral(api_key=os.environ.get("MISTRAL_API_KEY")) if os.environ.get("MISTRAL_API_KEY") else None
21
  if os.environ.get("GEMINI_API_KEY"):
22
  genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
23
 
24
+ # --- HELPER ---
25
  def encode_image(image_path):
26
+ try:
27
+ with open(image_path, "rb") as image_file:
28
+ return base64.b64encode(image_file.read()).decode('utf-8')
29
+ except Exception:
30
+ return None
31
+
32
+ # --- FUNÇÕES DE INFERÊNCIA ---
33
 
 
34
  @spaces.GPU(duration=60)
35
  def run_local_h200(messages):
36
+ # Qwen Local (Texto puro)
37
  for m in messages:
38
  if isinstance(m['content'], list):
39
  return "⚠️ O modelo Qwen Local H200 suporta apenas texto. Use Gemini ou Pixtral para imagens."
 
46
  LOCAL_MODEL_ID, torch_dtype=torch.bfloat16, device_map="cuda"
47
  )
48
 
 
49
  text = local_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
50
  inputs = local_tokenizer([text], return_tensors="pt").to(local_model.device)
51
  outputs = local_model.generate(**inputs, max_new_tokens=2048, temperature=0.6, do_sample=True)
52
  return local_tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
53
 
 
54
  def run_groq(messages, model_id):
55
+ # Groq (Texto puro)
56
  for m in messages:
57
  if isinstance(m['content'], list):
58
  return "⚠️ Modelos Groq não veem imagens. Use Pixtral ou Gemini."
59
+
60
  if not groq_client: return "❌ Erro: Configure GROQ_API_KEY."
61
+
62
+ # Limpa formato para OpenAI
63
+ clean_msgs = []
64
+ for m in messages:
65
+ clean_msgs.append({"role": m['role'], "content": m['content']})
66
+
67
  try:
68
  completion = groq_client.chat.completions.create(
69
+ model=model_id, messages=clean_msgs, temperature=0.7, max_tokens=4096
70
  )
71
  return completion.choices[0].message.content
72
  except Exception as e: return f"❌ Groq Error: {e}"
73
 
 
74
  def run_mistral(messages, model_id):
75
  if not mistral_client: return "❌ Erro: Configure MISTRAL_API_KEY."
76
 
 
77
  mistral_msgs = []
78
  for msg in messages:
79
  role = msg['role']
 
81
 
82
  if isinstance(content, str):
83
  mistral_msgs.append({"role": role, "content": content})
84
+ elif isinstance(content, list):
85
  new_content = []
86
  for item in content:
87
  if item['type'] == 'text':
88
  new_content.append({"type": "text", "text": item['text']})
89
+ elif item['type'] == 'image_url':
90
+ # O router mandou como data url ou path, vamos garantir
91
+ url = item['image_url']['url']
92
+ if url.startswith("data:image"):
93
+ new_content.append({"type": "image_url", "image_url": url})
94
+ else:
95
+ b64 = encode_image(url)
96
+ if b64: new_content.append({"type": "image_url", "image_url": f"data:image/jpeg;base64,{b64}"})
97
  mistral_msgs.append({"role": role, "content": new_content})
98
 
99
  try:
 
101
  return res.choices[0].message.content
102
  except Exception as e: return f"❌ Mistral Error: {e}"
103
 
 
104
  def run_gemini(messages, model_id):
105
  if not os.environ.get("GEMINI_API_KEY"): return "❌ Erro: Configure GEMINI_API_KEY."
106
  try:
107
  model = genai.GenerativeModel(model_id)
108
  chat_history = []
109
 
 
110
  for msg in messages[:-1]:
111
  role = "user" if msg['role'] == "user" else "model"
112
  parts = []
113
  content = msg['content']
114
+ if isinstance(content, str): parts.append(content)
 
115
  elif isinstance(content, list):
116
  for item in content:
117
  if item['type'] == 'text': parts.append(item['text'])
118
+ elif item['type'] == 'image_url':
119
+ path = item['image_url']['url']
120
+ if not path.startswith("data:"): parts.append(Image.open(path))
121
  chat_history.append({"role": role, "parts": parts})
122
 
123
+ last_msg = messages[-1]
 
124
  current_parts = []
125
+ content = last_msg['content']
126
+ if isinstance(content, str): current_parts.append(content)
127
+ elif isinstance(content, list):
128
+ for item in content:
129
  if item['type'] == 'text': current_parts.append(item['text'])
130
+ elif item['type'] == 'image_url':
131
+ path = item['image_url']['url']
132
+ if not path.startswith("data:"): current_parts.append(Image.open(path))
133
 
134
  chat = model.start_chat(history=chat_history)
135
  response = chat.send_message(current_parts)
136
  return response.text
137
  except Exception as e: return f"❌ Gemini Error ({model_id}): {e}"
138
 
139
+ # --- ROTEADOR INTELIGENTE (Compatível Gradio 4) ---
140
  def router(message, history, model_selector):
141
+ # Em Gradio 4 com multimodal=True, 'message' é um dict: {'text': '...', 'files': ['path']}
 
 
142
 
143
+ # 1. Reconstrói histórico padronizado
144
+ formatted_history = []
145
+ for user_msg, bot_msg in history:
146
+ # User (pode ter imagem antiga, mas Gradio 4 passa files separados no histórico visual)
147
+ # Simplificando: Assumimos texto no histórico visual do Gradio 4
148
+ if isinstance(user_msg, dict): # Caso raro no history
149
+ formatted_history.append({"role": "user", "content": user_msg.get("text")})
150
+ else:
151
+ formatted_history.append({"role": "user", "content": str(user_msg)})
152
+
153
+ # Bot
154
+ if bot_msg:
155
+ formatted_history.append({"role": "assistant", "content": str(bot_msg)})
156
+
157
+ # 2. Formata a mensagem ATUAL (Onde a imagem está agora)
158
+ current_content = []
159
+ text_input = message.get("text", "")
160
+ files = message.get("files", [])
161
+
162
+ if text_input:
163
+ current_content.append({"type": "text", "text": text_input})
164
 
165
+ for file_path in files:
166
+ # Adiciona imagem para processamento
167
+ current_content.append({"type": "image_url", "image_url": {"url": file_path}})
168
+
169
+ # Se não tem imagem, manda string simples (melhor compatibilidade)
170
+ if not files:
171
+ formatted_history.append({"role": "user", "content": text_input})
172
+ else:
173
+ formatted_history.append({"role": "user", "content": current_content})
174
+
175
+ # 3. Roteamento
176
  if "Gemini" in model_selector:
177
+ tid = "gemini-1.5-flash" # Default seguro
178
+ if "3.0" in model_selector: tid = "gemini-3.0-pro-preview"
179
  elif "2.5 Pro" in model_selector: tid = "gemini-2.5-pro"
180
  elif "2.5 Flash" in model_selector: tid = "gemini-2.5-flash"
181
  elif "2.0 Flash" in model_selector: tid = "gemini-2.0-flash-exp"
182
+ return run_gemini(formatted_history, tid)
 
183
 
184
  elif "Mistral" in model_selector:
185
+ tid = "mistral-large-latest"
186
  if "Pixtral" in model_selector: tid = "pixtral-large-latest"
187
  elif "2512" in model_selector: tid = "mistral-large-2512"
188
  elif "Magistral" in model_selector: tid = "magistral-medium-latest"
189
  elif "Codestral" in model_selector: tid = "codestral-2508"
190
+ return run_mistral(formatted_history, tid)
 
191
 
192
  elif "Groq" in model_selector:
193
+ return run_groq(formatted_history, "llama-3.3-70b-versatile")
194
 
195
  elif "H200" in model_selector:
196
+ return run_local_h200(formatted_history)
197
 
198
  return "Modelo desconhecido."
199
 
200
+ # --- INTERFACE ---
201
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
202
+ gr.Markdown("# 🔀 APIDOST V7: Vision Update (Stable)")
203
 
204
  with gr.Row():
205
  model_dropdown = gr.Dropdown(
206
  choices=[
207
  "✨ Google: Gemini 3.0 Pro (Experimental)",
208
  "✨ Google: Gemini 2.5 Flash",
209
+ "✨ Google: Gemini 2.0 Flash (Exp)",
210
  "☁️ Groq: Llama 3.3 70B",
211
  "🇫🇷 Mistral: Pixtral Large (Vision) 🖼️",
212
  "🇫🇷 Mistral: Large 2512 (Dez/25)",
 
217
  interactive=True
218
  )
219
 
220
+ # REMOVIDO type="messages" (Isso corrige o erro!)
221
  chat = gr.ChatInterface(
222
  fn=router,
 
223
  additional_inputs=[model_dropdown],
224
+ multimodal=True, # Isso ativa o upload de imagens no Gradio 4
225
  )
226
 
227
  if __name__ == "__main__":
228
+ # Configuração de rede padrão
229
+ demo.launch(server_name="0.0.0.0", server_port=7860)