aldohenrique commited on
Commit
c4914e0
·
verified ·
1 Parent(s): cdfa179

Update ai_logic.py

Browse files
Files changed (1) hide show
  1. 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
- # Lista inicial de modelos
23
- NEW_MODELS_TO_TEST = [
24
- ("Microsoft DialoGPT", "microsoft/DialoGPT-medium")]
25
-
26
- # --- Função para buscar modelos ---
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("Lista atualizada de modelos:\n")
101
- for name, model_id in NEW_MODELS_TO_TEST[:10]: # Mostra apenas os primeiros 10
102
- print(f'("{name}", "{model_id}"),')
103
 
104
- print(f"\nTotal de modelos na lista: {len(NEW_MODELS_TO_TEST)}")
 
105
 
106
- DEFAULT_MODEL = "Zephyr 7B (Meio Termo)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- links = get_all_blog_links(BLOG_URL)
242
- texts = [scrape_text_from_url(link) for link in links if scrape_text_from_url(link)]
243
- if not texts:
244
- return "Nenhum conteúdo encontrado."
 
 
 
 
 
 
 
 
245
 
246
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
247
- chunks = text_splitter.create_documents(texts)
248
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
249
- vector_store = FAISS.from_documents(chunks, embeddings)
250
-
251
- with open(VECTOR_STORE_PATH, "wb") as f:
252
- pickle.dump(vector_store, f)
253
- with open(PROCESSED_URLS_PATH, "wb") as f:
254
- pickle.dump(links, f)
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
- if os.path.exists(VECTOR_STORE_PATH):
261
- with open(VECTOR_STORE_PATH, "rb") as f:
262
- vector_store = pickle.load(f)
263
- else:
 
 
 
 
 
 
 
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}", "Content-Type": "application/json"}
280
- self.base_api_url = "https://api-inference.huggingface.co/models"
281
- self.base_info_url = "https://huggingface.co/api/models"
282
 
283
- def check_model_info(self, model_name: str) -> Tuple[bool, str]:
284
- """Verifica informações do modelo via API do Hugging Face."""
285
- url = f"{self.base_info_url}/{model_name}"
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
- # Payload simplificado para teste
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
- response = requests.post(url, headers=self.headers, json=test_payload, timeout=60)
 
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
- if 'generated_text' in result[0] or 'translation_text' in result[0] or 'summary_text' in result[0]:
344
- return True, "Modelo disponível para inferência"
345
-
346
- elif isinstance(result, dict):
347
- if 'generated_text' in result or 'error' not in result:
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
- try:
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 ou sem permissão"
381
- elif response.status_code == 403:
382
- return False, "Acesso negado ao modelo"
383
- elif response.status_code == 404:
384
- return False, "Endpoint do modelo não encontrado"
385
- elif response.status_code == 429:
386
- return False, "Limite de requisições excedido"
387
  else:
388
- try:
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 na requisição de inferência"
397
  except requests.exceptions.RequestException as e:
398
- return False, f"Erro na requisição: {str(e)}"
399
-
400
- def test_model_availability(self, model_name: str) -> Tuple[bool, str]:
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 = 1000) -> str:
423
- """Faz requisição ao modelo usando text-generation com retry e fallback."""
 
424
  prompt = self._convert_messages_to_prompt(messages)
425
 
