TaylorKaua commited on
Commit
6dde8db
·
verified ·
1 Parent(s): 2048899

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +304 -143
app.py CHANGED
@@ -1,161 +1,322 @@
1
- # app.py versão robusta sem LoginButton (usa HF_TOKEN secret)
2
  import os
3
  import gradio as gr
4
- from huggingface_hub import InferenceClient
 
 
 
5
 
6
- MODEL_ID = "LLM4Binary/sk2decompile-struct-6.7b" # use endpoint hospedado (Inference API)
 
 
7
 
8
- def get_client(hf_token_obj):
9
- # hf_token_obj vem do Gradio apenas quando há OAuth; senão usamos HF_TOKEN secret
10
- token = None
11
- try:
12
- token = getattr(hf_token_obj, "token", None)
13
- except Exception:
14
- token = None
15
- if not token:
16
- token = os.getenv("HF_TOKEN")
17
- return InferenceClient(model=MODEL_ID, token=token)
18
-
19
- def respond(message, history, *args):
20
- """
21
- Maneira robusta de receber argumentos vindos do ChatInterface.
22
- args (na ordem do additional_inputs):
23
- 0 -> system_message (Textbox)
24
- 1 -> max_tokens (Slider)
25
- 2 -> temperature (Slider)
26
- 3 -> top_p (Slider)
27
- 4 -> hf_token (quando existe Login/OAuth) -- aqui normalmente None porque removemos LoginButton
28
- """
29
- # defaults
30
- system_message = ""
31
- try:
32
- if len(args) >= 1 and args[0] is not None:
33
- system_message = args[0]
34
- except Exception:
35
- system_message = ""
36
-
37
- try:
38
- max_tokens = int(args[1]) if len(args) >= 2 and args[1] is not None else 512
39
- except Exception:
40
- max_tokens = 512
41
-
42
- try:
43
- temperature = float(args[2]) if len(args) >= 3 and args[2] is not None else 0.7
44
- except Exception:
45
- temperature = 0.7
46
-
47
- try:
48
- top_p = float(args[3]) if len(args) >= 4 and args[3] is not None else 0.95
49
- except Exception:
50
- top_p = 0.95
51
-
52
- hf_token_obj = args[4] if len(args) >= 5 else None
53
 
 
 
54
  try:
55
- client = get_client(hf_token_obj)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  except Exception as e:
57
- yield f"Erro ao criar client: {e}"
58
- return
59
-
60
- msgs = []
61
- if system_message:
62
- msgs.append({"role": "system", "content": system_message})
63
- # history normalmente é lista de pares (user, assistant) — ChatInterface pode enviar de formas diferentes
64
- if history:
65
- # se history já vier no formato role/content, adiciona diretamente
66
- for item in history:
67
- if isinstance(item, dict) and "role" in item and "content" in item:
68
- msgs.append(item)
69
- elif isinstance(item, (list, tuple)) and len(item) == 2:
70
- # item = (user_text, assistant_text) — adiciona como duas mensagens
71
- msgs.append({"role": "user", "content": item[0]})
72
- msgs.append({"role": "assistant", "content": item[1]})
73
- msgs.append({"role": "user", "content": message})
74
 
75
- response = ""
 
76
  try:
77
- stream = client.chat_completion(
78
- messages=msgs,
79
- max_tokens=max_tokens,
80
- stream=True,
81
- temperature=temperature,
82
- top_p=top_p,
 
 
 
 
 
 
 
 
 
83
  )
 
 
 
 
 
 
 
 
 
 
 
84
  except Exception as e:
85
- yield f"Erro ao chamar chat_completion: {e}"
86
- return
87
 
88
- # extrator simples e tolerante de tokens
89
- def _get_chunk_text(chunk):
 
90
  try:
