aldohenrique commited on
Commit
d4a357d
·
verified ·
1 Parent(s): 3bdb3bd

Update ai_logic.py

Browse files
Files changed (1) hide show
  1. ai_logic.py +361 -566
ai_logic.py CHANGED
@@ -1,9 +1,8 @@
1
- melhore esse código, as perguntas não estão sendo respondida corretamente, simplifique, a memoria tem de ser só do usuario da sessão, e não uma unica memoria geral para todos, mostre só o código completo:  import requests
2
  import os
3
- import json
4
  import re
5
  import time
6
  import pickle
 
7
  from typing import Dict, Any, List, Optional, Tuple
8
  from bs4 import BeautifulSoup
9
  from urllib.parse import urljoin, urlparse
@@ -11,17 +10,20 @@ from langchain.text_splitter import RecursiveCharacterTextSplitter
11
  from langchain.vectorstores import FAISS
12
  from langchain.embeddings import HuggingFaceEmbeddings
13
 
14
- # --- Configuração do RAG ---
 
 
 
 
 
 
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 não será mais uma constante única
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
      "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
27
      "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
@@ -29,593 +31,386 @@ MODELS = {
29
      "Gemma 7B (Google)":"google/gemma-7b-it",
30
      "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta"
31
  }
32
-
33
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
34
 
35
- # --- Variáveis Globais para o RAG e Memória ---
 
36
  vector_store: Optional[FAISS] = None
37
- # Dicionário para armazenar a memória de cada usuário/sessão
38
- # Formato: {session_id: {'conversation': List[Dict], 'user_profile': Dict}}
 
39
  user_sessions: Dict[str, Dict[str, Any]] = {}
40
- max_memory_length = 10  # Máximo de trocas de mensagens na memória
41
 
42
  # ==============================================================================
43
- # SEÇÃO MEMÓRIA: GERENCIAMENTO DA CONVERSA E PERFIL DO USUÁRIO
44
  # ==============================================================================
45
 
46
- def get_session_memory_path(session_id: str) -> str:
47
-     """Retorna o caminho do arquivo de memória para uma dada sessão."""
48
-     return f"conversation_memory_{session_id}.json"
49
-
50
- def load_conversation_memory(session_id: str):
51
-     """Carrega a memória da conversa para uma sessão específica."""
52
-     memory_path = get_session_memory_path(session_id)
53
-     session_data = {'conversation': [], 'user_profile': {}}
54
-
55
-     try:
56
-         if os.path.exists(memory_path):
57
-             with open(memory_path, 'r', encoding='utf-8') as f:
58
-                 data = json.load(f)
59
-                 session_data['conversation'] = data.get('conversation', [])
60
-                 session_data['user_profile'] = data.get('user_profile', {})
61
-             print(f"Memória para sessão '{session_id}' carregada: {len(session_data['conversation'])} mensagens")
62
-         else:
63
-             print(f"Nova conversa iniciada para sessão '{session_id}'")
64
-     except Exception as e:
65
-         print(f"Erro ao carregar memória para sessão '{session_id}': {e}")
66
-     
67
-     user_sessions[session_id] = session_data
68
-
69
- def save_conversation_memory(session_id: str):
70
-     """Salva a memória da conversa para uma sessão específica."""
71
-     memory_path = get_session_memory_path(session_id)
72
-     session_data = user_sessions.get(session_id, {'conversation': [], 'user_profile': {}})
73
-
74
-     try:
75
-         data = {
76
-             'conversation': session_data['conversation'],
77
-             'user_profile': session_data['user_profile'],
78
-             'last_updated': time.time()
79
-         }
80
-         with open(memory_path, 'w', encoding='utf-8') as f:
81
-             json.dump(data, f, ensure_ascii=False, indent=2)
82
-     except Exception as e:
83
-         print(f"Erro ao salvar memória para sessão '{session_id}': {e}")
84
-
85
- def add_to_memory(session_id: str, user_message: str, assistant_response: str):
86
-     """Adiciona uma troca de mensagens à memória de uma sessão específica."""
87
-     
88
-     if session_id not in user_sessions:
89
-         load_conversation_memory(session_id) # Garante que a sessão está carregada
90
-
91
-     session_data = user_sessions[session_id]
92
-     conversation = session_data['conversation']
93
-     
94
-     conversation.append({
95
-         "role": "user",
96
-     ��   "content": user_message,
97
-         "timestamp": time.time()
98
-     })
99
-     
100
-     conversation.append({
101
-         "role": "assistant",
102
-         "content": assistant_response,
103
-         "timestamp": time.time()
104
-     })
105
-     
106
-     # Limita o tamanho da memória
107
-     if len(conversation) > max_memory_length * 2:  # *2 porque temos user + assistant
108
-         user_sessions[session_id]['conversation'] = conversation[-max_memory_length * 2:]
109
-     
110
-     save_conversation_memory(session_id)
111
 
112
  def update_user_profile(session_id: str, user_message: str):