426
- url = f"{self.base_api_url}/{model_name}"
427
-
428
- # Configurações otimizadas para diferentes tipos de modelo
429
  payload = {
430
  "inputs": prompt,
431
  "parameters": {
432
- "max_new_tokens": min(max_tokens, 500), # Limita para evitar timeouts
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
- max_retries = 2
447
- for attempt in range(max_retries):
448
- try:
449
- response = requests.post(
450
- url,
451
- headers=self.headers,
452
- json=payload,
453
- timeout=120 # Timeout aumentado
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
- try:
508
- error_data = response.json()
509
- error_msg = error_data.get('error', response.text)
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
- except requests.exceptions.RequestException as e:
522
- return f"Erro na requisição: {str(e)}"
523
-
524
- return "Falha após múltiplas tentativas"
525
 
526
  def _convert_messages_to_prompt(self, messages: List[Dict]) -> str:
527
- """Converte mensagens do formato chat para prompt otimizado."""
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"### Sistema:\n{content}\n")
536
  elif role == 'user':
537
- prompt_parts.append(f"### Usuário:\n{content}\n")
538
  elif role == 'assistant':
539
- prompt_parts.append(f"### Assistente:\n{content}\n")
540
-
541
- # Adiciona prompt final para gerar resposta
542
- prompt_parts.append("### Assistente:\n")
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
- # Cria um dicionário temporário para os modelos disponíveis
560
- temp_models = {}
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
- # Pula o modelo padrão se já foi testado
580
- if model_label == default_label and model_name == default_name:
581
- continue
582
-
583
- if tested_count >= max_tests:
584
- break
585
 
586
- is_available, message = api_client.test_model_availability(model_name)
587
-
588
- if is_available:
589
- temp_models[model_label] = model_name
590
- print(f" {model_label}")
591
- else:
592
- print(f"✗ {model_label} - {message}")
 
593
 
594
- tested_count += 1
595
- time.sleep(2) # Pausa entre testes para evitar rate limiting
596
 
597
- # Atualiza MODELS garantindo que o padrão seja o primeiro
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 (ORDEM):")
610
  print("=" * 60)
611
  for i, (label, name) in enumerate(MODELS.items(), 1):
612
- print(f"{i}. {label}")
613
 
614
- print(f"\nTOTAL DE MODELOS DISPONÍVEIS: {len(MODELS)}")
615
  print("=" * 60)
616
 
617
- save_updated_models()
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 de modelos disponíveis salva em 'models_available.json'")
626
  except Exception as e:
627
- print(f"Erro ao salvar lista de modelos: {e}")
 
 
628
 
629
  # --- Chat Principal ---
630
- def responder_como_aldo(session_id: str, pergunta: str, modelo: str = DEFAULT_MODEL) -> 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), mestre em Ciências da Computação pela UnB (2017) e bacharel em Sistemas de Informação pela UFV (2014).
648
- Professor universitário, onde leciona disciplinas como Algoritmos, Inteligência Artificial, Ciência de Dados e Mineração de Dados.
649
- Atua como analista de sistemas nível 4.
650
- Regras de conduta:
651
- - Sempre coloque uma piada ou trocadilho no final da resposta.
652
- - Responda em português, de forma clara, amigável e educativa.
653
- - Explique conceitos antes de mostrar soluções.
654
- - Use exemplos práticos.
655
- - Considere o nível do usuário (iniciante, intermediário ou avançado).
656
- - Use Markdown para formatar respostas, com ``` para blocos de código.
657
- - Dentro do código sempre coloque comentários explicando para o alunos aprender com os comentários.
658
- - Foque em tecnologia; se a pergunta for fora do escopo, informe educadamente que não é seu domínio.
 
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
- # Verifica se o modelo existe na lista, senão usa o padrão
670
- if modelo not in MODELS:
671
- modelo = next(iter(MODELS)) if MODELS else DEFAULT_MODEL
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
- load_vector_store()
692
- print("Sistema inicializado e pronto para uso com modelos suficientes!")
 
 
 
 
 
 
 
 
693
  return True, MODELS
694
  else:
695
- print(f"Erro: Apenas {num_available_models} modelos disponíveis. É necessário pelo menos 1 modelo para iniciar o sistema.")
 
 
 
 
696
  return False, MODELS
697
 
 
698
  if __name__ == "__main__":
699
  status, models = inicializar_sistema()
 
700
  if status:
701
  print("\n" + "="*50)
702
- print("SISTEMA INICIADO: Realizando teste básico do Chatbot... ")
703
  print("="*50)
 
704
  session_id = "teste_123"
705
- print(responder_como_aldo(session_id, "O que é Java?"))
706
- print("\n" + "-"*50)
707
- print(responder_como_aldo(session_id, "Mostre um exemplo de código Java."))
708
- print("\n" + "-"*50)
709
- print(clear_memory(session_id))
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  else:
711
- print("\nSistema não pôde ser iniciado devido à falta de modelos suficientes.")
712
- print(f"Modelos disponíveis: {', '.join(models.keys()) if models else 'Nenhum'}")
713
- print("Por favor, verifique a conexão com o Hugging Face e o token de acesso.")
 
 
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.")