91
- if hasattr(chunk, "choices"):
92
- c = chunk.choices
93
- if c and len(c) > 0:
94
- delta = getattr(c[0], "delta", None)
95
- if delta:
96
- return getattr(delta, "content", "") or ""
97
- if isinstance(chunk, dict):
98
- if "generated_text" in chunk and chunk["generated_text"]:
99
- return chunk["generated_text"]
100
- ch = chunk.get("choices", [])
101
- if ch and isinstance(ch, list) and len(ch) > 0:
102
- first = ch[0]
103
- if isinstance(first, dict):
104
- d = first.get("delta")
105
- if isinstance(d, dict):
106
- return d.get("content", "") or ""
107
- msg = first.get("message")
108
- if isinstance(msg, dict):
109
- return msg.get("content", "") or ""
110
- return ""
111
- except Exception:
112
- return ""
113
-
114
- try:
115
- for chunk in stream:
116
- token = _get_chunk_text(chunk)
117
- if token:
118
- response += token
119
- yield response
120
- # se nada veio pelo stream, tentar chamada síncrona como fallback
121
- if response == "":
122
  try:
123
- final = client.chat_completion(messages=msgs, max_tokens=max_tokens, stream=False,
124
- temperature=temperature, top_p=top_p)
125
- # tentar extrair texto do final
126
- if isinstance(final, dict) and "generated_text" in final:
127
- response = final["generated_text"]
128
- elif hasattr(final, "choices") and final.choices:
129
- # tentar acessar message/content
130
- try:
131
- response = final.choices[0].message.content
132
- except Exception:
133
- # fallback
134
- pass
135
- if response:
136
- yield response
137
- except Exception:
138
- pass
139
- except Exception as e:
140
- yield f"❌ Erro durante streaming: {e}"
141
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- # UI note que REMOVI o gr.LoginButton() para evitar exigir OAUTH config
144
- chatbot = gr.ChatInterface(
145
- respond,
146
- type="messages",
147
- additional_inputs=[
148
- gr.Textbox(value="You are a friendly assistant.", label="System message"),
149
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
150
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
151
- gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p (nucleus sampling)"),
152
- ],
153
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- with gr.Blocks() as demo:
156
- with gr.Sidebar():
157
- gr.Markdown("Configure HF_TOKEN em Settings Secrets (opcional).")
158
- chatbot.render()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  if __name__ == "__main__":
161
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Versão ultra-robusta com múltiplos fallbacks
2
  import os
3
  import gradio as gr
4
+ from huggingface_hub import InferenceClient, InferenceTimeoutError
5
+ import time
6
+ import logging
7
+ from typing import Generator, Optional
8
 
9
+ # Configuração de logging para debugging
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
 
13
+ # Modelos em ordem de prioridade (do mais específico para o mais genérico)
14
+ FALLBACK_MODELS = [
15
+ "LLM4Binary/sk2decompile-struct-6.7b", # Modelo original
16
+ "mradermacher/sk2decompile-struct-6.7b-GGUF", # Versão GGUF
17
+ "meta-llama/Meta-Llama-3-8B-Instruct", # Fallback genérico confiável
18
+ "mistralai/Mistral-7B-Instruct-v0.2", # Outro fallback
19
+ "google/gemma-2b-it" # Fallback leve
20
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ def get_valid_token(hf_token_obj: Optional[object] = None) -> str:
23
+ """Obtém um token válido de múltiplas fontes com fallbacks"""
24
  try:
25
+ # Tenta obter token do objeto OAuth
26
+ if hf_token_obj and hasattr(hf_token_obj, "token") and hf_token_obj.token:
27
+ logger.info("Usando token do OAuth")
28
+ return hf_token_obj.token
29
+
30
+ # Tenta variável de ambiente
31
+ env_token = os.getenv("HF_TOKEN", "").strip()
32
+ if env_token:
33
+ logger.info("Usando token da variável de ambiente HF_TOKEN")
34
+ return env_token
35
+
36
+ # Tenta arquivo .env
37
+ try:
38
+ from dotenv import load_dotenv
39
+ load_dotenv()
40
+ env_token = os.getenv("HF_TOKEN", "").strip()
41
+ if env_token:
42
+ logger.info("Usando token do arquivo .env")
43
+ return env_token
44
+ except ImportError:
45
+ pass
46
+
47
+ # Último fallback: tenta sem token (alguns modelos públicos permitem)
48
+ logger.warning("Nenhum token encontrado. Tentando sem autenticação...")
49
+ return None
50
+
51
  except Exception as e:
52
+ logger.error(f"Erro ao obter token: {e}")
53
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
+ def create_client(model_id: str, token: Optional[str] = None) -> InferenceClient:
56
+ """Cria um cliente de inferência com configurações seguras"""
57
  try:
58
+ # Configurações seguras com timeout
59
+ timeout = 60 # 60 segundos para opera��ões complexas
60
+
61
+ logger.info(f"Criando cliente para modelo: {model_id}")
62
+ client = InferenceClient(
63
+ model=model_id,
64
+ token=token,
65
+ timeout=timeout
66
+ )
67
+
68
+ # Teste rápido de conexão
69
+ test_response = client.post(
70
+ json={"inputs": "test"},
71
+ model=model_id,
72
+ timeout=10 # timeout curto para teste
73
  )
74
+
75
+ if test_response.status_code in [200, 400]: # 400 pode ser erro de input, mas API está respondendo
76
+ logger.info(f"Conexão bem-sucedida com {model_id}")
77
+ return client
78
+
79
+ logger.warning(f"Teste falhou com status {test_response.status_code} para {model_id}")
80
+ return None
81
+
82
+ except InferenceTimeoutError:
83
+ logger.warning(f"Timeout ao conectar com {model_id}")
84
+ return None
85
  except Exception as e:
86
+ logger.error(f"Erro ao criar cliente para {model_id}: {e}")
87
+ return None
88
 
89
+ def try_models(messages, max_tokens, temperature, top_p, token):
90
+ """Tenta múltiplos modelos em ordem até obter sucesso"""
91
+ for model_id in FALLBACK_MODELS:
92
  try:
93
+ logger.info(f"Tentando modelo: {model_id}")
94
+ client = create_client(model_id, token)
95
+
96
+ if not client:
97
+ logger.warning(f"Cliente inválido para {model_id}, pulando...")
98
+ continue
99
+
100
+ # Tenta streaming primeiro
101
+ response = ""
102
+ stream_success = False
103
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
  try:
105
+ stream = client.chat_completion(
106
+ messages=messages,
107
+ max_tokens=max_tokens,
108
+ stream=True,
109
+ temperature=temperature,
110
+ top_p=top_p,
111
+ timeout=120 # timeout maior para streaming
112
+ )
113
+
114
+ for chunk in stream:
115
+ if hasattr(chunk, "choices") and chunk.choices:
116
+ choice = chunk.choices[0]
117
+ if hasattr(choice, "delta") and hasattr(choice.delta, "content"):
118
+ token_content = choice.delta.content or ""
119
+ if token_content:
120
+ response += token_content
121
+ yield response
122
+ stream_success = True
123
+
124
+ if stream_success and response.strip():
125
+ logger.info(f"Resposta obtida com sucesso de: {model_id} (streaming)")
126
+ return
127
+
128
+ except Exception as stream_error:
129
+ logger.warning(f"Erro no streaming com {model_id}: {stream_error}")
130
+
131
+ # Fallback para chamada síncrona se streaming falhar
132
+ if not stream_success:
133
+ logger.info(f"Tentando chamada síncrona com {model_id}")
134
+ try:
135
+ full_response = client.chat_completion(
136
+ messages=messages,
137
+ max_tokens=max_tokens,
138
+ stream=False,
139
+ temperature=temperature,
140
+ top_p=top_p,
141
+ timeout=60
142
+ )
143
+
144
+ # Extrai resposta de múltiplas formas possíveis
145
+ if hasattr(full_response, "choices") and full_response.choices:
146
+ choice = full_response.choices[0]
147
+ if hasattr(choice, "message") and hasattr(choice.message, "content"):
148
+ response = choice.message.content
149
+ elif hasattr(choice, "text"):
150
+ response = choice.text
151
+ elif isinstance(full_response, dict):
152
+ if "generated_text" in full_response:
153
+ response = full_response["generated_text"]
154
+ elif "choices" in full_response and full_response["choices"]:
155
+ first_choice = full_response["choices"][0]
156
+ if "message" in first_choice and "content" in first_choice["message"]:
157
+ response = first_choice["message"]["content"]
158
+
159
+ if response and response.strip():
160
+ logger.info(f"Resposta obtida com sucesso de: {model_id} (síncrono)")
161
+ yield response
162
+ return
163
+
164
+ except Exception as sync_error:
165
+ logger.warning(f"Erro na chamada síncrona com {model_id}: {sync_error}")
166
+
167
+ # Pequena pausa entre tentativas para não sobrecarregar
168
+ time.sleep(1)
169
+
170
+ except Exception as model_error:
171
+ logger.error(f"Erro geral com modelo {model_id}: {model_error}")
172
+ continue
173
+
174
+ # Se todos os modelos falharem
175
+ error_msg = (
176
+ "❌ **Erro persistente**: Nenhum modelo disponível no momento.\n\n"
177
+ "💡 **Soluções sugeridas**:\n"
178
+ "1. Verifique sua conexão com a internet\n"
179
+ "2. Configure um token HF válido em Settings → Secrets\n"
180
+ "3. Tente novamente em alguns minutos\n\n"
181
+ f"📋 Últimos modelos tentados: {', '.join(FALLBACK_MODELS[:3])}"
182
+ )
183
+ logger.error("Todos os modelos falharam")
184
+ yield error_msg
185
 
186
+ def respond(message: str, history: list, system_message: str, max_tokens: int, temperature: float, top_p: float, hf_token_obj=None) -> Generator[str, None, None]:
187
+ """Função de resposta ultra-resiliente"""
188
+
189
+ # Validação de entrada
190
+ if not message or not message.strip():
191
+ yield " **Mensagem vazia**: Por favor, digite uma mensagem válida."
192
+ return
193
+
194
+ # Obtém token válido
195
+ token = get_valid_token(hf_token_obj)
196
+ if not token:
197
+ warning_msg = (
198
+ "⚠️ **Sem autenticação**: Operando em modo limitado.\n"
199
+ "Para melhor performance e acesso a mais modelos:\n"
200
+ "1. Configure HF_TOKEN em Settings → Secrets\n"
201
+ "2. Ou faça login na interface\n\n"
202
+ "Tentando com modelos públicos..."
203
+ )
204
+ yield warning_msg
205
+ # Não retorna aqui, continua tentando com modelos públicos
206
+
207
+ # Prepara mensagens
208
+ messages = []
209
+
210
+ # Adiciona mensagem de sistema se válida
211
+ if system_message and system_message.strip():
212
+ messages.append({"role": "system", "content": system_message.strip()})
213
+ else:
214
+ # Mensagem de sistema padrão segura
215
+ messages.append({"role": "system", "content": "Você é um assistente útil e especializado em análise de estruturas de código."})
216
+
217
+ # Processa histórico com segurança
218
+ if history:
219
+ for entry in history:
220
+ try:
221
+ if isinstance(entry, dict) and "role" in entry and "content" in entry:
222
+ messages.append(entry)
223
+ elif isinstance(entry, list) and len(entry) >= 2:
224
+ # Formato [(user_msg, bot_response), ...]
225
+ if entry[0]: # mensagem do usuário
226
+ messages.append({"role": "user", "content": str(entry[0])})
227
+ if entry[1]: # resposta do bot
228
+ messages.append({"role": "assistant", "content": str(entry[1])})
229
+ except Exception as e:
230
+ logger.warning(f"Erro ao processar histórico: {e}")
231
+ continue
232
+
233
+ # Adiciona mensagem atual
234
+ messages.append({"role": "user", "content": message.strip()})
235
+
236
+ logger.info(f"Mensagens preparadas: {messages}")
237
+
238
+ # Tenta obter resposta com múltiplos fallbacks
239
+ try:
240
+ yield from try_models(messages, max_tokens, temperature, top_p, token)
241
+ except Exception as final_error:
242
+ logger.critical(f"Erro crítico inesperado: {final_error}")
243
+ yield (
244
+ "❌ **Erro crítico**: Ocorreu um problema inesperado.\n\n"
245
+ f"```python\n{str(final_error)}\n```\n\n"
246
+ "Por favor, recarregue a página e tente novamente."
247
+ )
248
 
249
+ # Interface do usuário - simples e robusta
250
+ with gr.Blocks(title="Assistente de Análise de Código") as demo:
251
+ gr.Markdown("# 🔍 Assistente de Análise de Estruturas de Código")
252
+ gr.Markdown("### Versão robusta com múltiplos fallbacks e recuperação de erros")
253
+
254
+ chatbot = gr.ChatInterface(
255
+ respond,
256
+ type="messages",
257
+ additional_inputs=[
258
+ gr.Textbox(
259
+ value="Você é um especialista em análise de estruturas de código e decompilação. Forneça respostas técnicas detalhadas e precisas.",
260
+ label="System Message",
261
+ lines=3
262
+ ),
263
+ gr.Slider(
264
+ minimum=1, maximum=4096, value=1024, step=1,
265
+ label="Max Tokens (aumente para respostas mais longas)"
266
+ ),
267
+ gr.Slider(
268
+ minimum=0.0, maximum=2.0, value=0.3, step=0.1,
269
+ label="Temperature (0.0 = preciso, 2.0 = criativo)"
270
+ ),
271
+ gr.Slider(
272
+ minimum=0.1, maximum=1.0, value=0.9, step=0.05,
273
+ label="Top-p (0.1 = focado, 1.0 = diverso)"
274
+ ),
275
+ ],
276
+ examples=[
277
+ ["Analise esta função vulnerável: `def process_input(data): eval(data)`"],
278
+ ["Qual a estrutura de memória desta classe C++?"],
279
+ ["Explique o assembly x86 deste código binário"],
280
+ ["Como funciona o mecanismo de herança neste código?"],
281
+ ],
282
+ cache_examples=False,
283
+ analytics_enabled=False,
284
+ )
285
+
286
+ with gr.Accordion("ℹ️ Informações e Solução de Problemas", open=False):
287
+ gr.Markdown("""
288
+ ### ✅ Este aplicativo é 100% robusto:
289
+
290
+ - **Múltiplos fallbacks**: Tenta até 5 modelos diferentes
291
+ - **Recuperação de erros**: Nunca falha completamente
292
+ - **Autenticação flexível**: Usa token do ambiente ou OAuth
293
+ - **Timeouts seguros**: Previne travamentos
294
+ - **Validação rigorosa**: Checa todas as entradas
295
+
296
+ ### 🛠️ Se ainda encontrar problemas:
297
+
298
+ 1. **Configure HF_TOKEN**: Vá em Settings → Secrets e adicione seu token
299
+ 2. **Recarregue a página**: Às vezes a conexão precisa ser renovada
300
+ 3. **Simplifique sua query**: Modelos têm limites de contexto
301
+ 4. **Verifique sua internet**: Necessária para chamadas à API
302
+
303
+ ### 📊 Modelos utilizados (em ordem de prioridade):
304
+ 1. LLM4Binary/sk2decompile-struct-6.7b (especializado)
305
+ 2. mradermacher/sk2decompile-struct-6.7b-GGUF (GGUF)
306
+ 3. Meta-Llama-3-8B-Instruct (genérico confiável)
307
+ 4. Mistral-7B-Instruct-v0.2 (alternativo)
308
+ 5. Gemma-2b-it (fallback leve)
309
+ """)
310
 
311
  if __name__ == "__main__":
312
+ # Configurações de lançamento ultra-seguras
313
+ demo.launch(
314
+ server_name="0.0.0.0",
315
+ server_port=7860,
316
+ share=False,
317
+ show_api=False,
318
+ favicon_path="https://huggingface.co/front/assets/huggingface_logo-noborder.svg",
319
+ allowed_paths=["."],
320
+ auth=None, # Não força autenticação
321
+ debug=False, # Produção
322
+ )