aldohenrique commited on
Commit
73eb66f
·
verified ·
1 Parent(s): 852e294

Update ai_logic.py

Browse files
Files changed (1) hide show
  1. ai_logic.py +309 -561
ai_logic.py CHANGED
@@ -11,611 +11,359 @@ 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",
28
- "Deepseek (chat) 7B": "deepseek-ai/deepseek-vl-7b-chat",
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))
 
11
  from langchain.vectorstores import FAISS
12
  from langchain.embeddings import HuggingFaceEmbeddings
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # ==============================================================================
15
+ # SEÇÃO 1: CONFIGURAÇÃO CENTRALIZADA
16
  # ==============================================================================
17
 
18
+ class Config:
19
+ """Classe para centralizar todas as configurações do sistema."""
20
+ # Configuração do RAG
21
+ BLOG_URL = "https://aldohenrique.com.br/"
22
+ VECTOR_STORE_PATH = "faiss_index_store.pkl"
23
+ PROCESSED_URLS_PATH = "processed_urls.pkl"
24
+
25
+ # Configuração da Memória de Sessão
26
+ SESSION_MEMORY_DIR = "session_memories"
27
+ MAX_MEMORY_LENGTH = 10 # Máximo de trocas de mensagens (user + assistant)
28
+
29
+ # Configuração da API Hugging Face
30
+ HF_TOKEN = os.getenv("HF_TOKEN")
31
+ MODELS = {
32
+ "Phi-3 Mini (Microsoft)": "microsoft/Phi-3-mini-4k-instruct",
33
+ "Mistral 7B": "mistralai/Mistral-7B-Instruct-v0.3",
34
+ "Gemma 7B (Google)": "google/gemma-7b-it",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
+ DEFAULT_MODEL = "Phi-3 Mini (Microsoft)"
37
+ API_TIMEOUT = 45 # Timeout de 45 segundos para a API
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
 
39
+ def __init__(self):
40
+ if not self.HF_TOKEN:
41
+ raise ValueError("Token HF_TOKEN não encontrado nas variáveis de ambiente.")
42
+ os.makedirs(self.SESSION_MEMORY_DIR, exist_ok=True)
43
 
44
+ # ==============================================================================
45
+ # SEÇÃO 2: GERENCIAMENTO DE MEMÓRIA POR SESSÃO
46
+ # ==============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ class SessionManager:
49
+ """Gerencia a memória da conversa e o perfil de cada usuário/sessão."""
50
+ def __init__(self, config: Config):
51
+ self.config = config
52
+ self._sessions: Dict[str, Dict[str, Any]] = {}
53
+
54
+ def _get_memory_path(self, session_id: str) -> str:
55
+ """Retorna o caminho do arquivo de memória para uma sessão."""
56
+ return os.path.join(self.config.SESSION_MEMORY_DIR, f"memory_{session_id}.json")
57
+
58
+ def get_session(self, session_id: str) -> Dict[str, Any]:
59
+ """Carrega uma sessão da memória ou do disco, se não estiver carregada."""
60
+ if session_id not in self._sessions:
61
+ try:
62
+ with open(self._get_memory_path(session_id), 'r', encoding='utf-8') as f:
63
+ self._sessions[session_id] = json.load(f)
64
+ print(f"Memória para sessão '{session_id}' carregada do disco.")
65
+ except (FileNotFoundError, json.JSONDecodeError):
66
+ print(f"Nova conversa iniciada para sessão '{session_id}'.")
67
+ self._sessions[session_id] = {'conversation': [], 'user_profile': {'nivel': 'intermediario'}}
68
+ return self._sessions[session_id]
69
+
70
+ def save_session(self, session_id: str):
71
+ """Salva o estado atual de uma sessão em um arquivo JSON."""
72
+ if session_id in self._sessions:
73
+ try:
74
+ with open(self._get_memory_path(session_id), 'w', encoding='utf-8') as f:
75
+ json.dump(self._sessions[session_id], f, ensure_ascii=False, indent=2)
76
+ except IOError as e:
77
+ print(f"Erro ao salvar memória para sessão '{session_id}': {e}")
78
+
79
+ def add_to_memory(self, session_id: str, user_message: str, assistant_response: str):
80
+ """Adiciona uma nova troca de mensagens à memória da sessão."""
81
+ session = self.get_session(session_id)
82
+ conversation = session.get('conversation', [])
83
+
84
+ conversation.append({"role": "user", "content": user_message})
85
+ conversation.append({"role": "assistant", "content": assistant_response})
86
+
87
+ # Limita o tamanho da memória
88
+ if len(conversation) > self.config.MAX_MEMORY_LENGTH * 2:
89
+ session['conversation'] = conversation[-self.config.MAX_MEMORY_LENGTH * 2:]
90
+
91
+ self.update_user_profile(session_id, user_message)
92
+ self.save_session(session_id) # Salva após a atualização
93
+
94
+ def update_user_profile(self, session_id: str, user_message: str):
95
+ """Atualiza o perfil do usuário com base em heurísticas simples."""
96
+ session = self.get_session(session_id)
97
+ profile = session.get('user_profile', {})
98
+ msg_lower = user_message.lower()
99
+
100
+ # Atualiza nível de conhecimento
101
+ if any(word in msg_lower for word in ['básico', 'iniciante', 'começar', 'o que é']):
102
+ profile['nivel'] = 'iniciante'
103
+ elif any(word in msg_lower for word in ['avançado', 'complexo', 'otimização', 'arquitetura']):
104
+ profile['nivel'] = 'avançado'
105
+
106
+ profile['total_perguntas'] = profile.get('total_perguntas', 0) + 1
107
+ session['user_profile'] = profile
108
 
109
+ def clear_memory(self, session_id: str):
110
+ """Limpa a memória de uma sessão específica (em RAM e em disco)."""
111
+ if session_id in self._sessions:
112
+ del self._sessions[session_id]
113
+
114
+ try:
115
+ os.remove(self._get_memory_path(session_id))
116
+ return f"✅ Memória da sessão '{session_id}' limpa com sucesso!"
117
+ except FileNotFoundError:
118
+ return f"⚠️ Nenhuma memória em disco encontrada para a sessão '{session_id}'."
119
+ except Exception as e:
120
+ return f"❌ Erro ao limpar memória da sessão '{session_id}': {e}"
121
 
122
  # ==============================================================================
123
+ # SEÇÃO 3: SISTEMA DE RAG (Retrieval-Augmented Generation)
124
  # ==============================================================================
125
 
126
+ class RAGSystem:
127
+ """Gerencia a criação, carregamento e busca no Vector Store do blog."""
128
+ def __init__(self, config: Config):
129
+ self.config = config
130
+ self.vector_store: Optional[FAISS] = None
131
+ self.embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
 
 
 
132
 
133
+ def load(self):
134
+ """Carrega o vector store do disco ou o constrói se não existir."""
135
  try:
136
+ with open(self.config.VECTOR_STORE_PATH, "rb") as f:
137
+ self.vector_store = pickle.load(f)
138
+ print("✅ Vector Store carregado com sucesso do disco.")
139
+ except FileNotFoundError:
140
+ print("⚠️ Vector Store não encontrado. Iniciando processo de construção.")
141
+ self.build()
142
+
143
+ def build(self):
144
+ """Coleta dados do blog, cria embeddings e salva o vector store."""
145
+ print("Iniciando construção do RAG...")
146
+ # 1. Coletar links
147
+ response = requests.get(self.config.BLOG_URL, timeout=10)
 
 
 
 
 
 
 
 
 
148
  soup = BeautifulSoup(response.content, 'html.parser')
149
+ links = {urljoin(self.config.BLOG_URL, a['href']) for a in soup.find_all('a', href=True)}
150
+ # Filtro simples para pegar apenas posts
151
+ post_links = {link for link in links if urlparse(link).path.count('/') > 2 and '?' not in link}
152
+ print(f"Encontrados {len(post_links)} links de posts para processar.")
153
+
154
+ # 2. Extrair texto
155
+ texts = []
156
+ for link in post_links:
157
+ try:
158
+ page_response = requests.get(link, timeout=10)
159
+ page_soup = BeautifulSoup(page_response.content, 'html.parser')
160
+ article = page_soup.find('article') or page_soup.find('main')
161
+ if article:
162
+ texts.append(article.get_text(separator='\n', strip=True))
163
+ except requests.RequestException:
164
+ continue # Ignora links que falham
165
+
166
+ if not texts:
167
+ print("❌ Nenhum texto pôde ser extraído. A construção do RAG falhou.")
168
+ return
169
+
170
+ # 3. Criar chunks
171
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
172
+ chunks = text_splitter.create_documents(texts)
173
+ print(f"Textos divididos em {len(chunks)} chunks.")
174
+
175
+ # 4. Criar e salvar Vector Store
176
+ self.vector_store = FAISS.from_documents(chunks, self.embeddings_model)
177
+ with open(self.config.VECTOR_STORE_PATH, "wb") as f:
178
+ pickle.dump(self.vector_store, f)
179
+ print("✅ Novo Vector Store construído e salvo com sucesso!")
180
+
181
+ def retrieve(self, query: str, k: int = 3) -> str:
182
+ """Busca no vector store por chunks de texto relevantes para a query."""
183
+ if self.vector_store:
184
+ results = self.vector_store.similarity_search(query, k=k)
185
+ return "\n\n---\n\n".join([doc.page_content for doc in results])
186
  return ""
187
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  # ==============================================================================
189
+ # SEÇÃO 4: CLIENTE DA API E LÓGICA PRINCIPAL DO CHATBOT
190
  # ==============================================================================
191
 
192
  class HuggingFaceAPIClient:
193
+ """Cliente simplificado para interagir com a API de chat do Hugging Face."""
194
+ def __init__(self, config: Config):
195
+ self.config = config
196
+ self.headers = {"Authorization": f"Bearer {config.HF_TOKEN}"}
197
+
198
+ def query(self, model_name: str, messages: list) -> str:
199
+ """Envia a requisição para o endpoint de chat/completions."""
200
+ api_url = f"https://api-inference.huggingface.co/models/{model_name}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  payload = {
202
+ "inputs": messages,
203
+ "parameters": {"max_new_tokens": 1500, "temperature": 0.7, "return_full_text": False},
204
+ "options": {"wait_for_model": True}
 
 
 
205
  }
