aldohenrique commited on
Commit
abfcded
·
verified ·
1 Parent(s): 07507ea

Update ai_logic.py

Browse files
Files changed (1) hide show
  1. ai_logic.py +263 -59
ai_logic.py CHANGED
@@ -15,34 +15,173 @@ from langchain.embeddings import HuggingFaceEmbeddings
15
  BLOG_URL = "https://aldohenrique.com.br/"
16
  VECTOR_STORE_PATH = "faiss_index_store.pkl"
17
  PROCESSED_URLS_PATH = "processed_urls.pkl"
 
18
 
19
  # --- Configuração da API Hugging Face ---
20
  HF_TOKEN = os.getenv("HF_TOKEN")
21
  if not HF_TOKEN:
22
  raise ValueError("Token HF_TOKEN não encontrado nas variáveis de ambiente")
23
 
24
- MODELS = {
25
- "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
26
- "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
27
- "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta",
28
- "Gemma 2B (Google)": "google/gemma-2b-it",
29
- "Open Hermes 2.5 (teknium)": "teknium/OpenHermes-2.5-Mistral-7B",
30
- "TinyLlama 1.1B (TinyLlama Project)": "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
31
- }
32
  MODELS = {
33
  "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
34
  "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
35
  "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta"
36
  }
37
 
38
-
39
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
40
 
41
- # --- Variáveis Globais para o RAG ---
42
  vector_store: Optional[FAISS] = None
 
 
 
43
 
44
  # ==============================================================================
45
- # SEÇÃO RAG: FUNÇÕES PARA CRAWLING, EMBEDDING E ARMAZENAMENTO
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  # ==============================================================================
47
 
48
  def get_all_blog_links(url: str, processed_urls: set) -> set:
@@ -65,13 +204,11 @@ def get_all_blog_links(url: str, processed_urls: set) -> set:
65
  for link in soup.find_all('a', href=True):
66
  href = link['href']
67
  full_url = urljoin(url, href)
68
- # Garante que estamos no mesmo domínio e não é um link de âncora
69
  if urlparse(full_url).netloc == urlparse(url).netloc and full_url not in visited_links:
70
  links_to_visit.add(full_url)
71
  except requests.RequestException as e:
72
  print(f"Erro ao acessar {current_url}: {e}")
73
 
74
- # Filtra apenas as páginas que parecem ser posts ou páginas de conteúdo
75
  final_links = {link for link in visited_links if '/tag/' not in link and '/category/' not in link and '?' not in link}
76
  return final_links
77
 
@@ -80,7 +217,6 @@ def scrape_text_from_url(url: str) -> str:
80
  try:
81
  response = requests.get(url, timeout=10)
82
  soup = BeautifulSoup(response.content, 'html.parser')
83
- # Tenta encontrar a tag <article> ou <main> que geralmente contém o conteúdo principal
84
  main_content = soup.find('article') or soup.find('main')
85
  if main_content:
86
  return main_content.get_text(separator='\n', strip=True)
@@ -92,8 +228,6 @@ def scrape_text_from_url(url: str) -> str:
92
  def build_and_save_vector_store() -> Tuple[str, Optional[str], Optional[str]]:
93
  """
94
  Função principal do RAG: raspa o blog, cria chunks, gera embeddings e salva o vector store.
95
- Esta é a nossa função de "treino".
96
- Retorna uma tupla (mensagem_status, caminho_do_arquivo_faiss_para_download, caminho_do_arquivo_urls_para_download).
97
  """
98
  global vector_store
99
  start_time = time.time()
@@ -101,31 +235,26 @@ def build_and_save_vector_store() -> Tuple[str, Optional[str], Optional[str]]:
101
  print("Iniciando o processo de retreino do RAG...")
102
  processed_urls = set()
103
 
104
- # 1. Obter todos os links do blog
105
  all_links = get_all_blog_links(BLOG_URL, processed_urls)
106
  print(f"Encontrados {len(all_links)} links para processar.")
107
 