113
-     """Atualiza o perfil do usuário de uma sessão específica baseado nas mensagens."""
114
-     if session_id not in user_sessions:
115
-         load_conversation_memory(session_id) # Garante que a sessão está carregada
116
-
117
-     user_profile = user_sessions[session_id]['user_profile']
118
-     
119
-     # Detecta tópicos de interesse
120
-     topics = {
121
-         'java': ['java', 'classe', 'objeto', 'herança', 'polimorfismo'],
122
-         'c': ['linguagem c', 'ponteiro', 'malloc', 'struct'],
123
-         'web': ['html', 'css', 'javascript', 'react', 'node'],
124
-         'ia': ['inteligência artificial', 'machine learning', 'neural', 'algoritmo'],
125
-         'banco_dados': ['sql', 'database', 'banco de dados', 'mysql']
126
-     }
127
-     
128
-     user_message_lower = user_message.lower()
129
-     
130
-     for topic, keywords in topics.items():
131
-         if any(keyword in user_message_lower for keyword in keywords):
132
-             user_profile[f'interesse_{topic}'] = user_profile.get(f'interesse_{topic}', 0) + 1
133
-     
134
-     # Detecta nível de conhecimento baseado na complexidade das perguntas
135
-     if any(word in user_message_lower for word in ['básico', 'iniciante', 'começar', 'o que é']):
136
-         user_profile['nivel'] = 'iniciante'
137
-     elif any(word in user_message_lower for word in ['avançado', 'complexo', 'otimização', 'performance']):
138
-         user_profile['nivel'] = 'avançado'
139
-     elif user_profile.get('nivel') is None:
140
-         user_profile['nivel'] = 'intermediario'
141
-     
142
-     user_profile['total_perguntas'] = user_profile.get('total_perguntas', 0) + 1
143
-
144
-     user_sessions[session_id]['user_profile'] = user_profile # Atualiza no dicionário global
145
-
146
- def get_conversation_context(session_id: str) -> str:
147
-     """Gera um resumo do contexto da conversa para o prompt de uma sessão específica."""
148
-     if session_id not in user_sessions:
149
-         load_conversation_memory(session_id)
150
-
151
-     conversation_history = user_sessions[session_id]['conversation']
152
-     if not conversation_history:
153
-         return ""
154
-     
155
-     # Pega as últimas 6 mensagens (3 trocas)
156
-     recent_messages = conversation_history[-6:] if len(conversation_history) > 6 else conversation_history
157
-     
158
-     context = "--- CONTEXTO DA CONVERSA ANTERIOR ---\n"
159
-     for msg in recent_messages:
160
-         role = "USUÁRIO" if msg["role"] == "user" else "PROFESSOR"
161
-         # Limita o tamanho de cada mensagem no contexto
162
-         content = msg["content"][:200] + "..." if len(msg["content"]) > 200 else msg["content"]
163
-         context += f"{role}: {content}\n"
164
-     
165
-     context += "--- FIM DO CONTEXTO DA CONVERSA ---\n"
166
-     return context
167
-
168
- def get_user_profile_context(session_id: str) -> str:
169
-     """Gera informações sobre o perfil do usuário de uma sessão específica para personalizar a resposta."""
170
-     if session_id not in user_sessions:
171
-         load_conversation_memory(session_id)
172
-
173
-     user_profile = user_sessions[session_id]['user_profile']
174
-     if not user_profile:
175
-         return ""
176
-     
177
-     context = "--- PERFIL DO ALUNO ---\n"
178
-     
179
-     # Nível de conhecimento
180
-     nivel = user_profile.get('nivel', 'intermediario')
181
-     context += f"Nível: {nivel}\n"
182
-     
183
-     # Principais interesses
184
-     interesses = []
185
-     for key, value in user_profile.items():
186
-         if key.startswith('interesse_') and value > 0:
187
-             topic = key.replace('interesse_', '').replace('_', ' ')
188
-             interesses.append(f"{topic} ({value}x)")
189
-     
190
-     if interesses:
191
-         context += f"Principais interesses: {', '.join(interesses)}\n"
192
-     
193
-     total = user_profile.get('total_perguntas', 0)
194
-     context += f"Total de perguntas feitas: {total}\n"
195
-     context += "--- FIM DO PERFIL DO ALUNO ---\n"
196
-     
197
-     return context
198
-
199
- def clear_memory(session_id: str):
200
-     """Limpa a memória da conversa de uma sessão específica."""
201
-     memory_path = get_session_memory_path(session_id)
202
-
203
-     if session_id in user_sessions:
204
-         del user_sessions[session_id] # Remove da memória em tempo de execução
205
-     
206
-     try:
207
-         if os.path.exists(memory_path):
208
-             os.remove(memory_path)
209
-         return "✅ Memória da conversa limpa com sucesso!"
210
-     except Exception as e:
211
-         return f"❌ Erro ao limpar memória: {e}"
212
 
213
  # ==============================================================================
214
- # SEÇÃO RAG: FUNÇÕES PARA CRAWLING, EMBEDDING E ARMAZENAMENTO (SEM ALTERAÇÕES)
215
  # ==============================================================================
216
 
217
- def get_all_blog_links(url: str, processed_urls: set) -> set:
218
-     """Navega pelo blog para encontrar todos os links de posts e páginas."""
219
-     links_to_visit = {url}
220
-     visited_links = set()
221
-
222
-     while links_to_visit:
223
-         current_url = links_to_visit.pop()
224
-         if current_url in visited_links:
225
-             continue
226
-
227
-         try:
228
-             response = requests.get(current_url, timeout=10)
229
-             response.raise_for_status()
230
-             soup = BeautifulSoup(response.content, 'html.parser')
231
-             visited_links.add(current_url)
232
-             print(f"Visitando: {current_url}")
233
-
234
-             for link in soup.find_all('a', href=True):
235
-                 href = link['href']
236
-                 full_url = urljoin(url, href)
237
-                 if urlparse(full_url).netloc == urlparse(url).netloc and full_url not in visited_links:
238
-                     links_to_visit.add(full_url)
239
-         except requests.RequestException as e:
240
-             print(f"Erro ao acessar {current_url}: {e}")
241
-
242
-     final_links = {link for link in visited_links if '/tag/' not in link and '/category/' not in link and '?' not in link}
243
-     return final_links
244
-
245
  def scrape_text_from_url(url: str) -> str:
246
-     """Extrai o texto principal (de artigos) de uma URL."""
247
-     try:
248
-         response = requests.get(url, timeout=10)
249
-         soup = BeautifulSoup(response.content, 'html.parser')
250
-         main_content = soup.find('article') or soup.find('main')
251
-         if main_content:
252
-             return main_content.get_text(separator='\n', strip=True)
253
-         return ""
254
-     except Exception as e:
255
-         print(f"Erro ao raspar {url}: {e}")
256
-         return ""
257
-
258
- def build_and_save_vector_store() -> Tuple[str, Optional[str], Optional[str]]:
259
-     """
260
-     Função principal do RAG: raspa o blog, cria chunks, gera embeddings e salva o vector store.
261
-     """
262
-     global vector_store
263
-     start_time = time.time()
264
-
265
-     print("Iniciando o processo de retreino do RAG...")
266
-     processed_urls = set()
267
-
268
-     all_links = get_all_blog_links(BLOG_URL, processed_urls)
269
-     print(f"Encontrados {len(all_links)} links para processar.")
270
-
271
-     all_texts = [scrape_text_from_url(link) for link in all_links if link not in processed_urls]
272
-     all_texts = [text for text in all_texts if text]
273
-     print(f"Textos extraídos de {len(all_texts)} novas páginas.")
274
-
275
-     if not all_texts:
276
-         return "Nenhum novo conteúdo encontrado para treinar.", None, None
277
-
278
-     text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
279
-     chunks = text_splitter.create_documents(all_texts)
280
-     print(f"Textos divididos em {len(chunks)} chunks.")
281
-
282
-     print("Carregando modelo de embedding...")
283
-     embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
284
-
285
-     print("Criando o vector store com FAISS...")
286
-     vector_store = FAISS.from_documents(chunks, embeddings_model)
287
-
288
-     with open(VECTOR_STORE_PATH, "wb") as f:
289
-         pickle.dump(vector_store, f)
290
-
291
-     with open(PROCESSED_URLS_PATH, "wb") as f:
292
-         pickle.dump(all_links, f)
293
-
294
-     end_time = time.time()
295
-     message = f"✅ Retreino do RAG concluído em {end_time - start_time:.2f} segundos. {len(chunks)} chunks de texto processados."
296
-     return message, VECTOR_STORE_PATH, PROCESSED_URLS_PATH
297
 
