Spaces:
Running
Running
Update ai_logic.py
Browse files- ai_logic.py +204 -418
ai_logic.py
CHANGED
@@ -16,94 +16,33 @@ BLOG_URL = "https://aldohenrique.com.br/"
|
|
16 |
VECTOR_STORE_PATH = "faiss_index_store.pkl"
|
17 |
PROCESSED_URLS_PATH = "processed_urls.pkl"
|
18 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
19 |
-
if not HF_TOKEN:
|
20 |
-
raise ValueError("Token HF_TOKEN não encontrado ")
|
21 |
|
22 |
-
#
|
23 |
-
|
24 |
-
("
|
25 |
-
|
26 |
-
|
27 |
-
headers = {
|
28 |
-
"Authorization": f"Bearer {HF_TOKEN}"
|
29 |
-
}
|
30 |
-
|
31 |
-
# Modelos fixos que você quer manter
|
32 |
-
MODELS = {
|
33 |
-
"Microsoft DialoGPT": "microsoft/DialoGPT-medium",
|
34 |
-
"Google T5 Small": "google/flan-t5-small",
|
35 |
-
"Google T5 Base": "google/flan-t5-base",
|
36 |
-
"Facebook BART": "facebook/bart-base",
|
37 |
-
"TinyLlama 1B": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
38 |
-
"Phi-3 Mini": "microsoft/Phi-3-mini-4k-instruct",
|
39 |
-
"Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
|
40 |
-
"Llama 1B": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
|
41 |
-
"IBM 2B": "ibm-granite/granite-speech-3.3-2b",
|
42 |
-
"IBM 8B": "ibm-granite/granite-speech-3.3-8b",
|
43 |
-
"Phi-3 Mini (Mais rápido)": "microsoft/Phi-3-mini-4k-instruct",
|
44 |
-
"Zephyr 7B (Meio Termo)": "HuggingFaceH4/zephyr-7b-beta",
|
45 |
-
"Microsoft 8B (Meio Termo)": "meta-llama/Meta-Llama-3-8B-Instruct",
|
46 |
-
"Mistral-7B": "mistralai/Mistral-7B-Instruct-v0.3",
|
47 |
-
"DialoGPT": "microsoft/DialoGPT-medium",
|
48 |
-
"Google": "google/flan-t5-base",
|
49 |
-
"Facebook": "facebook/bart-large-cnn"
|
50 |
-
}
|
51 |
-
# --- Consulta a API da Hugging Face com tratamento de erro melhorado ---
|
52 |
-
def fetch_models_from_api():
|
53 |
-
"""Busca modelos adicionais da API do Hugging Face com tratamento robusto."""
|
54 |
-
url = "https://huggingface.co/api/models"
|
55 |
-
params = {
|
56 |
-
"filter": "text-generation",
|
57 |
-
"sort": "downloads",
|
58 |
-
"direction": -1,
|
59 |
-
"limit": 20,
|
60 |
-
"full": True
|
61 |
-
}
|
62 |
-
|
63 |
-
try:
|
64 |
-
response = requests.get(url, headers=headers, params=params, timeout=30)
|
65 |
-
|
66 |
-
if response.status_code == 200:
|
67 |
-
models_data = response.json()
|
68 |
-
|
69 |
-
# Filtra modelos que possuem base_model
|
70 |
-
for model in models_data:
|
71 |
-
try:
|
72 |
-
tags = model.get("tags", [])
|
73 |
-
model_name = model.get("id")
|
74 |
-
|
75 |
-
if not model_name:
|
76 |
-
continue
|
77 |
-
|
78 |
-
# Verifica se o modelo é adequado
|
79 |
-
if any(tag in tags for tag in ["text-generation", "conversational"]):
|
80 |
-
display_name = model_name.split("/")[-1]
|
81 |
-
|
82 |
-
# Verifica se já não está na lista para evitar duplicados
|
83 |
-
if not any(model_name == m[1] for m in NEW_MODELS_TO_TEST):
|
84 |
-
NEW_MODELS_TO_TEST.append((display_name, model_name))
|
85 |
-
|
86 |
-
except Exception as e:
|
87 |
-
print(f"Erro ao processar modelo {model.get('id', 'unknown')}: {e}")
|
88 |
-
continue
|
89 |
-
|
90 |
-
else:
|
91 |
-
print(f"Erro na API do Hugging Face: {response.status_code}")
|
92 |
-
|
93 |
-
except Exception as e:
|
94 |
-
print(f"Erro ao buscar modelos da API: {e}")
|
95 |
-
print("Continuando com a lista de modelos predefinida...")
|
96 |
-
|
97 |
-
# Executa a busca de modelos
|
98 |
-
fetch_models_from_api()
|
99 |
|
100 |
-
print("
|
101 |
-
for name, model_id in NEW_MODELS_TO_TEST[:10]: # Mostra apenas os primeiros 10
|
102 |
-
print(f'("{name}", "{model_id}"),')
|
103 |
|
104 |
-
|
|
|
105 |
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
|
108 |
# --- Gerenciamento de Sessão ---
|
109 |
user_sessions: Dict[str, Dict[str, List | Dict]] = {}
|
@@ -238,29 +177,43 @@ def scrape_text_from_url(url: str) -> str:
|
|
238 |
def build_and_save_vector_store():
|
239 |
"""Constrói e salva o vector store."""
|
240 |
global vector_store
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
return f"Vector store criado com {len(chunks)} chunks."
|
256 |
|
257 |
def load_vector_store():
|
258 |
"""Carrega o vector store."""
|
259 |
global vector_store
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
build_and_save_vector_store()
|
265 |
|
266 |
def retrieve_context_from_blog(query: str, k: int = 4) -> str:
|
@@ -273,368 +226,174 @@ def retrieve_context_from_blog(query: str, k: int = 4) -> str:
|
|
273 |
print(f"Erro ao buscar contexto: {e}")
|
274 |
return ""
|
275 |
|
276 |
-
# --- API Client ---
|
277 |
class HuggingFaceAPIClient:
|
278 |
def __init__(self, token: str):
|
279 |
-
self.headers = {"Authorization": f"Bearer {token}"
|
280 |
-
self.
|
281 |
-
self.base_info_url = "https://huggingface.co/api/models"
|
282 |
|
283 |
-
def
|
284 |
-
"""Verifica
|
285 |
-
url = f"{self.
|
286 |
-
try:
|
287 |
-
response = requests.get(url, headers=self.headers, timeout=15)
|
288 |
-
if response.status_code == 200:
|
289 |
-
model_info = response.json()
|
290 |
-
|
291 |
-
# Verifica se o modelo está desabilitado
|
292 |
-
if model_info.get('disabled', False):
|
293 |
-
return False, "Modelo desabilitado"
|
294 |
-
|
295 |
-
# Verifica se o modelo requer aprovação
|
296 |
-
if model_info.get('gated', False):
|
297 |
-
return False, "Modelo requer aprovação/aceite de licença"
|
298 |
-
|
299 |
-
# Verifica se o modelo existe mas não tem pipeline de text-generation
|
300 |
-
pipeline_tag = model_info.get('pipeline_tag')
|
301 |
-
if pipeline_tag and pipeline_tag not in ['text-generation', 'text2text-generation', 'conversational']:
|
302 |
-
return False, f"Modelo não suporta geração de texto (pipeline: {pipeline_tag})"
|
303 |
-
|
304 |
-
return True, "Modelo disponível"
|
305 |
-
|
306 |
-
elif response.status_code == 404:
|
307 |
-
return False, "Modelo não encontrado"
|
308 |
-
elif response.status_code == 401:
|
309 |
-
return False, "Token inválido ou sem permissão"
|
310 |
-
elif response.status_code == 403:
|
311 |
-
return False, "Acesso negado ao modelo"
|
312 |
-
else:
|
313 |
-
return False, f"Erro HTTP {response.status_code}"
|
314 |
-
|
315 |
-
except requests.exceptions.Timeout:
|
316 |
-
return False, "Timeout na verificação do modelo"
|
317 |
-
except requests.exceptions.RequestException as e:
|
318 |
-
return False, f"Erro na requisição: {str(e)}"
|
319 |
-
|
320 |
-
def test_model_inference(self, model_name: str) -> Tuple[bool, str]:
|
321 |
-
"""Testa se o modelo está disponível para inferência."""
|
322 |
-
url = f"{self.base_api_url}/{model_name}"
|
323 |
|
324 |
-
#
|
325 |
test_payload = {
|
326 |
"inputs": "Hello",
|
327 |
"parameters": {
|
328 |
"max_new_tokens": 5,
|
329 |
"temperature": 0.1,
|
330 |
-
"do_sample": False,
|
331 |
"return_full_text": False
|
332 |
}
|
333 |
}
|
334 |
|
335 |
try:
|
336 |
-
|
|
|
337 |
|
338 |
if response.status_code == 200:
|
339 |
result = response.json()
|
340 |
-
|
341 |
-
# Verifica diferentes formatos de resposta
|
342 |
if isinstance(result, list) and len(result) > 0:
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
return True, "Modelo disponível para inferência"
|
349 |
-
elif 'error' in result:
|
350 |
-
error_msg = result['error']
|
351 |
-
if 'loading' in error_msg.lower() or 'currently loading' in error_msg.lower():
|
352 |
-
return False, "Modelo está carregando"
|
353 |
-
return False, f"Erro do modelo: {error_msg}"
|
354 |
-
|
355 |
-
return False, f"Formato de resposta inesperado: {str(result)[:200]}"
|
356 |
|
357 |
elif response.status_code == 503:
|
358 |
-
|
359 |
-
error_data = response.json()
|
360 |
-
error_msg = error_data.get('error', 'Serviço indisponível')
|
361 |
-
if 'loading' in error_msg.lower():
|
362 |
-
return False, "Modelo está carregando"
|
363 |
-
return False, f"Serviço indisponível: {error_msg}"
|
364 |
-
except:
|
365 |
-
return False, "Modelo está carregando (503)"
|
366 |
-
|
367 |
-
elif response.status_code == 400:
|
368 |
-
try:
|
369 |
-
error_data = response.json()
|
370 |
-
error_msg = error_data.get('error', 'Erro 400')
|
371 |
-
if 'loading' in error_msg.lower():
|
372 |
-
return False, "Modelo está carregando"
|
373 |
-
elif 'not supported' in error_msg.lower():
|
374 |
-
return False, "Tipo de requisição não suportado"
|
375 |
-
return False, f"Erro 400: {error_msg}"
|
376 |
-
except:
|
377 |
-
return False, "Erro de requisição malformada"
|
378 |
-
|
379 |
elif response.status_code == 401:
|
380 |
-
return False, "Token inválido
|
381 |
-
elif response.status_code ==
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
return False, "Limite de requisições excedido"
|
387 |
else:
|
388 |
-
|
389 |
-
error_data = response.json()
|
390 |
-
error_msg = error_data.get('error', response.text)
|
391 |
-
return False, f"Erro HTTP {response.status_code}: {error_msg}"
|
392 |
-
except:
|
393 |
-
return False, f"Erro HTTP {response.status_code}: {response.text[:200]}"
|
394 |
|
395 |
except requests.exceptions.Timeout:
|
396 |
-
return False, "Timeout
|
397 |
except requests.exceptions.RequestException as e:
|
398 |
-
return False, f"Erro
|
399 |
-
|
400 |
-
|
401 |
-
"""Testa se um modelo está disponível, combinando verificação de info e inferência."""
|
402 |
-
print(f"Testando modelo: {model_name}")
|
403 |
-
|
404 |
-
# Primeiro verifica as informações do modelo
|
405 |
-
info_available, info_msg = self.check_model_info(model_name)
|
406 |
-
if not info_available:
|
407 |
-
print(f" ✗ Info check: {info_msg}")
|
408 |
-
return False, f"Info check failed: {info_msg}"
|
409 |
-
|
410 |
-
print(f" ✓ Info check: {info_msg}")
|
411 |
-
|
412 |
-
# Em seguida testa a inferência
|
413 |
-
inference_available, inference_msg = self.test_model_inference(model_name)
|
414 |
-
|
415 |
-
if inference_available:
|
416 |
-
print(f" ✓ Inference check: {inference_msg}")
|
417 |
-
return True, f"Disponível - {info_msg}"
|
418 |
-
else:
|
419 |
-
print(f" ✗ Inference check: {inference_msg}")
|
420 |
-
return False, f"Não disponível para inferência: {inference_msg}"
|
421 |
|
422 |
-
def query_model(self, model_name: str, messages: List[Dict], max_tokens: int =
|
423 |
-
"""Faz requisição ao modelo
|
|
|
424 |
prompt = self._convert_messages_to_prompt(messages)
|
425 |
|
426 |
-
url = f"{self.
|
427 |
-
|
428 |
-
# Configurações otimizadas para diferentes tipos de modelo
|
429 |
payload = {
|
430 |
"inputs": prompt,
|
431 |
"parameters": {
|
432 |
-
"max_new_tokens":
|
433 |
"temperature": 0.7,
|
434 |
-
"top_p": 0.9,
|
435 |
"do_sample": True,
|
436 |
-
"return_full_text": False
|
437 |
-
"repetition_penalty": 1.1,
|
438 |
-
"pad_token_id": 50256 # Token padrão para muitos modelos
|
439 |
-
},
|
440 |
-
"options": {
|
441 |
-
"wait_for_model": True,
|
442 |
-
"use_cache": False
|
443 |
}
|
444 |
}
|
445 |
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
if response.status_code == 200:
|
457 |
-
result = response.json()
|
458 |
-
|
459 |
-
# Trata diferentes formatos de resposta
|
460 |
-
if isinstance(result, list) and len(result) > 0:
|
461 |
-
generated_text = result[0].get('generated_text', '').strip()
|
462 |
-
if generated_text:
|
463 |
-
return generated_text
|
464 |
-
return "Resposta vazia do modelo"
|
465 |
-
|
466 |
-
elif isinstance(result, dict):
|
467 |
-
if 'generated_text' in result:
|
468 |
-
return result['generated_text'].strip()
|
469 |
-
elif 'error' in result:
|
470 |
-
return f"Erro do modelo: {result['error']}"
|
471 |
-
else:
|
472 |
-
return f"Formato inesperado: {str(result)[:300]}"
|
473 |
-
|
474 |
-
return f"Formato de resposta não reconhecido: {str(result)[:300]}"
|
475 |
-
|
476 |
-
elif response.status_code == 503:
|
477 |
-
if attempt < max_retries - 1:
|
478 |
-
wait_time = 5 * (attempt + 1)
|
479 |
-
print(f"Modelo carregando, aguardando {wait_time}s...")
|
480 |
-
time.sleep(wait_time)
|
481 |
-
continue
|
482 |
-
return "Modelo ainda está carregando após várias tentativas"
|
483 |
-
|
484 |
-
elif response.status_code == 400:
|
485 |
-
try:
|
486 |
-
error_data = response.json()
|
487 |
-
error_msg = error_data.get('error', 'Erro 400')
|
488 |
-
return f"Erro na requisição: {error_msg}"
|
489 |
-
except:
|
490 |
-
return "Erro na formatação da requisição"
|
491 |
-
|
492 |
-
elif response.status_code == 401:
|
493 |
-
return "Token de autenticação inválido ou expirado"
|
494 |
-
|
495 |
-
elif response.status_code == 403:
|
496 |
-
return "Acesso negado ao modelo (pode requerer aprovação)"
|
497 |
-
|
498 |
-
elif response.status_code == 429:
|
499 |
-
if attempt < max_retries - 1:
|
500 |
-
wait_time = 10 * (attempt + 1)
|
501 |
-
print(f"Rate limit atingido, aguardando {wait_time}s...")
|
502 |
-
time.sleep(wait_time)
|
503 |
-
continue
|
504 |
-
return "Limite de requisições excedido"
|
505 |
-
|
506 |
else:
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
return f"Erro HTTP {response.status_code}: {error_msg}"
|
511 |
-
except:
|
512 |
-
return f"Erro HTTP {response.status_code}: {response.text[:200]}"
|
513 |
-
|
514 |
-
except requests.exceptions.Timeout:
|
515 |
-
if attempt < max_retries - 1:
|
516 |
-
print(f"Timeout na tentativa {attempt + 1}, tentando novamente...")
|
517 |
-
time.sleep(5)
|
518 |
-
continue
|
519 |
-
return "Timeout: O modelo demorou muito para responder"
|
520 |
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
return "Falha após múltiplas tentativas"
|
525 |
|
526 |
def _convert_messages_to_prompt(self, messages: List[Dict]) -> str:
|
527 |
-
"""Converte mensagens
|
528 |
prompt_parts = []
|
529 |
-
|
530 |
for msg in messages:
|
531 |
role = msg['role']
|
532 |
content = msg['content']
|
533 |
|
534 |
if role == 'system':
|
535 |
-
prompt_parts.append(f"
|
536 |
elif role == 'user':
|
537 |
-
prompt_parts.append(f"
|
538 |
elif role == 'assistant':
|
539 |
-
prompt_parts.append(f"
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
return "\n".join(prompt_parts)
|
545 |
-
|
546 |
-
api_client = HuggingFaceAPIClient(HF_TOKEN)
|
547 |
|
548 |
# --- Função para Testar e Atualizar Modelos ---
|
549 |
def test_and_update_models() -> int:
|
550 |
-
"""
|
551 |
-
Testa a disponibilidade dos novos modelos e atualiza a lista MODELS.
|
552 |
-
Garante que o DEFAULT_MODEL seja sempre o primeiro da lista.
|
553 |
-
Retorna o número de modelos disponíveis.
|
554 |
-
"""
|
555 |
print("Testando disponibilidade dos modelos...")
|
556 |
print(f"Token HF disponível: {'Sim' if HF_TOKEN else 'Não'}")
|
557 |
print("-" * 60)
|
558 |
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
# Primeiro verifica o modelo padrão
|
563 |
-
default_label = "Mistral 7B"
|
564 |
-
default_name = "mistralai/Mistral-7B-Instruct-v0.3"
|
565 |
-
|
566 |
-
is_available, message = api_client.test_model_availability(default_name)
|
567 |
-
|
568 |
-
if is_available:
|
569 |
-
temp_models[default_label] = default_name
|
570 |
-
print(f"✓ {default_label} (DEFAULT MODEL)")
|
571 |
-
else:
|
572 |
-
print(f"✗ {default_label} - {message} (MODELO PADRÃO INDISPONÍVEL)")
|
573 |
-
|
574 |
-
# Depois verifica os outros modelos (limitando a quantidade para evitar rate limiting)
|
575 |
-
tested_count = 0
|
576 |
-
max_tests = 15 # Limita o número de testes
|
577 |
|
578 |
for model_label, model_name in NEW_MODELS_TO_TEST:
|
579 |
-
|
580 |
-
|
581 |
-
continue
|
582 |
-
|
583 |
-
if tested_count >= max_tests:
|
584 |
-
break
|
585 |
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
|
|
593 |
|
594 |
-
|
595 |
-
time.sleep(2)
|
596 |
|
597 |
-
# Atualiza MODELS
|
598 |
global MODELS
|
599 |
MODELS.clear()
|
600 |
-
|
601 |
-
# Adiciona primeiro o modelo padrão (se disponível)
|
602 |
-
if default_label in temp_models:
|
603 |
-
MODELS[default_label] = temp_models.pop(default_label)
|
604 |
-
|
605 |
-
# Adiciona os demais modelos
|
606 |
-
MODELS.update(temp_models)
|
607 |
|
608 |
print("\n" + "=" * 60)
|
609 |
-
print("MODELOS DISPONÍVEIS
|
610 |
print("=" * 60)
|
611 |
for i, (label, name) in enumerate(MODELS.items(), 1):
|
612 |
-
print(f"{i}. {label}")
|
613 |
|
614 |
-
print(f"\nTOTAL
|
615 |
print("=" * 60)
|
616 |
|
617 |
-
|
618 |
-
return len(MODELS)
|
619 |
-
|
620 |
-
def save_updated_models():
|
621 |
-
"""Salva a lista atualizada de modelos em um arquivo."""
|
622 |
try:
|
623 |
with open("models_available.json", "w", encoding="utf-8") as f:
|
624 |
json.dump(MODELS, f, ensure_ascii=False, indent=2)
|
625 |
-
print("Lista
|
626 |
except Exception as e:
|
627 |
-
print(f"Erro ao salvar lista
|
|
|
|
|
628 |
|
629 |
# --- Chat Principal ---
|
630 |
-
def responder_como_aldo(session_id: str, pergunta: str, modelo: str =
|
631 |
"""Gera resposta como Dr. Aldo Henrique."""
|
632 |
if not pergunta.strip():
|
633 |
return "Por favor, faça uma pergunta válida."
|
|
|
|
|
|
|
|
|
|
|
|
|
634 |
|
635 |
load_conversation_memory(session_id)
|
636 |
update_user_profile(session_id, pergunta)
|
637 |
|
|
|
638 |
contexto = []
|
639 |
if perfil := get_user_profile_context(session_id):
|
640 |
contexto.append(f"**Perfil do Usuário**\n{perfil}")
|
@@ -644,70 +403,97 @@ def responder_como_aldo(session_id: str, pergunta: str, modelo: str = DEFAULT_MO
|
|
644 |
contexto.append(f"**Contexto do Blog**\n{blog}")
|
645 |
|
646 |
system_prompt = """Você é o Dr. Aldo Henrique,
|
647 |
-
Doutor em Ciências da Computação pela UnB (2024),
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
-
|
652 |
-
|
653 |
-
|
654 |
-
-
|
655 |
-
-
|
656 |
-
-
|
657 |
-
-
|
658 |
-
-
|
|
|
659 |
"""
|
660 |
|
661 |
conteudo_contexto = "\n".join(contexto)
|
662 |
-
mensagem_usuario = f"{conteudo_contexto}\n\n**Pergunta**: {pergunta}"
|
663 |
|
664 |
messages = [
|
665 |
{"role": "system", "content": system_prompt},
|
666 |
{"role": "user", "content": mensagem_usuario}
|
667 |
]
|
668 |
|
669 |
-
#
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
model_name = MODELS.get(modelo, list(MODELS.values())[0] if MODELS else "mistralai/Mistral-7B-Instruct-v0.3")
|
674 |
resposta = api_client.query_model(model_name, messages)
|
|
|
|
|
675 |
add_to_memory(session_id, pergunta, resposta)
|
676 |
return resposta
|
677 |
|
678 |
# --- Inicialização ---
|
679 |
def inicializar_sistema():
|
680 |
-
"""
|
681 |
-
Inicializa o sistema, garantindo no mínimo 1 modelo disponível.
|
682 |
-
Retorna uma tupla: (status: bool, models: dict)
|
683 |
-
- status: True se >= 1 modelo disponível, False caso contrário
|
684 |
-
- models: Dicionário com os modelos disponíveis
|
685 |
-
"""
|
686 |
print("Inicializando Chatbot Dr. Aldo...")
|
|
|
687 |
|
|
|
688 |
num_available_models = test_and_update_models()
|
689 |
|
690 |
if num_available_models >= 1:
|
691 |
-
|
692 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
693 |
return True, MODELS
|
694 |
else:
|
695 |
-
print(f"Erro:
|
|
|
|
|
|
|
|
|
696 |
return False, MODELS
|
697 |
|
|
|
698 |
if __name__ == "__main__":
|
699 |
status, models = inicializar_sistema()
|
|
|
700 |
if status:
|
701 |
print("\n" + "="*50)
|
702 |
-
print("
|
703 |
print("="*50)
|
|
|
704 |
session_id = "teste_123"
|
705 |
-
|
706 |
-
|
707 |
-
print(
|
708 |
-
|
709 |
-
print(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
710 |
else:
|
711 |
-
print("\
|
712 |
-
print(
|
713 |
-
print("
|
|
|
|
16 |
VECTOR_STORE_PATH = "faiss_index_store.pkl"
|
17 |
PROCESSED_URLS_PATH = "processed_urls.pkl"
|
18 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
|
|
|
|
19 |
|
20 |
+
# Validação do token com mensagem mais clara
|
21 |
+
if not HF_TOKEN:
|
22 |
+
print("ERRO: Token HF_TOKEN não encontrado!")
|
23 |
+
print("Execute: export HF_TOKEN='seu_token_aqui' ou defina como variável de ambiente")
|
24 |
+
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
+
print(f"Token HF encontrado: {HF_TOKEN[:10]}...")
|
|
|
|
|
27 |
|
28 |
+
# --- Modelos para teste (versão simplificada e mais robusta) ---
|
29 |
+
MODELS = {}
|
30 |
|
31 |
+
# Lista de modelos mais estáveis e com maior chance de funcionar
|
32 |
+
NEW_MODELS_TO_TEST = [
|
33 |
+
("GPT-2", "gpt2"),
|
34 |
+
("DistilGPT-2", "distilgpt2"),
|
35 |
+
("GPT-2 Medium", "gpt2-medium"),
|
36 |
+
("Microsoft DialoGPT", "microsoft/DialoGPT-medium"),
|
37 |
+
("Google T5 Small", "google/flan-t5-small"),
|
38 |
+
("Google T5 Base", "google/flan-t5-base"),
|
39 |
+
("Facebook BART", "facebook/bart-base"),
|
40 |
+
("TinyLlama 1B", "TinyLlama/TinyLlama-1.1B-Chat-v1.0"),
|
41 |
+
("Phi-3 Mini", "microsoft/Phi-3-mini-4k-instruct"),
|
42 |
+
("Mistral 7B", "mistralai/Mistral-7B-Instruct-v0.3"),
|
43 |
+
]
|
44 |
+
|
45 |
+
DEFAULT_MODEL = "GPT-2"
|
46 |
|
47 |
# --- Gerenciamento de Sessão ---
|
48 |
user_sessions: Dict[str, Dict[str, List | Dict]] = {}
|
|
|
177 |
def build_and_save_vector_store():
|
178 |
"""Constrói e salva o vector store."""
|
179 |
global vector_store
|
180 |
+
print("Construindo vector store...")
|
181 |
+
try:
|
182 |
+
links = get_all_blog_links(BLOG_URL)
|
183 |
+
texts = [scrape_text_from_url(link) for link in links if scrape_text_from_url(link)]
|
184 |
+
if not texts:
|
185 |
+
print("Nenhum conteúdo encontrado no blog.")
|
186 |
+
return "Nenhum conteúdo encontrado."
|
187 |
+
|
188 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
|
189 |
+
chunks = text_splitter.create_documents(texts)
|
190 |
+
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
191 |
+
vector_store = FAISS.from_documents(chunks, embeddings)
|
192 |
|
193 |
+
with open(VECTOR_STORE_PATH, "wb") as f:
|
194 |
+
pickle.dump(vector_store, f)
|
195 |
+
with open(PROCESSED_URLS_PATH, "wb") as f:
|
196 |
+
pickle.dump(links, f)
|
197 |
+
print(f"Vector store criado com {len(chunks)} chunks.")
|
198 |
+
return f"Vector store criado com {len(chunks)} chunks."
|
199 |
+
except Exception as e:
|
200 |
+
print(f"Erro ao construir vector store: {e}")
|
201 |
+
return f"Erro ao construir vector store: {e}"
|
|
|
202 |
|
203 |
def load_vector_store():
|
204 |
"""Carrega o vector store."""
|
205 |
global vector_store
|
206 |
+
try:
|
207 |
+
if os.path.exists(VECTOR_STORE_PATH):
|
208 |
+
with open(VECTOR_STORE_PATH, "rb") as f:
|
209 |
+
vector_store = pickle.load(f)
|
210 |
+
print("Vector store carregado com sucesso.")
|
211 |
+
else:
|
212 |
+
print("Vector store não encontrado. Criando novo...")
|
213 |
+
build_and_save_vector_store()
|
214 |
+
except Exception as e:
|
215 |
+
print(f"Erro ao carregar vector store: {e}")
|
216 |
+
print("Tentando criar novo vector store...")
|
217 |
build_and_save_vector_store()
|
218 |
|
219 |
def retrieve_context_from_blog(query: str, k: int = 4) -> str:
|
|
|
226 |
print(f"Erro ao buscar contexto: {e}")
|
227 |
return ""
|
228 |
|
229 |
+
# --- API Client (Versão Melhorada) ---
|
230 |
class HuggingFaceAPIClient:
|
231 |
def __init__(self, token: str):
|
232 |
+
self.headers = {"Authorization": f"Bearer {token}"}
|
233 |
+
self.base_url = "https://api-inference.huggingface.co/models/"
|
|
|
234 |
|
235 |
+
def check_model_status(self, model_name: str) -> Tuple[bool, str]:
|
236 |
+
"""Verifica se um modelo está disponível via API."""
|
237 |
+
url = f"{self.base_url}{model_name}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
|
239 |
+
# Teste simples para verificar se o modelo responde
|
240 |
test_payload = {
|
241 |
"inputs": "Hello",
|
242 |
"parameters": {
|
243 |
"max_new_tokens": 5,
|
244 |
"temperature": 0.1,
|
|
|
245 |
"return_full_text": False
|
246 |
}
|
247 |
}
|
248 |
|
249 |
try:
|
250 |
+
print(f" Testando {model_name}...")
|
251 |
+
response = requests.post(url, headers=self.headers, json=test_payload, timeout=30)
|
252 |
|
253 |
if response.status_code == 200:
|
254 |
result = response.json()
|
|
|
|
|
255 |
if isinstance(result, list) and len(result) > 0:
|
256 |
+
return True, "Modelo disponível"
|
257 |
+
elif isinstance(result, dict) and 'generated_text' in result:
|
258 |
+
return True, "Modelo disponível"
|
259 |
+
else:
|
260 |
+
return False, f"Resposta inesperada: {result}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
|
262 |
elif response.status_code == 503:
|
263 |
+
return False, "Modelo carregando (503)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
elif response.status_code == 401:
|
265 |
+
return False, "Token inválido (401)"
|
266 |
+
elif response.status_code == 400:
|
267 |
+
error_msg = response.json().get('error', 'Erro desconhecido')
|
268 |
+
if 'loading' in error_msg.lower():
|
269 |
+
return False, "Modelo carregando"
|
270 |
+
return False, f"Erro 400: {error_msg}"
|
|
|
271 |
else:
|
272 |
+
return False, f"HTTP {response.status_code}: {response.text[:100]}"
|
|
|
|
|
|
|
|
|
|
|
273 |
|
274 |
except requests.exceptions.Timeout:
|
275 |
+
return False, "Timeout"
|
276 |
except requests.exceptions.RequestException as e:
|
277 |
+
return False, f"Erro de conexão: {str(e)[:100]}"
|
278 |
+
except Exception as e:
|
279 |
+
return False, f"Erro inesperado: {str(e)[:100]}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
280 |
|
281 |
+
def query_model(self, model_name: str, messages: List[Dict], max_tokens: int = 500) -> str:
|
282 |
+
"""Faz requisição ao modelo."""
|
283 |
+
# Converte mensagens para formato de prompt simples
|
284 |
prompt = self._convert_messages_to_prompt(messages)
|
285 |
|
286 |
+
url = f"{self.base_url}{model_name}"
|
|
|
|
|
287 |
payload = {
|
288 |
"inputs": prompt,
|
289 |
"parameters": {
|
290 |
+
"max_new_tokens": max_tokens,
|
291 |
"temperature": 0.7,
|
|
|
292 |
"do_sample": True,
|
293 |
+
"return_full_text": False
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
}
|
295 |
}
|
296 |
|
297 |
+
try:
|
298 |
+
response = requests.post(url, headers=self.headers, json=payload, timeout=60)
|
299 |
+
|
300 |
+
if response.status_code == 200:
|
301 |
+
result = response.json()
|
302 |
+
if isinstance(result, list) and len(result) > 0:
|
303 |
+
return result[0].get('generated_text', '').strip()
|
304 |
+
elif isinstance(result, dict) and 'generated_text' in result:
|
305 |
+
return result['generated_text'].strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
else:
|
307 |
+
return f"Formato de resposta inesperado: {result}"
|
308 |
+
else:
|
309 |
+
return f"Erro na requisição: {response.status_code} - {response.text[:200]}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
310 |
|
311 |
+
except Exception as e:
|
312 |
+
return f"Erro ao consultar modelo: {str(e)}"
|
|
|
|
|
313 |
|
314 |
def _convert_messages_to_prompt(self, messages: List[Dict]) -> str:
|
315 |
+
"""Converte mensagens para prompt simples."""
|
316 |
prompt_parts = []
|
|
|
317 |
for msg in messages:
|
318 |
role = msg['role']
|
319 |
content = msg['content']
|
320 |
|
321 |
if role == 'system':
|
322 |
+
prompt_parts.append(f"Sistema: {content}")
|
323 |
elif role == 'user':
|
324 |
+
prompt_parts.append(f"Usuário: {content}")
|
325 |
elif role == 'assistant':
|
326 |
+
prompt_parts.append(f"Assistente: {content}")
|
327 |
+
|
328 |
+
prompt_parts.append("Assistente:")
|
329 |
+
return "\n\n".join(prompt_parts)
|
|
|
|
|
|
|
|
|
330 |
|
331 |
# --- Função para Testar e Atualizar Modelos ---
|
332 |
def test_and_update_models() -> int:
|
333 |
+
"""Testa modelos e atualiza a lista MODELS."""
|
|
|
|
|
|
|
|
|
334 |
print("Testando disponibilidade dos modelos...")
|
335 |
print(f"Token HF disponível: {'Sim' if HF_TOKEN else 'Não'}")
|
336 |
print("-" * 60)
|
337 |
|
338 |
+
api_client = HuggingFaceAPIClient(HF_TOKEN)
|
339 |
+
available_models = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
340 |
|
341 |
for model_label, model_name in NEW_MODELS_TO_TEST:
|
342 |
+
try:
|
343 |
+
is_available, message = api_client.check_model_status(model_name)
|
|
|
|
|
|
|
|
|
344 |
|
345 |
+
if is_available:
|
346 |
+
available_models[model_label] = model_name
|
347 |
+
print(f"✓ {model_label} - {message}")
|
348 |
+
else:
|
349 |
+
print(f"✗ {model_label} - {message}")
|
350 |
+
|
351 |
+
except Exception as e:
|
352 |
+
print(f"✗ {model_label} - Erro: {str(e)}")
|
353 |
|
354 |
+
# Pausa para evitar rate limiting
|
355 |
+
time.sleep(2)
|
356 |
|
357 |
+
# Atualiza MODELS
|
358 |
global MODELS
|
359 |
MODELS.clear()
|
360 |
+
MODELS.update(available_models)
|
|
|
|
|
|
|
|
|
|
|
|
|
361 |
|
362 |
print("\n" + "=" * 60)
|
363 |
+
print("MODELOS DISPONÍVEIS:")
|
364 |
print("=" * 60)
|
365 |
for i, (label, name) in enumerate(MODELS.items(), 1):
|
366 |
+
print(f"{i}. {label} ({name})")
|
367 |
|
368 |
+
print(f"\nTOTAL: {len(MODELS)} modelos disponíveis")
|
369 |
print("=" * 60)
|
370 |
|
371 |
+
# Salva lista atualizada
|
|
|
|
|
|
|
|
|
372 |
try:
|
373 |
with open("models_available.json", "w", encoding="utf-8") as f:
|
374 |
json.dump(MODELS, f, ensure_ascii=False, indent=2)
|
375 |
+
print("Lista salva em 'models_available.json'")
|
376 |
except Exception as e:
|
377 |
+
print(f"Erro ao salvar lista: {e}")
|
378 |
+
|
379 |
+
return len(MODELS)
|
380 |
|
381 |
# --- Chat Principal ---
|
382 |
+
def responder_como_aldo(session_id: str, pergunta: str, modelo: str = None) -> str:
|
383 |
"""Gera resposta como Dr. Aldo Henrique."""
|
384 |
if not pergunta.strip():
|
385 |
return "Por favor, faça uma pergunta válida."
|
386 |
+
|
387 |
+
# Usar primeiro modelo disponível se nenhum especificado
|
388 |
+
if not modelo or modelo not in MODELS:
|
389 |
+
if not MODELS:
|
390 |
+
return "Erro: Nenhum modelo disponível!"
|
391 |
+
modelo = list(MODELS.keys())[0]
|
392 |
|
393 |
load_conversation_memory(session_id)
|
394 |
update_user_profile(session_id, pergunta)
|
395 |
|
396 |
+
# Monta contexto
|
397 |
contexto = []
|
398 |
if perfil := get_user_profile_context(session_id):
|
399 |
contexto.append(f"**Perfil do Usuário**\n{perfil}")
|
|
|
403 |
contexto.append(f"**Contexto do Blog**\n{blog}")
|
404 |
|
405 |
system_prompt = """Você é o Dr. Aldo Henrique,
|
406 |
+
Doutor em Ciências da Computação pela UnB (2024), professor universitário especializado em:
|
407 |
+
- Algoritmos e Estruturas de Dados
|
408 |
+
- Inteligência Artificial
|
409 |
+
- Ciência de Dados e Mineração de Dados
|
410 |
+
- Desenvolvimento de Software
|
411 |
+
|
412 |
+
Responda sempre em português, de forma didática e clara.
|
413 |
+
- Explique conceitos antes de mostrar código
|
414 |
+
- Use exemplos práticos
|
415 |
+
- Considere o nível do usuário
|
416 |
+
- Termine sempre com uma piada ou trocadilho relacionado ao tema
|
417 |
+
- Use Markdown para formatação
|
418 |
+
- Adicione comentários explicativos no código
|
419 |
"""
|
420 |
|
421 |
conteudo_contexto = "\n".join(contexto)
|
422 |
+
mensagem_usuario = f"{conteudo_contexto}\n\n**Pergunta**: {pergunta}" if contexto else pergunta
|
423 |
|
424 |
messages = [
|
425 |
{"role": "system", "content": system_prompt},
|
426 |
{"role": "user", "content": mensagem_usuario}
|
427 |
]
|
428 |
|
429 |
+
# Faz requisição
|
430 |
+
api_client = HuggingFaceAPIClient(HF_TOKEN)
|
431 |
+
model_name = MODELS[modelo]
|
|
|
|
|
432 |
resposta = api_client.query_model(model_name, messages)
|
433 |
+
|
434 |
+
# Salva na memória
|
435 |
add_to_memory(session_id, pergunta, resposta)
|
436 |
return resposta
|
437 |
|
438 |
# --- Inicialização ---
|
439 |
def inicializar_sistema():
|
440 |
+
"""Inicializa o sistema."""
|
|
|
|
|
|
|
|
|
|
|
441 |
print("Inicializando Chatbot Dr. Aldo...")
|
442 |
+
print("=" * 50)
|
443 |
|
444 |
+
# Testa modelos
|
445 |
num_available_models = test_and_update_models()
|
446 |
|
447 |
if num_available_models >= 1:
|
448 |
+
print(f"\n✓ Sistema inicializado com {num_available_models} modelos!")
|
449 |
+
|
450 |
+
# Carrega vector store (opcional)
|
451 |
+
try:
|
452 |
+
load_vector_store()
|
453 |
+
print("✓ Vector store carregado!")
|
454 |
+
except Exception as e:
|
455 |
+
print(f"⚠ Erro ao carregar vector store: {e}")
|
456 |
+
print("⚠ Sistema funcionará sem contexto do blog.")
|
457 |
+
|
458 |
return True, MODELS
|
459 |
else:
|
460 |
+
print(f"\n✗ Erro: Nenhum modelo disponível!")
|
461 |
+
print("Verifique:")
|
462 |
+
print("1. Conexão com internet")
|
463 |
+
print("2. Token HF_TOKEN válido")
|
464 |
+
print("3. Permissões da conta Hugging Face")
|
465 |
return False, MODELS
|
466 |
|
467 |
+
# --- Execução Principal ---
|
468 |
if __name__ == "__main__":
|
469 |
status, models = inicializar_sistema()
|
470 |
+
|
471 |
if status:
|
472 |
print("\n" + "="*50)
|
473 |
+
print("TESTE DO SISTEMA")
|
474 |
print("="*50)
|
475 |
+
|
476 |
session_id = "teste_123"
|
477 |
+
|
478 |
+
# Teste 1
|
479 |
+
print("\n1. Testando pergunta básica...")
|
480 |
+
resposta1 = responder_como_aldo(session_id, "O que é Python?")
|
481 |
+
print(f"Resposta: {resposta1[:200]}...")
|
482 |
+
|
483 |
+
# Teste 2
|
484 |
+
print("\n2. Testando pergunta com código...")
|
485 |
+
resposta2 = responder_como_aldo(session_id, "Mostre um exemplo de função em Python")
|
486 |
+
print(f"Resposta: {resposta2[:200]}...")
|
487 |
+
|
488 |
+
# Limpeza
|
489 |
+
print(f"\n3. {clear_memory(session_id)}")
|
490 |
+
|
491 |
+
print("\n" + "="*50)
|
492 |
+
print("SISTEMA PRONTO PARA USO!")
|
493 |
+
print("="*50)
|
494 |
+
|
495 |
else:
|
496 |
+
print("\n" + "="*50)
|
497 |
+
print("SISTEMA NÃO PÔDE SER INICIALIZADO")
|
498 |
+
print("="*50)
|
499 |
+
print("Verifique as configurações e tente novamente.")
|