206
  try:
207
+ response = requests.post(api_url, headers=self.headers, json=payload, timeout=self.config.API_TIMEOUT)
208
+ response.raise_for_status() # Lança exceção para códigos de erro HTTP
209
+ result = response.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
+ # A resposta pode vir em formatos diferentes dependendo do modelo
212
+ if isinstance(result, list) and result and "generated_text" in result[0]:
213
+ return result[0]["generated_text"].strip()
214
+ # Tratamento para alguns modelos que podem encapsular a resposta de outra forma
215
+ if isinstance(result, dict) and "generated_text" in result:
216
+ return result["generated_text"].strip()
217
 
218
+ return f"Resposta inesperada da API: {str(result)[:200]}"
 
219
 
220
+ except requests.Timeout:
221
+ return "⏰ Erro: A API demorou muito para responder (timeout)."
222
+ except requests.HTTPError as e:
223
+ return f"❌ Erro HTTP {e.response.status_code}: {e.response.text[:200]}"
224
+ except Exception as e:
225
+ return f"💥 Erro inesperado ao contatar a API: {str(e)}"
226
+
227
+ class AldoChatbot:
228
+ """Orquestra o RAG, a Memória e a chamada à API para gerar respostas."""
229
+ def __init__(self, config: Config, session_manager: SessionManager, rag_system: RAGSystem, api_client: HuggingFaceAPIClient):
230
+ self.config = config
231
+ self.session_manager = session_manager
232
+ self.rag_system = rag_system
233
+ self.api_client = api_client
234
+
235
+ def _format_response(self, text: str) -> str:
236
+ """Formata a resposta com HTML simples para código e negrito."""
237
+ # Escapa caracteres HTML básicos para segurança
238
+ text = text.replace('<', '&lt;').replace('>', '&gt;')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
+ # Formata blocos de código ```...```
241
+ text = re.sub(
242
+ r'```(\w*?)\n(.*?)```',
243
+ r'<div style="background-color:#f0f0f0; border-radius:5px; padding:10px; margin:10px 0; font-family:monospace; color:black;"><pre><code>\2</code></pre></div>',
244
+ text,
245
+ flags=re.DOTALL
246
+ )
247
+ # Formata títulos **...**
248
+ text = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', text)
249
+ return text.replace('\n', '<br>')
250
+
251
+ def get_response(self, session_id: str, user_question: str, model_key: str) -> str:
252
+ """Processo completo para gerar uma resposta para o usuário."""
253
+ if not user_question.strip():
254
+ return "Por favor, faça uma pergunta."
255
+
256
+ session = self.session_manager.get_session(session_id)
257
+ user_profile = session.get('user_profile', {})
258
+ conversation_history = session.get('conversation', [])
259
 