108
- # 2. Raspar o texto de cada link
109
  all_texts = [scrape_text_from_url(link) for link in all_links if link not in processed_urls]
110
- all_texts = [text for text in all_texts if text] # Remove textos vazios
111
  print(f"Textos extraídos de {len(all_texts)} novas páginas.")
112
 
113
  if not all_texts:
114
- return "Nenhum novo conteúdo encontrado para treinar.", None, None # Retorna None para os arquivos se não houver conteúdo
115
 
116
- # 3. Dividir os textos em chunks
117
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
118
  chunks = text_splitter.create_documents(all_texts)
119
  print(f"Textos divididos em {len(chunks)} chunks.")
120
 
121
- # 4. Criar embeddings e o vector store (FAISS)
122
  print("Carregando modelo de embedding...")
123
  embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
124
 
125
  print("Criando o vector store com FAISS...")
126
  vector_store = FAISS.from_documents(chunks, embeddings_model)
127
 
128
- # 5. Salvar o vector store e as URLs processadas em disco
129
  with open(VECTOR_STORE_PATH, "wb") as f:
130
  pickle.dump(vector_store, f)
131
 
@@ -146,10 +275,8 @@ def load_vector_store():
146
  print("Vector store carregado com sucesso.")
147
  else:
148
  print("Nenhum vector store encontrado. É necessário treinar o modelo.")
149
- # Inicia o treino automaticamente se não houver um índice
150
- # Modificado para ignorar o retorno dos caminhos dos arquivos ao carregar
151
  message, _, _ = build_and_save_vector_store()
152
- print(message) # Imprime a mensagem de status do treino inicial
153
 
154
  def retrieve_context_from_blog(query: str, k: int = 3) -> str:
155
  """Busca no vector store por chunks de texto similares à pergunta."""
@@ -242,14 +369,13 @@ class HuggingFaceAPIClient:
242
  api_client = HuggingFaceAPIClient(HF_TOKEN)
243
 
244
  # ==============================================================================
245
- # SEÇÃO PRINCIPAL: LÓGICA DO CHATBOT
246
  # ==============================================================================
247
 
248
  def formatar_resposta_com_codigo(resposta: str) -> str:
249
  """Formata a resposta destacando códigos em blocos separados."""
250
  if not resposta: return resposta
251
 
252
- # Primeiro, substituir < e > por entidades HTML para evitar interpretação como tags
253
  resposta = resposta.replace('<', '&lt;').replace('>', '&gt;')
