Spaces:
Running
Running
File size: 35,559 Bytes
977edfa 13660cf 977edfa 13660cf 7dce7eb 13660cf 977edfa 9831b5f 7dce7eb 4711ae4 b05a1c5 3bdb3bd 977edfa c95d0c8 7dce7eb d5df267 7dce7eb 1bcec59 7dce7eb 557d276 abfcded 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 0173d74 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 977edfa 3bdb3bd b05a1c5 d4a357d b05a1c5 d4a357d b05a1c5 0173d74 b05a1c5 d4a357d 977edfa b05a1c5 557d276 b05a1c5 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 977edfa b05a1c5 abfcded 7dce7eb b05a1c5 977edfa b05a1c5 d3a6527 b05a1c5 977edfa d3a6527 977edfa b05a1c5 977edfa b05a1c5 d3a6527 b05a1c5 977edfa b05a1c5 977edfa 3bdb3bd b05a1c5 d4a357d d3a6527 d4a357d b05a1c5 977edfa d4a357d b05a1c5 d4a357d b05a1c5 0173d74 d4a357d b05a1c5 d4a357d 977edfa b05a1c5 3bdb3bd b05a1c5 d4a357d b05a1c5 d4a357d 1565ea2 b05a1c5 d4a357d b05a1c5 d4a357d b05a1c5 d4a357d abfcded 7dce7eb d4a357d b05a1c5 7dce7eb b05a1c5 977edfa b05a1c5 0173d74 c5ec8c2 942ce93 ad7cabf 942ce93 9ce88f7 942ce93 7dce7eb 9ce88f7 942ce93 d4a357d 04df468 b05a1c5 1bcec59 b05a1c5 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb 3bdb3bd 7dce7eb 243f645 7dce7eb 243f645 7dce7eb c95d0c8 7dce7eb c95d0c8 7dce7eb c95d0c8 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 6ec05a1 d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb d5df267 7dce7eb b3600df 7dce7eb e09122f 7dce7eb d5df267 7dce7eb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 |
import requests
import os
import json
import time
import pickle
from typing import Dict, List, Optional, Tuple, Any
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
import random
import asyncio
import aiohttp
# --- Configurações ---
BLOG_URL = "https://aldohenrique.com.br/"
VECTOR_STORE_PATH = "faiss_index_store.pkl"
PROCESSED_URLS_PATH = "processed_urls.pkl"
HF_TOKEN = os.getenv("HF_TOKEN")
OR_TOKEN= os.getenv("OR_TOKEN")
# --- Provedores de LLM Gratuitos ---
class LLMProvider:
"""Classe base para provedores de LLM"""
def __init__(self, name: str, base_url: str = None):
self.name = name
self.base_url = base_url
self.is_available = False
self.models = []
async def test_availability(self) -> bool:
"""Testa se o provedor está disponível"""
return False
async def query_model(self, messages: List[Dict], model_name: str = None, **kwargs) -> str:
"""Faz query no modelo"""
return ""
def convert_messages_to_prompt(self, messages: List[Dict]) -> str:
"""Converte mensagens para formato de prompt"""
prompt_parts = []
for msg in messages:
role = msg['role']
content = msg['content']
if role == 'system':
prompt_parts.append(f"Sistema: {content}")
elif role == 'user':
prompt_parts.append(f"Usuário: {content}")
elif role == 'assistant':
prompt_parts.append(f"Assistente: {content}")
prompt_parts.append("Assistente:")
return "\n\n".join(prompt_parts)
class HuggingFaceProvider(LLMProvider):
"""Provedor Hugging Face (original)"""
def __init__(self, token: str):
super().__init__("Hugging Face", "https://api-inference.huggingface.co")
self.token = token
self.headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
self.models = [
("Phi-3 Mini (Mais rápido)", "microsoft/Phi-3-mini-4k-instruct"),
("Zephyr 7B (Meio Termo)", "HuggingFaceH4/zephyr-7b-beta"),
("Mistral-7B", "mistralai/Mistral-7B-Instruct-v0.3"),
("Microsoft 8B", "meta-llama/Meta-Llama-3-8B-Instruct"),
("DialoGPT", "microsoft/DialoGPT-medium"),
("Google Flan-T5", "google/flan-t5-base"),
("Facebook BART", "facebook/bart-large-cnn")
]
async def test_availability(self) -> bool:
if not self.token:
return False
# Testa com um modelo simples
test_model = "microsoft/DialoGPT-medium"
url = f"{self.base_url}/models/{test_model}"
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=self.headers,
json={"inputs": "Hello", "parameters": {"max_new_tokens": 5}},
timeout=aiohttp.ClientTimeout(total=10)
) as response:
self.is_available = response.status in [200, 503] # 503 = modelo carregando
return self.is_available
except:
return False
async def query_model(self, messages: List[Dict], model_name: str = None, **kwargs) -> str:
if not model_name:
model_name = "mistralai/Mistral-7B-Instruct-v0.3"
prompt = self.convert_messages_to_prompt(messages)
url = f"{self.base_url}/models/{model_name}"
payload = {
"inputs": prompt,
"parameters": {
"max_new_tokens": kwargs.get("max_tokens", 1000),
"temperature": kwargs.get("temperature", 0.7),
"do_sample": True,
"return_full_text": False
}
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
url,
headers=self.headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
if response.status == 200:
result = await response.json()
if isinstance(result, list) and len(result) > 0:
return result[0].get('generated_text', '').strip()
elif isinstance(result, dict) and 'generated_text' in result:
return result['generated_text'].strip()
return f"Erro HF: {response.status}"
except Exception as e:
return f"Erro na requisição HF: {str(e)}"
class OllamaProvider(LLMProvider):
"""Provedor Ollama (local)"""
def __init__(self, host: str = "http://localhost:11434"):
super().__init__("Ollama", host)
self.models = [
("Llama 3.2", "llama3.2"),
("Phi 3", "phi3"),
("Gemma 2", "gemma2"),
("Mistral", "mistral"),
("CodeLlama", "codellama")
]
async def test_availability(self) -> bool:
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.base_url}/api/tags",
timeout=aiohttp.ClientTimeout(total=5)
) as response:
self.is_available = response.status == 200
return self.is_available
except:
return False
async def query_model(self, messages: List[Dict], model_name: str = "llama3.2", **kwargs) -> str:
prompt = self.convert_messages_to_prompt(messages)
payload = {
"model": model_name,
"prompt": prompt,
"stream": False,
"options": {
"num_predict": kwargs.get("max_tokens", 1000),
"temperature": kwargs.get("temperature", 0.7)
}
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/api/generate",
json=payload,
timeout=aiohttp.ClientTimeout(total=60)
) as response:
if response.status == 200:
result = await response.json()
return result.get('response', '').strip()
return f"Erro Ollama: {response.status}"
except Exception as e:
return f"Erro na requisição Ollama: {str(e)}"
class OpenRouterProvider(LLMProvider):
"""Provedor OpenRouter (alguns modelos gratuitos)"""
def __init__(self, api_key: str = None):
super().__init__("OpenRouter", "https://openrouter.ai/api/v1")
self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
self.headers = {
"Authorization": f"Bearer {self.api_key}" if self.api_key else "",
"Content-Type": "application/json"
}
self.models = [
("Mistral 7B Free", "mistralai/mistral-7b-instruct:free"),
("Mythomist 7B", "gryphe/mythomist-7b:free"),
("Toppy M 7B", "undi95/toppy-m-7b:free"),
("OpenChat 3.5", "openchat/openchat-7b:free")
]
async def test_availability(self) -> bool:
if not self.api_key:
return False
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.base_url}/models",
headers=self.headers,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
self.is_available = response.status == 200
return self.is_available
except:
return False
async def query_model(self, messages: List[Dict], model_name: str = "mistralai/mistral-7b-instruct:free", **kwargs) -> str:
payload = {
"model": model_name,
"messages": messages,
"max_tokens": kwargs.get("max_tokens", 1000),
"temperature": kwargs.get("temperature", 0.7)
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/chat/completions",
headers=self.headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
return f"Erro OpenRouter: {response.status}"
except Exception as e:
return f"Erro na requisição OpenRouter: {str(e)}"
class GroqProvider(LLMProvider):
"""Provedor Groq (gratuito com limites)"""
def __init__(self, api_key: str = None):
super().__init__("Groq", "https://api.groq.com/openai/v1")
self.api_key = api_key or os.getenv("GROQ_API_KEY")
self.headers = {
"Authorization": f"Bearer {self.api_key}" if self.api_key else "",
"Content-Type": "application/json"
}
self.models = [
("Llama 3 8B", "llama3-8b-8192"),
("Llama 3 70B", "llama3-70b-8192"),
("Mixtral 8x7B", "mixtral-8x7b-32768"),
("Gemma 7B", "gemma-7b-it")
]
async def test_availability(self) -> bool:
if not self.api_key:
return False
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.base_url}/models",
headers=self.headers,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
self.is_available = response.status == 200
return self.is_available
except:
return False
async def query_model(self, messages: List[Dict], model_name: str = "llama3-8b-8192", **kwargs) -> str:
payload = {
"model": model_name,
"messages": messages,
"max_tokens": kwargs.get("max_tokens", 1000),
"temperature": kwargs.get("temperature", 0.7)
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/chat/completions",
headers=self.headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
return f"Erro Groq: {response.status}"
except Exception as e:
return f"Erro na requisição Groq: {str(e)}"
class TogetherAIProvider(LLMProvider):
"""Provedor Together AI (alguns modelos gratuitos)"""
def __init__(self, api_key: str = None):
super().__init__("Together AI", "https://api.together.xyz/v1")
self.api_key = api_key or os.getenv("TOGETHER_API_KEY")
self.headers = {
"Authorization": f"Bearer {self.api_key}" if self.api_key else "",
"Content-Type": "application/json"
}
self.models = [
("Mistral 7B", "mistralai/Mistral-7B-Instruct-v0.1"),
("Code Llama 7B", "codellama/CodeLlama-7b-Instruct-hf"),
("Llama 2 7B", "meta-llama/Llama-2-7b-chat-hf"),
("WizardCoder 15B", "WizardLM/WizardCoder-15B-V1.0")
]
async def test_availability(self) -> bool:
if not self.api_key:
return False
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{self.base_url}/models",
headers=self.headers,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
self.is_available = response.status == 200
return self.is_available
except:
return False
async def query_model(self, messages: List[Dict], model_name: str = "mistralai/Mistral-7B-Instruct-v0.1", **kwargs) -> str:
payload = {
"model": model_name,
"messages": messages,
"max_tokens": kwargs.get("max_tokens", 1000),
"temperature": kwargs.get("temperature", 0.7)
}
try:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{self.base_url}/chat/completions",
headers=self.headers,
json=payload,
timeout=aiohttp.ClientTimeout(total=30)
) as response:
if response.status == 200:
result = await response.json()
return result['choices'][0]['message']['content'].strip()
return f"Erro Together AI: {response.status}"
except Exception as e:
return f"Erro na requisição Together AI: {str(e)}"
# --- Gerenciador de Múltiplos Provedores ---
class MultiProviderLLMClient:
"""Cliente que gerencia múltiplos provedores de LLM"""
def __init__(self):
self.providers = []
self.available_providers = []
self.current_provider_index = 0
self.models_cache = {}
# Inicializa provedores
if HF_TOKEN:
self.providers.append(HuggingFaceProvider(HF_TOKEN))
# Adiciona outros provedores
self.providers.extend([
OllamaProvider(),
OpenRouterProvider(),
GroqProvider(),
TogetherAIProvider()
])
async def initialize_providers(self):
"""Inicializa e testa todos os provedores"""
print("Testando provedores de LLM...")
tasks = []
for provider in self.providers:
tasks.append(self._test_provider(provider))
await asyncio.gather(*tasks)
# Filtra apenas provedores disponíveis
self.available_providers = [p for p in self.providers if p.is_available]
if not self.available_providers:
print("⚠️ Nenhum provedor de LLM disponível!")
return False
print(f"✅ {len(self.available_providers)} provedores disponíveis:")
for provider in self.available_providers:
print(f" - {provider.name}")
self._build_models_cache()
return True
async def _test_provider(self, provider: LLMProvider):
"""Testa um provedor específico"""
try:
is_available = await provider.test_availability()
if is_available:
print(f"✅ {provider.name} - Disponível")
else:
print(f"❌ {provider.name} - Indisponível")
except Exception as e:
print(f"❌ {provider.name} - Erro: {e}")
def _build_models_cache(self):
"""Constrói cache de modelos disponíveis"""
self.models_cache = {}
for provider in self.available_providers:
for model_name, model_id in provider.models:
full_name = f"{model_name} ({provider.name})"
self.models_cache[full_name] = {
"provider": provider,
"model_id": model_id,
"provider_name": provider.name
}
def get_available_models(self) -> Dict[str, str]:
"""Retorna modelos disponíveis em formato compatível"""
return {name: info["model_id"] for name, info in self.models_cache.items()}
async def query_model(self, messages: List[Dict], model_name: str = None, **kwargs) -> str:
"""Faz query tentando diferentes provedores como fallback"""
if not self.available_providers:
return "Nenhum provedor de LLM disponível. Por favor, configure ao menos um provedor."
# Se modelo específico foi solicitado
if model_name and model_name in self.models_cache:
provider_info = self.models_cache[model_name]
try:
result = await provider_info["provider"].query_model(
messages,
provider_info["model_id"],
**kwargs
)
if not result.startswith("Erro"):
return result
except Exception as e:
print(f"Erro com {provider_info['provider_name']}: {e}")
# Fallback: tenta todos os provedores disponíveis
for provider in self.available_providers:
try:
if provider.models:
default_model = provider.models[0][1] # Pega o primeiro modelo
result = await provider.query_model(messages, default_model, **kwargs)
if not result.startswith("Erro"):
return f"{result}\n\n_Resposta gerada por: {provider.name}_"
except Exception as e:
print(f"Erro com provedor {provider.name}: {e}")
continue
return "Desculpe, todos os provedores de LLM estão indisponíveis no momento. Tente novamente mais tarde."
# --- Integração com o código original ---
# Variáveis globais atualizadas
MODELS = {}
DEFAULT_MODEL = "Mistral 7B (Hugging Face)"
multi_client = MultiProviderLLMClient()
# Gerenciamento de Sessão (mantido igual)
user_sessions: Dict[str, Dict[str, List | Dict]] = {}
MAX_MEMORY_LENGTH = 5
def get_session_memory_path(session_id: str) -> str:
"""Retorna o caminho do arquivo de memória para a sessão."""
return f"conversation_memory_{session_id}.json"
def load_conversation_memory(session_id: str):
"""Carrega a memória da sessão do usuário."""
if session_id in user_sessions:
return
memory_path = get_session_memory_path(session_id)
session_data = {'conversation': [], 'user_profile': {'nivel': 'intermediario', 'total_perguntas': 0}}
if os.path.exists(memory_path):
try:
with open(memory_path, 'r', encoding='utf-8') as f:
session_data = json.load(f)
except Exception as e:
print(f"Erro ao carregar memória para sessão '{session_id}': {e}")
user_sessions[session_id] = session_data
def save_conversation_memory(session_id: str):
"""Salva a memória da sessão do usuário."""
memory_path = get_session_memory_path(session_id)
try:
with open(memory_path, 'w', encoding='utf-8') as f:
json.dump(user_sessions[session_id], f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"Erro ao salvar memória para sessão '{session_id}': {e}")
def add_to_memory(session_id: str, user_message: str, assistant_response: str):
"""Adiciona uma troca de mensagens à memória da sessão."""
load_conversation_memory(session_id)
conversation = user_sessions[session_id]['conversation']
conversation.extend([
{"role": "user", "content": user_message, "timestamp": time.time()},
{"role": "assistant", "content": assistant_response, "timestamp": time.time()}
])
if len(conversation) > MAX_MEMORY_LENGTH * 2:
user_sessions[session_id]['conversation'] = conversation[-MAX_MEMORY_LENGTH * 2:]
save_conversation_memory(session_id)
def update_user_profile(session_id: str, user_message: str):
"""Atualiza o perfil do usuário com base na mensagem."""
load_conversation_memory(session_id)
profile = user_sessions[session_id]['user_profile']
message_lower = user_message.lower()
if any(word in message_lower for word in ['básico', 'iniciante']):
profile['nivel'] = 'iniciante'
elif any(word in message_lower for word in ['avançado', 'complexo']):
profile['nivel'] = 'avançado'
topics = {
'java': ['java', 'classe', 'objeto'],
'web': ['html', 'css', 'javascript'],
'ia': ['inteligência artificial', 'machine learning']
}
for topic, keywords in topics.items():
if any(keyword in message_lower for keyword in keywords):
profile[f'interesse_{topic}'] = profile.get(f'interesse_{topic}', 0) + 1
profile['total_perguntas'] = profile.get('total_perguntas', 0) + 1
user_sessions[session_id]['user_profile'] = profile
def get_conversation_context(session_id: str) -> str:
"""Gera o contexto da conversa recente."""
load_conversation_memory(session_id)
conversation = user_sessions[session_id]['conversation'][-4:]
if not conversation:
return ""
return "\n".join(f"{msg['role'].upper()}: {msg['content']}" for msg in conversation)
def get_user_profile_context(session_id: str) -> str:
"""Gera o contexto do perfil do usuário."""
load_conversation_memory(session_id)
profile = user_sessions[session_id]['user_profile']
context = f"Nível: {profile.get('nivel', 'intermediario')}\n"
context += f"Total de perguntas: {profile.get('total_perguntas', 0)}\n"
interesses = [f"{k.replace('interesse_', '').title()} ({v})" for k, v in profile.items() if k.startswith('interesse_')]
if interesses:
context += f"Interesses: {', '.join(interesses)}\n"
return context
def clear_memory(session_id: str) -> str:
"""Limpa a memória de uma sessão específica."""
if session_id in user_sessions:
del user_sessions[session_id]
memory_path = get_session_memory_path(session_id)
if os.path.exists(memory_path):
os.remove(memory_path)
return "Memória limpa com sucesso!"
# --- RAG (mantido igual) ---
vector_store: Optional[FAISS] = None
def get_all_blog_links(url: str) -> set:
"""Coleta todos os links do blog."""
links = {url}
visited = set()
while links:
current_url = links.pop()
if current_url in visited:
continue
try:
response = requests.get(current_url, timeout=500)
soup = BeautifulSoup(response.content, 'html.parser')
visited.add(current_url)
for link in soup.find_all('a', href=True):
href = urljoin(url, link['href'])
if urlparse(href).netloc == urlparse(url).netloc and '/tag/' not in href and '/category/' not in href:
links.add(href)
except Exception as e:
print(f"Erro ao acessar {current_url}: {e}")
return visited
def scrape_text_from_url(url: str) -> str:
"""Extrai texto de uma URL."""
try:
response = requests.get(url, timeout=500)
soup = BeautifulSoup(response.content, 'html.parser')
content = soup.find('article') or soup.find('main')
return content.get_text(separator='\n', strip=True) if content else ""
except Exception as e:
print(f"Erro ao raspar {url}: {e}")
return ""
def build_and_save_vector_store():
"""Constrói e salva o vector store."""
global vector_store
links = get_all_blog_links(BLOG_URL)
texts = [scrape_text_from_url(link) for link in links if scrape_text_from_url(link)]
if not texts:
return "Nenhum conteúdo encontrado."
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
chunks = text_splitter.create_documents(texts)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vector_store = FAISS.from_documents(chunks, embeddings)
with open(VECTOR_STORE_PATH, "wb") as f:
pickle.dump(vector_store, f)
with open(PROCESSED_URLS_PATH, "wb") as f:
pickle.dump(links, f)
return f"Vector store criado com {len(chunks)} chunks."
def load_vector_store():
"""Carrega o vector store."""
global vector_store
if os.path.exists(VECTOR_STORE_PATH):
with open(VECTOR_STORE_PATH, "rb") as f:
vector_store = pickle.load(f)
else:
build_and_save_vector_store()
def retrieve_context_from_blog(query: str, k: int = 4) -> str:
"""Busca contexto relevante no vector store."""
if vector_store:
try:
results = vector_store.similarity_search(query, k=k)
return "\n".join(doc.page_content for doc in results)
except Exception as e:
print(f"Erro ao buscar contexto: {e}")
return ""
# --- Função Principal Atualizada ---
async def responder_como_aldo(session_id: str, pergunta: str, modelo: str = DEFAULT_MODEL) -> str:
"""Gera resposta como Dr. Aldo Henrique usando múltiplos provedores."""
if not pergunta.strip():
return "Por favor, faça uma pergunta válida."
load_conversation_memory(session_id)
update_user_profile(session_id, pergunta)
contexto = []
if perfil := get_user_profile_context(session_id):
contexto.append(f"**Perfil do Usuário**\n{perfil}")
if conversa := get_conversation_context(session_id):
contexto.append(f"**Conversa Anterior**\n{conversa}")
if blog := retrieve_context_from_blog(pergunta):
contexto.append(f"**Contexto do Blog**\n{blog}")
system_prompt = """Você é o Dr. Aldo Henrique,
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).
Professor universitário, onde leciona disciplinas como Algoritmos, Inteligência Artificial, Ciência de Dados e Mineração de Dados.
Atua como analista de sistemas nível 4.
Regras de conduta:
- Sempre coloque uma piada ou trocadilho no final da resposta.
- Responda em português, de forma clara, amigável e educativa.
- Explique conceitos antes de mostrar soluções.
- Use exemplos práticos.
- Considere o nível do usuário (iniciante, intermediário ou avançado).
- Use Markdown para formatar respostas, com ``` para blocos de código.
- Dentro do código sempre coloque comentários explicando para o alunos aprender com os comentários.
- Foque em tecnologia; se a pergunta for fora do escopo, informe educadamente que não é seu domínio.
"""
conteudo_contexto = "\n".join(contexto)
mensagem_usuario = f"{conteudo_contexto}\n\n**Pergunta**: {pergunta}"
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": mensagem_usuario}
]
# Usa o cliente multi-provedor
resposta = await multi_client.query_model(messages, modelo)
add_to_memory(session_id, pergunta, resposta)
return resposta
# --- Função de Inicialização Atualizada ---
async def inicializar_sistema():
"""
Inicializa o sistema com múltiplos provedores de LLM.
Retorna uma tupla: (status: bool, models: dict)
"""
print("Inicializando Sistema Multi-Provedor Dr. Aldo...")
print("=" * 60)
# Inicializa os provedores
success = await multi_client.initialize_providers()
if not success:
print("❌ Nenhum provedor de LLM disponível!")
return False, {}
# Atualiza variáveis globais
global MODELS, DEFAULT_MODEL
MODELS = multi_client.get_available_models()
if MODELS:
# Define o primeiro modelo como padrão
DEFAULT_MODEL = list(MODELS.keys())[0]
print(f"✅ Modelo padrão definido: {DEFAULT_MODEL}")
# Carrega o vector store
load_vector_store()
print("=" * 60)
print("✅ Sistema inicializado com sucesso!")
print(f"📊 Total de modelos disponíveis: {len(MODELS)}")
print(f"🔧 Provedores ativos: {len(multi_client.available_providers)}")
return True, MODELS
# --- Função de Teste de Modelos ---
async def testar_modelos_disponiveis():
"""Testa todos os modelos disponíveis"""
print("\n" + "=" * 60)
print("TESTANDO MODELOS DISPONÍVEIS")
print("=" * 60)
test_message = [
{"role": "system", "content": "Você é um assistente útil."},
{"role": "user", "content": "Diga apenas 'Olá, funcionando!' em português."}
]
for model_name in MODELS.keys():
print(f"\n🧪 Testando: {model_name}")
try:
result = await multi_client.query_model(test_message, model_name)
if result and not result.startswith("Erro"):
print(f"✅ {model_name} - Funcionando")
print(f" Resposta: {result[:100]}...")
else:
print(f"❌ {model_name} - Falha: {result}")
except Exception as e:
print(f"❌ {model_name} - Erro: {e}")
# Pequena pausa entre testes
await asyncio.sleep(1)
# --- Função de Configuração de Provedores ---
def configurar_provedores():
"""Exibe instruções para configurar os provedores"""
print("\n" + "=" * 60)
print("CONFIGURAÇÃO DE PROVEDORES LLM GRATUITOS")
print("=" * 60)
configs = {
"Hugging Face": {
"var": HF_TOKEN,
"url": "https://huggingface.co/settings/tokens",
"descricao": "Token gratuito para usar modelos do Hugging Face"
},
"Groq": {
"var": "GROQ_API_KEY",
"url": "https://console.groq.com/keys",
"descricao": "API key gratuita (limite diário)"
},
"OpenRouter": {
"var": OR_TOKEN,
"url": "https://openrouter.ai/keys",
"descricao": "Alguns modelos gratuitos disponíveis"
},
"Together AI": {
"var": "",
"url": "https://api.together.xyz/settings/api-keys",
"descricao": "Credits gratuitos iniciais"
},
"Ollama": {
"var": "Não necessário",
"url": "https://ollama.ai/",
"descricao": "Instalação local - totalmente gratuito"
}
}
print("Para usar todos os provedores, configure as seguintes variáveis de ambiente:")
print()
for provedor, info in configs.items():
status = "✅ Configurado" if os.getenv(info["var"]) else "❌ Não configurado"
print(f"{provedor}:")
print(f" • Variável: {info['var']}")
print(f" • URL: {info['url']}")
print(f" • Descrição: {info['descricao']}")
print(f" • Status: {status}")
print()
print("📝 Exemplo de configuração no seu .env:")
print("export HF_TOKEN='seu_token_aqui'")
print("export GROQ_API_KEY='sua_chave_aqui'")
print("export OPENROUTER_API_KEY='sua_chave_aqui'")
print("export TOGETHER_API_KEY='sua_chave_aqui'")
# --- Função de Monitoramento ---
async def monitorar_provedores():
"""Monitora o status dos provedores"""
print("\n" + "=" * 60)
print("MONITORAMENTO DE PROVEDORES")
print("=" * 60)
for provider in multi_client.available_providers:
print(f"\n🔍 Testando: {provider.name}")
try:
is_available = await provider.test_availability()
status = "✅ Online" if is_available else "❌ Offline"
print(f" Status: {status}")
print(f" Modelos: {len(provider.models)}")
except Exception as e:
print(f" Status: ❌ Erro - {e}")
# --- Função Principal Wrapper (para compatibilidade) ---
def responder_como_aldo_sync(session_id: str, pergunta: str, modelo: str = DEFAULT_MODEL) -> str:
"""Wrapper síncrono para compatibilidade com código existente"""
try:
loop = asyncio.get_event_loop()
if loop.is_running():
# Se já existe um loop rodando, cria uma nova task
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(asyncio.run, responder_como_aldo(session_id, pergunta, modelo))
return future.result()
else:
return loop.run_until_complete(responder_como_aldo(session_id, pergunta, modelo))
except Exception as e:
return f"Erro ao processar resposta: {str(e)}"
# --- Função de Salvamento de Configurações ---
def salvar_configuracoes():
"""Salva as configurações atuais"""
config = {
"provedores_disponiveis": [p.name for p in multi_client.available_providers],
"modelos_disponiveis": list(MODELS.keys()),
"modelo_padrao": DEFAULT_MODEL,
"timestamp": time.time()
}
try:
with open("llm_config.json", "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
print("✅ Configurações salvas em 'llm_config.json'")
except Exception as e:
print(f"❌ Erro ao salvar configurações: {e}")
# --- Função Principal ---
async def main():
"""Função principal para teste e inicialização"""
print("🚀 Iniciando Sistema Multi-Provedor LLM")
print("=" * 60)
# Mostra configurações
configurar_provedores()
# Inicializa sistema
status, models = await inicializar_sistema_sync()
if not status:
print("❌ Sistema não pôde ser inicializado!")
return
# Salva configurações
salvar_configuracoes()
# Testa modelos
await testar_modelos_disponiveis()
# Monitora provedores
await monitorar_provedores()
# Teste básico do chatbot
print("\n" + "=" * 60)
print("TESTE DO CHATBOT")
print("=" * 60)
session_id = "teste_multi_provider"
test_questions = [
"O que é Python?",
"Mostre um exemplo de código Python simples",
"Qual a diferença entre lista e tupla em Python?"
]
for i, question in enumerate(test_questions, 1):
print(f"\n📝 Pergunta {i}: {question}")
print("-" * 40)
try:
resposta = await responder_como_aldo(session_id, question)
print(f"🤖 Resposta: {resposta[:500]}...")
except Exception as e:
print(f"❌ Erro: {e}")
await asyncio.sleep(2) # Pausa entre perguntas
# Limpa memória do teste
print(f"\n🧹 {clear_memory(session_id)}")
# --- Compatibilidade com código original ---
def inicializar_sistema_sync():
"""Função síncrona para compatibilidade """
try:
# A forma mais simples de rodar um evento asyncio a partir de um contexto síncrono
# é usar asyncio.run(). A lógica de checar o loop pode ser simplificada.
return asyncio.run(inicializar_sistema())
except Exception as e:
print(f"Erro na inicialização: {e}")
return False, {}
# Para compatibilidade, mantém a função original
responder_como_aldo_original = responder_como_aldo_sync
if __name__ == "__main__":
# Executa o sistema
asyncio.run(main()) |