260
+ # 1. RAG: Buscar contexto no blog
261
+ rag_context = self.rag_system.retrieve(user_question)
262
 
263
+ # 2. PROMPT ENGINEERING: Montar a estrutura de mensagens
264
+ system_prompt = f"""Você é o professor Dr. Aldo Henrique, um especialista em programação (C, Java, Web) e IA.
265
+ - Personalidade: Paciente, amigável e didático.
266
+ - Nível do Aluno: Adapte sua linguagem para o nível '{user_profile.get('nivel', 'intermediario')}'.
267
+ - Regras: Responda em português do Brasil. Use blocos de código (```) para exemplos. Explique conceitos de forma clara com analogias.
268
+ - Fonte de Conhecimento: Baseie-se PRINCIPALMENTE no contexto fornecido do seu blog. Se o contexto não for suficiente, use seu conhecimento geral, mas informe que a informação não veio do blog."""
269
+
270
+ # Mensagens para o modelo
271
+ messages = [{"role": "system", "content": system_prompt}]
272
 
273
+ # Adiciona histórico da conversa (se houver)
274
+ messages.extend(conversation_history)
275
+
276
+ # Constrói a pergunta final do usuário com o contexto do RAG
277
+ final_user_content = f"""
278
+ **Contexto do meu blog para consulta:**
279
+ ---
280
+ {rag_context if rag_context else "Nenhum contexto relevante encontrado no blog."}
281
+ ---
282
+
283
+ **Minha pergunta é:**
284
+ {user_question}
285
+ """
286
+ messages.append({"role": "user", "content": final_user_content})
287
 