298
  def load_vector_store():
299
-     """Carrega o vector store do arquivo, se existir."""
300
-     global vector_store
301
-     if os.path.exists(VECTOR_STORE_PATH):
302
-         print(f"Carregando vector store existente de '{VECTOR_STORE_PATH}'...")
303
-         with open(VECTOR_STORE_PATH, "rb") as f:
304
-             vector_store = pickle.load(f)
305
-         print("Vector store carregado com sucesso.")
306
-     else:
307
-         print("Nenhum vector store encontrado. É necessário treinar o modelo.")
308
-         message, _, _ = build_and_save_vector_store()
309
-         print(message)
310
-
311
- def retrieve_context_from_blog(query: str, k: int = 3) -> str:
312
-     """Busca no vector store por chunks de texto similares à pergunta."""
313
-     if vector_store:
314
-         try:
315
-             results = vector_store.similarity_search(query, k=k)
316
-             context = "\n\n---\n\n".join([doc.page_content for doc in results])
317
-             return context
318
-         except Exception as e:
319
-             return f"Erro ao buscar contexto: {e}"
320
-     return ""
321
 
322
  # ==============================================================================
323
- # SEÇÃO API CLIENT: CÓDIGO ORIGINAL PARA CHAMAR A API DO HUGGING FACE
324
  # ==============================================================================
325
 
326
  class HuggingFaceAPIClient:
327
-     def __init__(self, token: str):
328
-         self.token = token
329
-         self.headers = {
330
-             "Authorization": f"Bearer {token}",
331
-             "Content-Type": "application/json"
332
-         }
333
-
334
-     def query_model(self, model_name: str, messages: list, max_tokens: int = 1500) -> str:
335
-         """Faz requisição para a API do Hugging Face"""
336
-         url = f"https://api-inference.huggingface.co/models/{model_name}/v1/chat/completions"
337
-         payload = {
338
-             "model": model_name,
339
-             "messages": messages,
340
-             "max_tokens": max_tokens,
341
-             "temperature": 0.7,
342
-             "top_p": 0.9,
343
-             "stream": False
344
-         }
345
-         try:
346
-             response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
347
-             if response.status_code == 200:
348
-                 result = response.json()
349
-                 return result["choices"][0]["message"]["content"]
350
-             else:
351
-                 return self._fallback_text_generation(model_name, messages, max_tokens)
352
-         except Exception as e:
353
-             return f"Erro na API: {str(e)}"
354
-
355
-     def _fallback_text_generation(self, model_name: str, messages: list, max_tokens: int) -> str:
356
-         url = f"https://api-inference.huggingface.co/models/{model_name}"
357
-         prompt = self._messages_to_prompt(messages)
358
-         payload = {
359
-             "inputs": prompt,
360
-             "parameters": {
361
-                 "max_new_tokens": max_tokens, "temperature": 0.7, "top_p": 0.9,
362
-                 "do_sample": True, "return_full_text": False
363
-             },
364
-             "options": {"wait_for_model": True, "use_cache": False}
365
-         }
366
-         try:
367
-             response = requests.post(url, headers=self.headers, json=payload, timeout=9999)
368
-             if response.status_code == 200:
369
-                 result = response.json()
370
-                 if isinstance(result, list) and len(result) > 0:
371
-                     generated_text = result[0].get("generated_text", "")
372
-                     if generated_text:
373
-                         if "Assistente: " in generated_text:
374
-                             parts = generated_text.split("Assistente: ")
375
-                             if len(parts) > 1: return parts[-1].strip()
376
-                         return generated_text.strip()
377
-                     return "Resposta vazia"
378
-                 elif isinstance(result, dict):
379
-                     if "error" in result: return f"Erro do modelo: {result['error']}"
380
-                     elif "generated_text" in result: return result["generated_text"].strip()
381
-                 return "Formato de resposta inesperado"
382
-             elif response.status_code == 404: return f"❌ Modelo '{model_name}' não encontrado."
383
-             elif response.status_code == 503: return "⏳ Modelo carregando... Tente novamente."
384
-             elif response.status_code == 429: return "⚠️ Muitas requisições. Tente novamente."
385
-             else: return f"Erro HTTP {response.status_code}: {response.text[:200]}..."
386
-         except requests.Timeout:
387
-             return "⏰ Timeout - Modelo demorou muito para responder."
388
-         except Exception as e:
389
-             return f"Erro na requisição: {str(e)}"
390
-
391
-     def _messages_to_prompt(self, messages: list) -> str:
392
-         prompt = ""
393
-         for msg in messages:
394
-             prompt += f"{msg['role'].capitalize()}: {msg['content']}\n\n"
395
-         prompt += "Assistente: "
396
-         return prompt
397
-
398
- # Inicializar cliente da API
399
- api_client = HuggingFaceAPIClient(HF_TOKEN)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
  # ==============================================================================
402
- # SEÇÃO PRINCIPAL: LÓGICA DO CHATBOT COM MEMÓRIA POR USUÁRIO
403
  # ==============================================================================
404
 