254
  resposta_formatada = re.sub(
255
  r'```(\w+)?\n(.*?)\n```',
@@ -270,44 +396,72 @@ def formatar_resposta_com_codigo(resposta: str) -> str:
270
  return resposta_formatada
271
 
272
  def responder_como_aldo(pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
273
- """Função principal para gerar respostas, agora com RAG."""
274
  if not pergunta.strip():
275
  return "Por favor, faça uma pergunta."
276
 
277
  try:
 
 
 
278
  # --- ETAPA DE RAG ---
279
  print(f"Buscando contexto para a pergunta: '{pergunta[:50]}...'")
280
  contexto_blog = retrieve_context_from_blog(pergunta)
281
 
282
- # Montar o prompt do sistema com o contexto do RAG
283
- system_prompt = (
284
- "Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial. "
285
- "Responda com clareza, profundidade e tom acadêmico. Foque em explicar e não em só mostrar o resultado. "
286
- "Responda sempre em português brasileiro. Use blocos de código formatados com ```. "
287
- "Não responda se a pergunta não for sobre o universo de programação e tecnologia."
288
- "Nem sempre fornecer código, mas quando tiver código, sempre explique utilizando comentários, o aluno precisa aprender lendo os comentários."
289
- "Quando for pergunta sobre disciplinas, foque no conteúdo do blog."
290
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
- # Montar prompt do usuário, injetando o contexto do blog
 
 
 
 
 
293
  if contexto_blog:
294
- pergunta_completa = (
295
- "Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial. "
296
- "Com base no seguinte contexto extraído do seu blog, responda à pergunta do usuário.\n\n"
297
- "--- CONTEXTO DO BLOG ---\n"
298
- f"{contexto_blog}\n"
299
- "--- FIM DO CONTEXTO ---\n\n"
300
- f"PERGUNTA DO USUÁRIO: {pergunta}"
301
- "Responda sempre em português brasileiro. Use blocos de código formatados com ```. "
302
- "Não responda nada se a pergunta não for sobre o universo de programação e tecnologia."
303
- "Nem sempre fornecer código, mas quando tiver código, sempre explique utilizando comentários, o aluno precisa aprender lendo os comentários."
304
- "Quando for pergunta sobre disciplinas, foque no conteúdo do blog."
305
- )
306
- print("Contexto encontrado e injetado no prompt.")
307
- else:
308
- pergunta_completa = f"{pergunta} Não responda nada se a pergunta não for sobre o universo de programação e tecnologia, informe que o Dr. Aldo Henrique só tem domínio em TI. Você é o Professor Dr. Aldo Henrique, foque em explicar e não em só mostrar o resultado. Quando apresentar código, use blocos de código formatados com ```. Sempre responda primeiro a explicação e depois modestre o código."
309
- print("Nenhum contexto relevante encontrado no blog, usando prompt padrão.")
310
-
311
  messages = [
312
  {"role": "system", "content": system_prompt},
313
  {"role": "user", "content": pergunta_completa}
@@ -318,14 +472,20 @@ def responder_como_aldo(pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) ->
318
 
319
  if resposta.startswith("Assistente: "):
320
  resposta = resposta.replace("Assistente: ", "")
321
-
 
 
 
322
  resposta_formatada = formatar_resposta_com_codigo(resposta.strip())
323
  return resposta_formatada
324
 
325
  except Exception as e:
326
  return f"Erro ao processar sua pergunta: {str(e)}"
327
 
328
- # Funções de teste
 
 
 
329
  def verificar_modelo_disponivel(model_name: str) -> str:
330
  try:
331
  url = f"https://api-inference.huggingface.co/models/{model_name}"
@@ -344,4 +504,48 @@ def testar_todos_modelos():
344
  for nome, modelo in MODELS.items():
345
  status = verificar_modelo_disponivel(modelo)
346
  resultados.append(f"{nome}: {status}")
347
- return "\n".join(resultados)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  BLOG_URL = "https://aldohenrique.com.br/"
16
  VECTOR_STORE_PATH = "faiss_index_store.pkl"
17
  PROCESSED_URLS_PATH = "processed_urls.pkl"
18
+ CONVERSATION_MEMORY_PATH = "conversation_memory.json" # Novo arquivo para memória
19
 
20
  # --- Configuração da API Hugging Face ---
21
  HF_TOKEN = os.getenv("HF_TOKEN")
22
  if not HF_TOKEN:
23
  raise ValueError("Token HF_TOKEN não encontrado nas variáveis de ambiente")
24
 
 
 
 
 
 
 
 
 
25
  MODELS = {
26
  "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
27
  "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
28
  "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta"
29
  }
30
 
 
31
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
32
 
33
+ # --- Variáveis Globais para o RAG e Memória ---
34
  vector_store: Optional[FAISS] = None
35
+ conversation_memory: List[Dict[str, str]] = [] # Memória da conversa atual
36
+ max_memory_length = 10 # Máximo de trocas de mensagens na memória
37
+ user_profile = {} # Perfil do usuário (interesses, nível de conhecimento, etc.)
38
 
39
  # ==============================================================================
40
+ # SEÇÃO MEMÓRIA: GERENCIAMENTO DA CONVERSA E PERFIL DO USUÁRIO
41
+ # ==============================================================================
42
+
43
+ def load_conversation_memory():
44
+ """Carrega a memória da conversa do arquivo JSON."""
45
+ global conversation_memory, user_profile
46
+ try:
47
+ if os.path.exists(CONVERSATION_MEMORY_PATH):
48
+ with open(CONVERSATION_MEMORY_PATH, 'r', encoding='utf-8') as f:
49
+ data = json.load(f)
50
+ conversation_memory = data.get('conversation', [])
51
+ user_profile = data.get('user_profile', {})
52
+ print(f"Memória carregada: {len(conversation_memory)} mensagens")
53
+ else:
54
+ conversation_memory = []
55
+ user_profile = {}
56
+ print("Nova conversa iniciada")
57
+ except Exception as e:
58
+ print(f"Erro ao carregar memória: {e}")
59
+ conversation_memory = []
60
+ user_profile = {}
61
+
62
+ def save_conversation_memory():
63
+ """Salva a memória da conversa no arquivo JSON."""
64
+ try:
65
+ data = {
66
+ 'conversation': conversation_memory,
67
+ 'user_profile': user_profile,
68
+ 'last_updated': time.time()
69
+ }
70
+ with open(CONVERSATION_MEMORY_PATH, 'w', encoding='utf-8') as f:
71
+ json.dump(data, f, ensure_ascii=False, indent=2)
72
+ except Exception as e:
73
+ print(f"Erro ao salvar memória: {e}")
74
+
75
+ def add_to_memory(user_message: str, assistant_response: str):
76
+ """Adiciona uma troca de mensagens à memória."""
77
+ global conversation_memory
78
+
79
+ conversation_memory.append({
80
+ "role": "user",
81
+ "content": user_message,
82
+ "timestamp": time.time()
83
+ })
84
+
85
+ conversation_memory.append({
86
+ "role": "assistant",
87
+ "content": assistant_response,
88
+ "timestamp": time.time()
89
+ })
90
+
91
+ # Limita o tamanho da memória
92
+ if len(conversation_memory) > max_memory_length * 2: # *2 porque temos user + assistant
93
+ conversation_memory = conversation_memory[-max_memory_length * 2:]
94
+
95
+ save_conversation_memory()
96
+
97
+ def update_user_profile(user_message: str):
98
+ """Atualiza o perfil do usuário baseado nas mensagens."""
99
+ global user_profile
100
+
101
+ # Detecta tópicos de interesse
102
+ topics = {
103
+ 'java': ['java', 'classe', 'objeto', 'herança', 'polimorfismo'],
104
+ 'c': ['linguagem c', 'ponteiro', 'malloc', 'struct'],
105
+ 'web': ['html', 'css', 'javascript', 'react', 'node'],
106
+ 'ia': ['inteligência artificial', 'machine learning', 'neural', 'algoritmo'],
107
+ 'banco_dados': ['sql', 'database', 'banco de dados', 'mysql']
108
+ }
109
+
110
+ user_message_lower = user_message.lower()
111
+
112
+ for topic, keywords in topics.items():
113
+ if any(keyword in user_message_lower for keyword in keywords):
114
+ user_profile[f'interesse_{topic}'] = user_profile.get(f'interesse_{topic}', 0) + 1
115
+
116
+ # Detecta nível de conhecimento baseado na complexidade das perguntas
117
+ if any(word in user_message_lower for word in ['básico', 'iniciante', 'começar', 'o que é']):
118
+ user_profile['nivel'] = 'iniciante'
119
+ elif any(word in user_message_lower for word in ['avançado', 'complexo', 'otimização', 'performance']):
120
+ user_profile['nivel'] = 'avançado'
121
+ elif user_profile.get('nivel') is None:
122
+ user_profile['nivel'] = 'intermediario'
123
+
124
+ user_profile['total_perguntas'] = user_profile.get('total_perguntas', 0) + 1
125
+
126
+ def get_conversation_context() -> str:
127
+ """Gera um resumo do contexto da conversa para o prompt."""
128
+ if not conversation_memory:
129
+ return ""
130
+
131
+ # Pega as últimas 6 mensagens (3 trocas)
132
+ recent_messages = conversation_memory[-6:] if len(conversation_memory) > 6 else conversation_memory
133
+
134
+ context = "--- CONTEXTO DA CONVERSA ANTERIOR ---\n"
135
+ for msg in recent_messages:
136
+ role = "USUÁRIO" if msg["role"] == "user" else "PROFESSOR"
137
+ # Limita o tamanho de cada mensagem no contexto
138
+ content = msg["content"][:200] + "..." if len(msg["content"]) > 200 else msg["content"]
139
+ context += f"{role}: {content}\n"
140
+
141
+ context += "--- FIM DO CONTEXTO DA CONVERSA ---\n"
142
+ return context
143
+
144
+ def get_user_profile_context() -> str:
145
+ """Gera informações sobre o perfil do usuário para personalizar a resposta."""
146
+ if not user_profile:
147
+ return ""
148
+
149
+ context = "--- PERFIL DO ALUNO ---\n"
150
+
151
+ # Nível de conhecimento
152
+ nivel = user_profile.get('nivel', 'intermediario')
153
+ context += f"Nível: {nivel}\n"
154
+
155
+ # Principais interesses
156
+ interesses = []
157
+ for key, value in user_profile.items():
158
+ if key.startswith('interesse_') and value > 0:
159
+ topic = key.replace('interesse_', '').replace('_', ' ')
160
+ interesses.append(f"{topic} ({value}x)")
161
+
162
+ if interesses:
163
+ context += f"Principais interesses: {', '.join(interesses)}\n"
164
+
165
+ total = user_profile.get('total_perguntas', 0)
166
+ context += f"Total de perguntas feitas: {total}\n"
167
+ context += "--- FIM DO PERFIL DO ALUNO ---\n"
168
+
169
+ return context
170
+
171
+ def clear_memory():
172
+ """Limpa a memória da conversa (função útil para resetar o chat)."""
173
+ global conversation_memory, user_profile
174
+ conversation_memory = []
175
+ user_profile = {}
176
+ try:
177
+ if os.path.exists(CONVERSATION_MEMORY_PATH):
178
+ os.remove(CONVERSATION_MEMORY_PATH)
179
+ return "✅ Memória da conversa limpa com sucesso!"
180
+ except Exception as e:
181
+ return f"❌ Erro ao limpar memória: {e}"
182
+
183
+ # ==============================================================================
184
+ # SEÇÃO RAG: FUNÇÕES PARA CRAWLING, EMBEDDING E ARMAZENAMENTO (SEM ALTERAÇÕES)
185
  # ==============================================================================
186
 
187
  def get_all_blog_links(url: str, processed_urls: set) -> set:
 
204
  for link in soup.find_all('a', href=True):
205
  href = link['href']
206
  full_url = urljoin(url, href)
 
207
  if urlparse(full_url).netloc == urlparse(url).netloc and full_url not in visited_links:
208
  links_to_visit.add(full_url)
209
  except requests.RequestException as e:
210
  print(f"Erro ao acessar {current_url}: {e}")
211
 
 
212
  final_links = {link for link in visited_links if '/tag/' not in link and '/category/' not in link and '?' not in link}
213
  return final_links
214
 
 
217
  try:
218
  response = requests.get(url, timeout=10)
219
  soup = BeautifulSoup(response.content, 'html.parser')
 
220
  main_content = soup.find('article') or soup.find('main')
221
  if main_content:
222
  return main_content.get_text(separator='\n', strip=True)
 
228
  def build_and_save_vector_store() -> Tuple[str, Optional[str], Optional[str]]:
229
  """
230
  Função principal do RAG: raspa o blog, cria chunks, gera embeddings e salva o vector store.
 
 
231
  """
232
  global vector_store
233
  start_time = time.time()
 
235
  print("Iniciando o processo de retreino do RAG...")
236
  processed_urls = set()
237
 
 
238
  all_links = get_all_blog_links(BLOG_URL, processed_urls)
239
  print(f"Encontrados {len(all_links)} links para processar.")
240
 
 
241
  all_texts = [scrape_text_from_url(link) for link in all_links if link not in processed_urls]
242
+ all_texts = [text for text in all_texts if text]
243
  print(f"Textos extraídos de {len(all_texts)} novas páginas.")
244
 
245
  if not all_texts:
246
+ return "Nenhum novo conteúdo encontrado para treinar.", None, None
247
 
 
248
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
249
  chunks = text_splitter.create_documents(all_texts)
250
  print(f"Textos divididos em {len(chunks)} chunks.")
251
 
 
252
  print("Carregando modelo de embedding...")
253
  embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
254
 
255
  print("Criando o vector store com FAISS...")
256
  vector_store = FAISS.from_documents(chunks, embeddings_model)
257
 
 
258
  with open(VECTOR_STORE_PATH, "wb") as f:
259
  pickle.dump(vector_store, f)
260
 
 
275
  print("Vector store carregado com sucesso.")
276
  else:
277
  print("Nenhum vector store encontrado. É necessário treinar o modelo.")
 
 
278
  message, _, _ = build_and_save_vector_store()
279
+ print(message)
280
 
281
  def retrieve_context_from_blog(query: str, k: int = 3) -> str:
282
  """Busca no vector store por chunks de texto similares à pergunta."""
 
369
  api_client = HuggingFaceAPIClient(HF_TOKEN)
370
 
371
  # ==============================================================================
372
+ # SEÇÃO PRINCIPAL: LÓGICA DO CHATBOT COM MEMÓRIA
373
  # ==============================================================================
374
 
375
  def formatar_resposta_com_codigo(resposta: str) -> str:
376
  """Formata a resposta destacando códigos em blocos separados."""
377
  if not resposta: return resposta
378
 
 
379
  resposta = resposta.replace('<', '&lt;').replace('>', '&gt;')
380
  resposta_formatada = re.sub(
381
  r'```(\w+)?\n(.*?)\n```',
 
396
  return resposta_formatada
397
 
398
  def responder_como_aldo(pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
399
+ """Função principal para gerar respostas, agora com RAG e MEMÓRIA."""
400
  if not pergunta.strip():
401
  return "Por favor, faça uma pergunta."
402
 
403
  try:
404
+ # Atualiza o perfil do usuário baseado na pergunta
405
+ update_user_profile(pergunta)
406
+
407
  # --- ETAPA DE RAG ---
408
  print(f"Buscando contexto para a pergunta: '{pergunta[:50]}...'")
409
  contexto_blog = retrieve_context_from_blog(pergunta)
410
 
411
+ # --- ETAPA DE MEMÓRIA ---
412
+ contexto_conversa = get_conversation_context()
413
+ contexto_perfil = get_user_profile_context()
414
+
415
+ # Prompt do sistema personalizado baseado no perfil do usuário
416
+ nivel = user_profile.get('nivel', 'intermediario')
417
+
418
+ system_prompt = f"""Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial.
419
+
420
+ PERSONALIDADE E COMPORTAMENTO:
421
+ - Seja caloroso, acolhedor e paciente como um professor humano experiente
422
+ - Demonstre interesse genuíno pelo aprendizado do aluno
423
+ - Use um tom conversacional e amigável, mas mantenha autoridade acadêmica
424
+ - Quando apropriado, faça conexões com conversas anteriores
425
+ - Celebre o progresso do aluno e encoraje quando necessário
426
+ - Adapte sua linguagem ao nível do aluno: {nivel}
427
+
428
+ ESTILO DE ENSINO:
429
+ - Sempre explique o "porquê" antes do "como"
430
+ - Use analogias e exemplos práticos
431
+ - Encoraje perguntas e curiosidade
432
+ - Quando mostrar código, sempre explique com comentários detalhados
433
+ - Foque na compreensão, não apenas na solução
434
+ - Conecte conceitos com aplicações do mundo real
435
+
436
+ REGRAS:
437
+ - Responda sempre em português brasileiro
438
+ - Use blocos de código formatados com ```
439
+ - Só responda perguntas relacionadas a programação e tecnologia
440
+ - Se não for sobre TI, informe educadamente que sua especialidade é tecnologia
441
+ - Quando apresentar código, sempre explique linha por linha nos comentários"""
442
+
443
+ # Monta o prompt completo com todos os contextos
444
+ prompt_parts = []
445
 
446
+ if contexto_perfil:
447
+ prompt_parts.append(contexto_perfil)
448
+
449
+ if contexto_conversa:
450
+ prompt_parts.append(contexto_conversa)
451
+
452
  if contexto_blog:
453
+ prompt_parts.append("--- CONTEXTO DO SEU BLOG ---")
454
+ prompt_parts.append(contexto_blog)
455
+ prompt_parts.append("--- FIM DO CONTEXTO DO BLOG ---")
456
+
457
+ prompt_parts.append(f"PERGUNTA ATUAL DO ALUNO: {pergunta}")
458
+
459
+ # Adiciona instruções específicas baseadas no contexto
460
+ if contexto_conversa:
461
+ prompt_parts.append("\nIMPORTANTE: Considere o contexto da nossa conversa anterior ao responder. Se esta pergunta se relaciona com algo que discutimos, faça essa conexão naturalmente.")
462
+
463
+ pergunta_completa = "\n\n".join(prompt_parts)
464
+
 
 
 
 
 
465
  messages = [
466
  {"role": "system", "content": system_prompt},
467
  {"role": "user", "content": pergunta_completa}
 
472
 
473
  if resposta.startswith("Assistente: "):
474
  resposta = resposta.replace("Assistente: ", "")
475
+
476
+ # Adiciona a conversa à memória
477
+ add_to_memory(pergunta, resposta)
478
+
479
  resposta_formatada = formatar_resposta_com_codigo(resposta.strip())
480
  return resposta_formatada
481
 
482
  except Exception as e:
483
  return f"Erro ao processar sua pergunta: {str(e)}"
484
 
485
+ # ==============================================================================
486
+ # FUNÇÕES AUXILIARES E DE TESTE
487
+ # ==============================================================================
488
+
489
  def verificar_modelo_disponivel(model_name: str) -> str:
490
  try:
491
  url = f"https://api-inference.huggingface.co/models/{model_name}"
 
504
  for nome, modelo in MODELS.items():
505
  status = verificar_modelo_disponivel(modelo)
506
  resultados.append(f"{nome}: {status}")
507
+ return "\n".join(resultados)
508
+
509
+ def get_memory_stats() -> str:
510
+ """Retorna estatísticas da memória atual."""
511
+ total_messages = len(conversation_memory)
512
+ user_messages = len([m for m in conversation_memory if m["role"] == "user"])
513
+
514
+ stats = f"📊 **Estatísticas da Memória:**\n"
515
+ stats += f"• Total de mensagens: {total_messages}\n"
516
+ stats += f"• Perguntas do usuário: {user_messages}\n"
517
+ stats += f"• Nível detectado: {user_profile.get('nivel', 'Não definido')}\n"
518
+
519
+ # Principais interesses
520
+ interesses = []
521
+ for key, value in user_profile.items():
522
+ if key.startswith('interesse_') and value > 0:
523
+ topic = key.replace('interesse_', '').replace('_', ' ').title()
524
+ interesses.append(f"{topic} ({value})")
525
+
526
+ if interesses:
527
+ stats += f"• Principais interesses: {', '.join(interesses)}\n"
528
+
529
+ return stats
530
+
531
+ # ==============================================================================
532
+ # INICIALIZAÇÃO DO SISTEMA
533
+ # ==============================================================================
534
+
535
+ def inicializar_sistema():
536
+ """Inicializa todos os componentes do sistema."""
537
+ print("🚀 Inicializando o Chatbot Dr. Aldo Henrique com Memória...")
538
+
539
+ # Carrega o vector store (RAG)
540
+ load_vector_store()
541
+
542
+ # Carrega a memória de conversas
543
+ load_conversation_memory()
544
+
545
+ print("✅ Sistema inicializado com sucesso!")
546
+ print(f"💬 Memória: {len(conversation_memory)} mensagens carregadas")
547
+ print(f"🧠 Vector Store: {'Carregado' if vector_store else 'Não encontrado'}")
548
+
549
+ # Chama a inicialização quando o módulo é carregado
550
+ if __name__ == "__main__":
551
+ inicializar_sistema()