288
+ # 3. API: Chamar o modelo de linguagem
289
+ model_id = self.config.MODELS.get(model_key, self.config.MODELS[self.config.DEFAULT_MODEL])
290
 
291
+ print(f"Enviando para o modelo '{model_id}'...")
292
+ assistant_response = self.api_client.query(model_id, messages)
293
+
294
+ # 4. MEMÓRIA: Salvar a interação
295
+ self.session_manager.add_to_memory(session_id, user_question, assistant_response)
296
+
297
+ # 5. FORMATAÇÃO: Formatar a resposta final para exibição
298
+ return self._format_response(assistant_response)
299
 
300
  # ==============================================================================
301
+ # SEÇÃO 5: INICIALIZAÇÃO E EXECUÇÃO
302
  # ==============================================================================
303
 
304
+ if __name__ == "__main__":
305
+ print("🚀 Inicializando o Chatbot Dr. Aldo Henrique...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
+ # 1. Inicializar componentes
308
+ config = Config()
309
+ session_manager = SessionManager(config)
310
+ rag_system = RAGSystem(config)
311
+ api_client = HuggingFaceAPIClient(config)
312
 
313
+ # 2. Carregar ou construir RAG
314
+ rag_system.load()
 
 
 
 
315
 
316
+ # 3. Inicializar o chatbot principal
317
+ chatbot = AldoChatbot(config, session_manager, rag_system, api_client)
318
 
319
+ print("\n✅ Sistema pronto para uso.")
320
+ print("--- Início dos Testes de Sessão ---")
 
 
 
321
 
322
+ # --- SIMULAÇÃO DO USUÁRIO A ---
323
+ session_a = "usuario_alfa_001"
324
+ print(f"\n--- [Sessão A: {session_a}] ---")
325
 
326
+ pergunta1_a = "Olá! O que é polimorfismo em Java? Me explique como se eu fosse um iniciante."
327
+ print(f"Usuário A: {pergunta1_a}")
328
+ resposta1_a = chatbot.get_response(session_a, pergunta1_a, "Phi-3 Mini (Microsoft)")
329
+ print(f"AldoBot:\n{resposta1_a}\n")
330
+
331
+ pergunta2_a = "Legal! Pode me dar um exemplo de código sobre isso?"
332
+ print(f"Usuário A: {pergunta2_a}")
333
+ resposta2_a = chatbot.get_response(session_a, pergunta2_a, "Phi-3 Mini (Microsoft)")
334
+ print(f"AldoBot:\n{resposta2_a}\n")
335
+
336
+ # --- SIMULAÇÃO DO USUÁRIO B ---
337
+ session_b = "usuario_beta_002"
338
+ print(f"\n--- [Sessão B: {session_b}] ---")
339
 
340
+ pergunta1_b = "Qual a diferença fundamental entre Inteligência Artificial e Machine Learning?"
341
+ print(f"Usuário B: {pergunta1_b}")
342
+ resposta1_b = chatbot.get_response(session_b, pergunta1_b, "Phi-3 Mini (Microsoft)")
343
+ print(f"AldoBot:\n{resposta1_b}\n")
344
 
345
+ # --- USUÁRIO A CONTINUA ---
346
+ print(f"\n--- [Sessão A: {session_a} continua] ---")
347
+ pergunta3_a = "Entendi o exemplo. Esse conceito se aplica em outras linguagens como C++?"
348
+ print(f"Usuário A: {pergunta3_a}")
349
+ resposta3_a = chatbot.get_response(session_a, pergunta3_a, "Phi-3 Mini (Microsoft)")
350
+ print(f"AldoBot:\n{resposta3_a}\n")
351
+
352
+ # --- VERIFICANDO A MEMÓRIA ---
353
+ # A memória do Usuário B não deve ter nenhuma informação sobre Java
354
+ print("\n--- Verificando estado da memória ---")
355
+ memoria_a = session_manager.get_session(session_a)
356
+ memoria_b = session_manager.get_session(session_b)
357
 
358
+ print(f"Tamanho da conversa de A: {len(memoria_a['conversation'])} mensagens.")
359
+ print(f"Nível detectado para A: {memoria_a['user_profile']['nivel']}")
360
 
361
+ print(f"Tamanho da conversa de B: {len(memoria_b['conversation'])} mensagens.")
362
+ print(f"Nível detectado para B: {memoria_b['user_profile']['nivel']}")
363
+
364
+ # --- LIMPANDO UMA SESSÃO ---
365
+ print(session_manager.clear_memory(session_b))
366
+ try:
367
+ session_manager.get_session(session_b)
368
+ except Exception as e:
369
+ print(f"Tentativa de acessar sessão B limpa resultou em: {e}")