405
- def formatar_resposta_com_codigo(resposta: str) -> str:
406
-     """Formata a resposta destacando códigos em blocos separados."""
407
-     if not resposta: return resposta
408
-     
409
-     resposta = resposta.replace('<', '&lt;').replace('>', '&gt;')    
410
-     resposta_formatada = re.sub(
411
-         r'```(\w+)?\n(.*?)\n```',
412
-         r'<div style="background-color: #f8f9fa; color: #1a1a1a; border: 1px solid #e9ecef; border-radius: 8px; padding: 15px; margin: 10px 0; font-family: Monaco, Consolas, monospace; overflow-x: auto;"><strong style="color: #1a1a1a;">💻 Código:</strong><br><pre style="color: #1a1a1a; margin: 5px 0; white-space: pre-wrap; word-wrap: break-word;"><code>\2</code></pre></div>',
413
-         resposta, flags=re.DOTALL
414
-     )
415
-     resposta_formatada = re.sub(
416
-         r'`([^`]+)`',
417
-         r'<code style="background-color: #f1f3f4; color: #1a1a1a; padding: 2px 4px; border-radius: 4px; font-family: Monaco, Consolas, monospace;">\1</code>',
418
-         resposta_formatada
419
-     )
420
-     resposta_formatada = resposta_formatada.replace('\n', '<br>')
421
-     resposta_formatada = re.sub(
422
-         r'^\*\*(.*?)\*\*',
423
-         r'<h3 style="color: #1a1a1a; margin-top: 20px; margin-bottom: 10px;">\1</h3>',
424
-         resposta_formatada, flags=re.MULTILINE
425
-     )
426
-     return resposta_formatada
427
-
428
- def responder_como_aldo(session_id: str, pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
429
-     """Função principal para gerar respostas, agora com RAG e MEMÓRIA por sessão."""
430
-     if not pergunta.strip():
431
-         return "Por favor, faça uma pergunta."
432
-
433
-     try:
434
-         # Garante que a sessão do usuário está carregada
435
-         if session_id not in user_sessions:
436
-             load_conversation_memory(session_id)
437
-         
438
-         # Atualiza o perfil do usuário baseado na pergunta
439
-         update_user_profile(session_id, pergunta)
440
-         
441
-         # --- ETAPA DE RAG ---
442
-         print(f"Buscando contexto para a pergunta: '{pergunta[:50]}...' para sessão '{session_id}'")
443
-         contexto_blog = retrieve_context_from_blog(pergunta)
444
-         
445
-         # --- ETAPA DE MEMÓRIA ---
446
-         contexto_conversa = get_conversation_context(session_id)
447
-         contexto_perfil = get_user_profile_context(session_id)
448
-         
449
-         # Prompt do sistema personalizado baseado no perfil do usuário
450
-         nivel = user_sessions[session_id]['user_profile'].get('nivel', 'intermediario')
451
-         
452
-         system_prompt = f"""Você é o professor Dr. Aldo Henrique, especialista em C, Java, desenvolvimento web e inteligência artificial.
453
-
454
- PERSONALIDADE E COMPORTAMENTO:
455
- - Seja caloroso, acolhedor e paciente como um professor humano experiente
456
- - Demonstre interesse genuíno pelo aprendizado do aluno
457
- - Use um tom conversacional e amigável, mas mantenha autoridade acadêmica
458
- - Quando apropriado, faça conexões com conversas anteriores
459
- - Celebre o progresso do aluno e encoraje quando necessário
460
- - Adapte sua linguagem ao nível do aluno: {nivel}
461
-
462
- ESTILO DE ENSINO:
463
- - Sempre explique o "porquê" antes do "como"
464
- - Use analogias e exemplos práticos
465
- - Encoraje perguntas e curiosidade
466
- - Quando mostrar código, sempre explique com comentários detalhados
467
- - Foque na compreensão, não apenas na solução
468
- - Conecte conceitos com aplicações do mundo real
469
-
470
- REGRAS:
471
- - Responda sempre em português brasileiro
472
- - Use blocos de código formatados com ```
473
- - Só responda perguntas relacionadas a programação e tecnologia
474
- - Se não for sobre TI, informe educadamente que sua especialidade é tecnologia
475
- - Quando apresentar código, sempre explique linha por linha nos comentários"""
476
-
477
-         # Monta o prompt completo com todos os contextos
478
-         prompt_parts = []
479
-         
480
-         if contexto_perfil:
481
-             prompt_parts.append(contexto_perfil)
482
-         
483
-         if contexto_conversa:
484
-             prompt_parts.append(contexto_conversa)
485
-             
486
-         if contexto_blog:
487
-             prompt_parts.append("--- CONTEXTO DO SEU BLOG ---")
488
-             prompt_parts.append(contexto_blog)
489
-             prompt_parts.append("--- FIM DO CONTEXTO DO BLOG ---")
490
-         
491
-         prompt_parts.append(f"PERGUNTA ATUAL DO ALUNO: {pergunta}")
492
-         
493
-         # Adiciona instruções específicas baseadas no contexto
494
-         if contexto_conversa:
495
-             prompt_parts.append("\nIMPORTANTE: Considere o contexto da nossa conversa anterior ao responder. Se esta pergunta se relaciona com algo que já discutimos, faça essa conexão naturalmente.")
496
-         
497
-         pergunta_completa = "\n\n".join(prompt_parts)
498
-         
499
-         messages = [
500
-             {"role": "system", "content": system_prompt},
501
-             {"role": "user", "content": pergunta_completa}
502
-         ]
503
-         
504
-         model_name = MODELS.get(modelo_escolhido, MODELS[DEFAULT_MODEL])
505
-         resposta = api_client.query_model(model_name, messages)
506
-         
507
-         if resposta.startswith("Assistente: "):
508
-             resposta = resposta.replace("Assistente: ", "")
509
-         
510
-         # Adiciona a conversa à memória da sessão
511
-         add_to_memory(session_id, pergunta, resposta)
512
-         
513
-         resposta_formatada = formatar_resposta_com_codigo(resposta.strip())
514
-         return resposta_formatada
515
-         
516
-     except Exception as e:
517
-         return f"Erro ao processar sua pergunta: {str(e)}"
518
 
519
- # ==============================================================================
520
- # FUNÇÕES AUXILIARES E DE TESTE
521
- # ==============================================================================
 
 
522
 
523
- def verificar_modelo_disponivel(model_name: str) -> str:
524
-     try:
525
-         url = f"[https://api-inference.huggingface.co/models/](https://api-inference.huggingface.co/models/){model_name}"
526
-         headers = {"Authorization": f"Bearer {HF_TOKEN}"}
527
-         payload = {"inputs": "Hello", "parameters": {"max_new_tokens": 5}}
528
-         response = requests.post(url, headers=headers, json=payload, timeout=9999)
529
-         if response.status_code == 200: return "✅ Disponível"
530
-         elif response.status_code == 404: return "❌ Não encontrado"
531
-         elif response.status_code == 503: return "⏳ Carregando..."
532
-         else: return f"⚠️ Status {response.status_code}"
533
-     except Exception as e:
534
-         return f"❌ Erro: {str(e)[:50]}..."
535
-
536
- def testar_todos_modelos():
537
-     resultados = []
538
-     for nome, modelo in MODELS.items():
539
-         status = verificar_modelo_disponivel(modelo)
540
-         resultados.append(f"{nome}: {status}")
541
-     return "\n".join(resultados)
542
-
543
- def get_memory_stats(session_id: str) -> str:
544
-     """Retorna estatísticas da memória atual para uma sessão específica."""
545
-     if session_id not in user_sessions:
546
-         load_conversation_memory(session_id)
547
-
548
-     session_data = user_sessions[session_id]
549
-     conversation_history = session_data['conversation']
550
-     user_profile = session_data['user_profile']
551
-
552
-     total_messages = len(conversation_history)
553
-     user_messages = len([m for m in conversation_history if m["role"] == "user"])
554
-     
555
-     stats = f"📊 **Estatísticas da Memória para Sessão '{session_id}':**\n"
556
-     stats += f"• Total de mensagens: {total_messages}\n"
557
-     stats += f"• Perguntas do usuário: {user_messages}\n"
558
-     stats += f"• Nível detectado: {user_profile.get('nivel', 'Não definido')}\n"
559
-     
560
-     # Principais interesses
561
-     interesses = []
562
-     for key, value in user_profile.items():
563
-         if key.startswith('interesse_') and value > 0:
564
-             topic = key.replace('interesse_', '').replace('_', ' ').title()
565
-             interesses.append(f"{topic} ({value})")
566
-     
567
-     if interesses:
568
-         stats += f"• Principais interesses: {', '.join(interesses)}\n"
569
-     
570
-     return stats
571
 
572
  # ==============================================================================
573
- # INICIALIZAÇÃO DO SISTEMA
574
  # ==============================================================================
575
 
576
- def inicializar_sistema():
577
-     """Inicializa todos os componentes do sistema."""
578
-     print("🚀 Inicializando o Chatbot Dr. Aldo Henrique com Memória...")
579
-     
580
-     # Carrega o vector store (RAG)
581
-     load_vector_store()
582
-     
583
-     # **NÃO CARREGAMOS MAIS UMA MEMÓRIA GLOBAL AQUI**
584
-     # A memória será carregada e gerenciada por sessão individualmente
585
-     
586
-     print("✅ Sistema inicializado com sucesso!")
587
-     print(f"🧠 Vector Store: {'Carregado' if vector_store else 'Não encontrado'}")
588
-
589
- # Chama a inicialização quando o módulo é carregado
590
  if __name__ == "__main__":
591
-     inicializar_sistema()
592
-     
593
-     # Exemplo de uso para testar com sessões diferentes:
594
-     print("\n--- Testando sessões independentes ---")
595
-     
596
-     # Simulando um usuário A
597
-     session_id_a = "user_A_session_123"
598
-     print(f"\nUsuário A ({session_id_a}):")
599
-     print(responder_como_aldo(session_id_a, "Olá, o que é Java?"))
600
-     print(responder_como_aldo(session_id_a, "Pode me dar um exemplo de código Java para 'Hello World'?"))
601
-     print(get_memory_stats(session_id_a))
602
-
603
-     # Simulando um usuário B
604
-     session_id_b = "user_B_session_456"
605
-     print(f"\nUsuário B ({session_id_b}):")
606
-     print(responder_como_aldo(session_id_b, "Qual a diferença entre IA e Machine Learning?"))
607
-     print(get_memory_stats(session_id_b))
608
-
609
-     # Usuário A continua sua conversa
610
-     print(f"\nUsuário A ({session_id_a}) continua:")
611
-     print(responder_como_aldo(session_id_a, "E sobre polimorfismo em Java?"))
612
-     print(get_memory_stats(session_id_a))
613
-
614
-     # Limpar a memória de uma sessão específica
615
-     print(f"\nLimpando memória do Usuário B ({session_id_b}):")
616
-     print(clear_memory(session_id_b))
617
-     print(get_memory_stats(session_id_b)) # Deve mostrar memória vazia ou não encontrada para B
618
-
619
-     # Usuário A ainda tem sua memória
620
-     print(f"\nVerificando memória do Usuário A ({session_id_a}) após limpar B:")
621
-     print(get_memory_stats(session_id_a))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
2
  import re
3
  import time
4
  import pickle
5
+ import requests
6
  from typing import Dict, Any, List, Optional, Tuple
7
  from bs4 import BeautifulSoup
8
  from urllib.parse import urljoin, urlparse
 
10
  from langchain.vectorstores import FAISS
11
  from langchain.embeddings import HuggingFaceEmbeddings
12
 
13
+ # --- Configurações ---
14
+ # Chave da API da Hugging Face (essencial para o funcionamento)
15
+ HF_TOKEN = os.getenv("HF_TOKEN")
16
+ if not HF_TOKEN:
17
+ raise ValueError("A variável de ambiente HF_TOKEN não foi definida. Defina-a com seu token da Hugging Face.")
18
+
19
+ # URL do blog para a base de conhecimento (RAG)
20
  BLOG_URL = "https://aldohenrique.com.br/"
21
+
22
+ # Caminhos para os arquivos do RAG
23
  VECTOR_STORE_PATH = "faiss_index_store.pkl"
24
  PROCESSED_URLS_PATH = "processed_urls.pkl"
 
 
 
 
 
 
25
 
26
+ # Modelos disponíveis na Hugging Face
27
  MODELS = {
28
      "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
29
      "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
 
31
      "Gemma 7B (Google)":"google/gemma-7b-it",
32
      "Zephyr 7B": "HuggingFaceH4/zephyr-7b-beta"
33
  }
 
34
  DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
35
 
36
+ # --- Variáveis Globais ---
37
+ # Armazena o índice vetorial para busca de contexto (RAG)
38
  vector_store: Optional[FAISS] = None
39
+
40
+ # Dicionário para gerenciar todas as sessões de usuário em memória
41
+ # Estrutura: {session_id: {"history": [...], "profile": {...}}}
42
  user_sessions: Dict[str, Dict[str, Any]] = {}
43
+ MAX_MEMORY_TURNS = 5 # Manter as últimas 5 trocas (usuário + assistente)
44
 
45
  # ==============================================================================
46
+ # SEÇÃO DE GERENCIAMENTO DA SESSÃO (MEMÓRIA E PERFIL)
47
  # ==============================================================================
48
 
49
+ def get_or_create_session(session_id: str) -> Dict[str, Any]:
50
+ """
51
+ Obtém uma sessão de usuário existente ou cria uma nova.
52
+ A sessão é mantida apenas em memória.
53
+ """
54
+ if session_id not in user_sessions:
55
+ print(f"Nova sessão criada para o ID: {session_id}")
56
+ user_sessions[session_id] = {
57
+ "history": [],
58
+ "profile": {"nivel": "indefinido", "interesses": {}, "total_perguntas": 0}
59
+ }
60
+ return user_sessions[session_id]
61
+
62
+ def update_memory(session_id: str, user_message: str, assistant_response: str):
63
+ """Adiciona a troca de mensagens ao histórico da sessão."""
64
+ session = get_or_create_session(session_id)
65
+
66
+ # Adiciona as mensagens mais recentes
67
+ session["history"].append({"role": "user", "content": user_message})
68
+ session["history"].append({"role": "assistant", "content": assistant_response})
69
+
70
+ # Garante que o histórico não exceda o tamanho máximo
71
+ if len(session["history"]) > MAX_MEMORY_TURNS * 2:
72
+ session["history"] = session["history"][-(MAX_MEMORY_TURNS * 2):]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  def update_user_profile(session_id: str, user_message: str):
75
+ """
76
+ Analisa a mensagem do usuário para inferir e atualizar seu perfil de interesses e nível.
77
+ """
78
+ session = get_or_create_session(session_id)
79
+ profile = session["profile"]
80
+ msg_lower = user_message.lower()
81
+
82
+ # Atualiza contador de perguntas
83
+ profile["total_perguntas"] += 1
84
+
85
+ # Inferência de nível
86
+ if any(word in msg_lower for word in ['básico', 'iniciante', 'começar', 'o que é']):
87
+ profile['nivel'] = 'iniciante'
88
+ elif any(word in msg_lower for word in ['avançado', 'complexo', 'otimização', 'performance', 'arquitetura']):
89
+ profile['nivel'] = 'avançado'
90
+ elif profile['nivel'] == 'indefinido': # Define como intermediário se ainda não tiver um nível
91
+ profile['nivel'] = 'intermediário'
92
+
93
+ # Inferência de interesses
94
+ topics = {
95
+ 'java': ['java', 'spring', 'jpa', 'jvm'],
96
+ 'python': ['python', 'django', 'flask', 'pandas'],
97
+ 'web': ['html', 'css', 'javascript', 'react', 'node'],
98
+ 'ia': ['inteligência artificial', 'machine learning', 'llm', 'rag'],
99
+ 'banco de dados': ['sql', 'nosql', 'mongodb', 'postgresql']
100
+ }
101
+ for topic, keywords in topics.items():
102
+ if any(keyword in msg_lower for keyword in keywords):
103
+ profile['interesses'][topic] = profile['interesses'].get(topic, 0) + 1
104
+
105
+ def clear_session_memory(session_id: str) -> str:
106
+ """Limpa a memória de uma sessão específica."""
107
+ if session_id in user_sessions:
108
+ del user_sessions[session_id]
109
+ return f" Memória da sessão '{session_id}' foi limpa."
110
+ return f"⚠️ Sessão '{session_id}' não encontrada."
111
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  # ==============================================================================
114
+ # SEÇÃO RAG: BUSCA E PROCESSAMENTO DE CONTEÚDO (SEM ALTERAÇÕES SIGNIFICATIVAS)
115
  # ==============================================================================
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  def scrape_text_from_url(url: str) -> str:
118
+ """Extrai texto de uma URL, focando no conteúdo principal."""
119
+ try:
120
+ response = requests.get(url, timeout=10)
121
+ response.raise_for_status()
122
+ soup = BeautifulSoup(response.content, 'html.parser')
123
+ main_content = soup.find('article') or soup.find('main')
124
+ return main_content.get_text(separator='\n', strip=True) if main_content else ""
125
+ except requests.RequestException as e:
126
+ print(f"Erro ao acessar {url}: {e}")
127
+ return ""
128
+
129
+ def build_and_save_vector_store():
130
+ """Coleta dados do blog, processa e cria um índice vetorial com FAISS."""
131
+ global vector_store
132
+ print("Iniciando construção do RAG...")
133
+
134
+ # Lógica simplificada de coleta de links (pode ser expandida se necessário)
135
+ # Para este exemplo, vamos focar em uma URL principal
136
+ all_texts = [scrape_text_from_url(BLOG_URL)]
137
+
138
+ # Adicione mais URLs manualmente se desejar
139
+ # additional_urls = [f"{BLOG_URL}/sobre", f"{BLOG_URL}/contato"]
140
+ # all_texts.extend([scrape_text_from_url(url) for url in additional_urls])
141
+
142
+ valid_texts = [text for text in all_texts if text and len(text) > 100]
143
+ if not valid_texts:
144
+ print("Nenhum texto válido encontrado para criar o RAG.")
145
+ return
146
+
147
+ print(f"Processando {len(valid_texts)} página(s).")
148
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
149
+ chunks = text_splitter.create_documents(valid_texts)
150
+
151
+ print(f"Criando {len(chunks)} chunks de texto.")
152
+ embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
153
+ vector_store = FAISS.from_documents(chunks, embeddings_model)
154
+
155
+ with open(VECTOR_STORE_PATH, "wb") as f:
156
+ pickle.dump(vector_store, f)
157
+
158
+ print("✅ RAG construído e salvo com sucesso!")
 
 
 
 
 
 
 
 
 
 
159
 
160
  def load_vector_store():
161
+ """Carrega o índice vetorial do disco."""
162
+ global vector_store
163
+ if os.path.exists(VECTOR_STORE_PATH):
164
+ print(f"Carregando RAG de '{VECTOR_STORE_PATH}'...")
165
+ with open(VECTOR_STORE_PATH, "rb") as f:
166
+ vector_store = pickle.load(f)
167
+ print(" RAG carregado.")
168
+ else:
169
+ print("Índice RAG não encontrado. Construindo um novo...")
170
+ build_and_save_vector_store()
171
+
172
+ def retrieve_rag_context(query: str, k: int = 3) -> str:
173
+ """Busca no RAG por contexto relevante para a pergunta."""
174
+ if vector_store:
175
+ try:
176
+ results = vector_store.similarity_search(query, k=k)
177
+ return "\n\n---\n\n".join([doc.page_content for doc in results])
178
+ except Exception as e:
179
+ print(f"Erro ao buscar contexto no RAG: {e}")
180
+ return ""
 
 
181
 
182
  # ==============================================================================
183
+ # SEÇÃO DA API E CONSTRUÇÃO DO PROMPT
184
  # ==============================================================================
185
 
186
  class HuggingFaceAPIClient:
187
+ """Cliente para interagir com a API de Inferência da Hugging Face."""
188
+ def __init__(self, token: str):
189
+ self.headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
190
+
191
+ def query(self, model_id: str, messages: List[Dict[str, str]], max_tokens: int = 2048) -> str:
192
+ api_url = f"https://api-inference.huggingface.co/models/{model_id}"
193
+ payload = {
194
+ "inputs": self._format_prompt_for_model(messages),
195
+ "parameters": {
196
+ "max_new_tokens": max_tokens,
197
+ "temperature": 0.7,
198
+ "top_p": 0.95,
199
+ "return_full_text": False,
200
+ },
201
+ "options": {"wait_for_model": True}
202
+ }
203
+ try:
204
+ response = requests.post(api_url, headers=self.headers, json=payload, timeout=60)
205
+ response.raise_for_status()
206
+ result = response.json()
207
+ # A resposta da API de inferência pode vir em uma lista
208
+ if isinstance(result, list) and result:
209
+ return result[0].get("generated_text", "").strip()
210
+ # Ou em um dicionário
211
+ elif isinstance(result, dict):
212
+ return result.get("generated_text", f"Erro: Resposta inesperada do modelo: {result.get('error', '')}").strip()
213
+ return "Erro: Resposta vazia ou em formato inesperado."
214
+ except requests.Timeout:
215
+ return "Erro: A requisição à API demorou muito para responder (timeout)."
216
+ except requests.HTTPError as http_err:
217
+ return f"Erro HTTP: {http_err}. Detalhes: {response.text}"
218
+ except Exception as e:
219
+ return f"Ocorreu um erro inesperado na chamada da API: {e}"
220
+
221
+ def _format_prompt_for_model(self, messages: List[Dict[str, str]]) -> str:
222
+ """Formata a lista de mensagens em uma string única para a API de inferência."""
223
+ prompt_str = ""
224
+ for msg in messages:
225
+ if msg['role'] == 'system':
226
+ prompt_str += f"<|system|>\n{msg['content']}</s>\n"
227
+ elif msg['role'] == 'user':
228
+ prompt_str += f"<|user|>\n{msg['content']}</s>\n"
229
+ elif msg['role'] == 'assistant':
230
+ prompt_str += f"<|assistant|>\n{msg['content']}</s>\n"
231
+ prompt_str += "<|assistant|>\n" # Solicita a continuação do assistente
232
+ return prompt_str
233
+
234
+
235
+ class PromptBuilder:
236
+ """Constrói o prompt final a ser enviado para o modelo."""
237
+
238
+ SYS_PROMPT_TEMPLATE = """Você é o Professor Aldo, um especialista em programação (Java, C, Web) e IA.
239
+
240
+ **Sua Personalidade:**
241
+ - **Didático e Paciente:** Aja como um professor experiente. Explique o "porquê" das coisas, não apenas o "como".
242
+ - **Acolhedor e Amigável:** Use uma linguagem calorosa e acessível.
243
+ - **Adaptável:** Ajuste a complexidade da sua resposta ao nível de conhecimento do aluno.
244
+ - **Contextual:** Se a pergunta atual se conectar a algo que já discutimos, mencione essa conexão.
245
+
246
+ **Suas Regras:**
247
+ 1. Responda sempre em português do Brasil.
248
+ 2. Use blocos de código (```java, ```python, etc.) para exemplos. Comente o código para explicar cada parte.
249
+ 3. Se a pergunta não for sobre tecnologia ou programação, educadamente informe que sua especialidade é outra.
250
+ 4. Baseie sua resposta primariamente nas informações do seu blog (se houver contexto) e no nosso histórico de conversa.
251
+
252
+ A seguir, informações para te ajudar a contextualizar sua resposta:"""
253
+
254
+ def __init__(self, session_id: str, rag_context: str):
255
+ self.session = get_or_create_session(session_id)
256
+ self.rag_context = rag_context
257
+ self.parts = []
258
+
259
+ def _add_profile_context(self):
260
+ profile = self.session["profile"]
261
+ if profile["total_perguntas"] > 0:
262
+ profile_summary = [f"**Perfil do Aluno (Inferido):**"]
263
+ profile_summary.append(f"- Nível de conhecimento: {profile['nivel'].capitalize()}")
264
+ interesses = sorted(profile['interesses'].items(), key=lambda item: item[1], reverse=True)
265
+ if interesses:
266
+ formatted_interesses = [f"{topic.capitalize()} ({count}x)" for topic, count in interesses]
267
+ profile_summary.append(f"- Principais interesses: {', '.join(formatted_interesses)}")
268
+ self.parts.append("\n".join(profile_summary))
269
+
270
+ def _add_rag_context(self):
271
+ if self.rag_context:
272
+ self.parts.append(f"**Contexto Relevante do seu Blog (RAG):**\n{self.rag_context}")
273
+
274
+ def _add_history_context(self, current_question: str) -> List[Dict[str, str]]:
275
+ """Prepara o histórico de mensagens para o modelo."""
276
+ history = self.session.get("history", [])
277
+ # Pega as mensagens do histórico e adiciona a pergunta atual
278
+ messages = history + [{"role": "user", "content": current_question}]
279
+ return messages
280
+
281
+ def build(self, user_question: str) -> List[Dict[str, str]]:
282
+ # Adiciona os contextos ao prompt do sistema
283
+ self._add_profile_context()
284
+ self._add_rag_context()
285
+
286
+ system_content = self.SYS_PROMPT_TEMPLATE
287
+ if self.parts:
288
+ system_content += "\n\n" + "\n\n".join(self.parts)
289
+
290
+ # Monta a lista final de mensagens
291
+ messages = [{"role": "system", "content": system_content}]
292
+ messages.extend(self._add_history_context(user_question))
293
+
294
+ return messages
295
 
296
  # ==============================================================================
297
+ # FUNÇÃO PRINCIPAL E INICIALIZAÇÃO
298
  # ==============================================================================
299
 
300
+ # Inicializa o cliente da API
301
+ api_client = HuggingFaceAPIClient(token=HF_TOKEN)
302
+
303
+ def formatar_resposta(resposta: str) -> str:
304
+ """Formata a resposta com HTML para melhor visualização de código e texto."""
305
+ resposta_html = resposta.replace('<', '&lt;').replace('>', '&gt;')
306
+
307
+ # Formata blocos de código
308
+ resposta_html = re.sub(
309
+ r'```(\w+)?\n(.*?)\n```',
310
+ r'<div style="background-color:#f0f0f0; border:1px solid #ddd; border-radius:8px; padding:15px; margin:1em 0; font-family:monospace; color:black;"><pre><code>\2</code></pre></div>',
311
+ resposta_html,
312
+ flags=re.DOTALL
313
+ )
314
+ # Formata negrito
315
+ resposta_html = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', resposta_html)
316
+ # Formata nova linha
317
+ return resposta_html.replace('\n', '<br>')
318
+
319
+ def responder_pergunta(session_id: str, pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
320
+ """
321
+ Função principal que orquestra todo o processo de resposta.
322
+ """
323
+ if not pergunta.strip():
324
+ return "Por favor, faça uma pergunta."
325
+
326
+ print(f"\n--- Processando pergunta para a sessão: {session_id} ---")
327
+
328
+ # 1. Atualizar perfil do usuário com base na pergunta atual
329
+ update_user_profile(session_id, pergunta)
330
+
331
+ # 2. Buscar contexto relevante no RAG
332
+ print("Buscando no RAG...")
333
+ rag_context = retrieve_rag_context(pergunta)
334
+ if rag_context:
335
+ print("Contexto encontrado no RAG.")
336
+
337
+ # 3. Construir o prompt completo usando o PromptBuilder
338
+ print("Construindo prompt...")
339
+ builder = PromptBuilder(session_id, rag_context)
340
+ messages = builder.build(pergunta)
341
+
342
+ # DEBUG: Descomente a linha abaixo para ver o prompt exato enviado ao modelo
343
+ # print("MENSAGENS ENVIADAS AO MODELO:", json.dumps(messages, indent=2, ensure_ascii=False))
344
+
345
+ # 4. Chamar a API da Hugging Face
346
+ print(f"Enviando para o modelo '{modelo_escolhido}'...")
347
+ model_id = MODELS.get(modelo_escolhido, MODELS[DEFAULT_MODEL])
348
+ resposta_bruta = api_client.query(model_id, messages)
349
+
350
+ # 5. Adicionar a interação à memória da sessão
351
+ update_memory(session_id, pergunta, resposta_bruta)
352
+
353
+ # 6. Formatar e retornar a resposta
354
+ return formatar_resposta(resposta_bruta)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
 
356
+ def inicializar_sistema():
357
+ """Carrega o RAG ao iniciar."""
358
+ print("🚀 Inicializando o sistema...")
359
+ load_vector_store()
360
+ print("✅ Sistema pronto para uso.")
361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
  # ==============================================================================
364
+ # BLOCO DE TESTE
365
  # ==============================================================================
366
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  if __name__ == "__main__":
368
+ inicializar_sistema()
369
+
370
+ # --- Simulação de Conversas ---
371
+
372
+ # Sessão do Usuário A (interessado em Java)
373
+ session_a = "aluno_java_123"
374
+ print("\n--- INÍCIO DA CONVERSA COM ALUNO A (Java) ---")
375
+
376
+ pergunta1_a = "Olá! Pode me explicar o que é Polimorfismo em Java de uma forma simples?"
377
+ resposta1_a = responder_pergunta(session_a, pergunta1_a)
378
+ print(f"ALUNO A: {pergunta1_a}\nPROFESSOR ALDO:\n{resposta1_a}\n")
379
+
380
+ pergunta2_a = "Entendi! Pode me dar um exemplo de código com classes e herança para ilustrar?"
381
+ resposta2_a = responder_pergunta(session_a, pergunta2_a)
382
+ print(f"ALUNO A: {pergunta2_a}\nPROFESSOR ALDO:\n{resposta2_a}\n")
383
+
384
+ # Sessão do Usuário B (interessado em IA)
385
+ session_b = "aluna_ia_456"
386
+ print("\n--- INÍCIO DA CONVERSA COM ALUNA B (IA) ---")
387
+
388
+ pergunta1_b = "Oi, professor! Eu sou nova na área. Qual a diferença entre Inteligência Artificial e Machine Learning?"
389
+ resposta1_b = responder_pergunta(session_b, pergunta1_b)
390
+ print(f"ALUNA B: {pergunta1_b}\nPROFESSOR ALDO:\n{resposta1_b}\n")
391
+
392
+ # Usuário A continua sua conversa, a memória do usuário B não deve interferir
393
+ print("\n--- ALUNO A CONTINUA SUA CONVERSA ---")
394
+ pergunta3_a = "Faz sentido. E como o conceito de 'override' se encaixa nisso que acabamos de ver?"
395
+ resposta3_a = responder_pergunta(session_a, pergunta3_a)
396
+ print(f"ALUNO A: {pergunta3_a}\nPROFESSOR ALDO:\n{resposta3_a}\n")
397
+
398
+ # Exibe o estado final das sessões
399
+ print("\n--- ESTADO FINAL DAS SESSÕES EM MEMÓRIA ---")
400
+ print(f"\nSessão A ({session_a}):")
401
+ # print(json.dumps(user_sessions.get(session_a), indent=2, ensure_ascii=False))
402
+ print(f" Nível: {user_sessions[session_a]['profile']['nivel']}")
403
+ print(f" Interesses: {user_sessions[session_a]['profile']['interesses']}")
404
+ print(f" Tamanho do Histórico: {len(user_sessions[session_a]['history'])} mensagens")
405
+
406
+
407
+ print(f"\nSessão B ({session_b}):")
408
+ # print(json.dumps(user_sessions.get(session_b), indent=2, ensure_ascii=False))
409
+ print(f" Nível: {user_sessions[session_b]['profile']['nivel']}")
410
+ print(f" Interesses: {user_sessions[session_b]['profile']['interesses']}")
411
+ print(f" Tamanho do Histórico: {len(user_sessions[session_b]['history'])} mensagens")
412
+
413
+ # Limpando a memória de uma sessão
414
+ print("\n--- LIMPANDO MEMÓRIA ---")
415
+ print(clear_session_memory(session_a))
416
+ print(f"Sessões ativas agora: {list(user_sessions.keys())}")