aldohenrique commited on
Commit
977edfa
·
verified ·
1 Parent(s): 4711ae4

Update ai_logic.py

Browse files
Files changed (1) hide show
  1. ai_logic.py +512 -252
ai_logic.py CHANGED
@@ -1,361 +1,621 @@
 
1
  import os
 
2
  import re
3
  import time
4
  import pickle
5
- import json
6
- import requests
7
  from typing import Dict, Any, List, Optional, Tuple
8
  from bs4 import BeautifulSoup
9
  from urllib.parse import urljoin, urlparse
 
 
 
10
 
11
- # CORREÇÃO: Imports atualizados para as novas versões do LangChain
12
- from langchain_community.vectorstores import FAISS
13
- from langchain_community.embeddings import HuggingFaceEmbeddings
14
- from langchain_text_splitters import RecursiveCharacterTextSplitter
15
-
16
-
17
- # --- Configurações ---
18
- # Chave da API da Hugging Face (essencial para o funcionamento)
19
- HF_TOKEN = os.getenv("HF_TOKEN")
20
- if not HF_TOKEN:
21
- raise ValueError("A variável de ambiente HF_TOKEN não foi definida. Defina-a com seu token da Hugging Face.")
22
-
23
- # URL do blog para a base de conhecimento (RAG)
24
  BLOG_URL = "https://aldohenrique.com.br/"
25
-
26
- # Caminhos para os arquivos do RAG
27
  VECTOR_STORE_PATH = "faiss_index_store.pkl"
28
  PROCESSED_URLS_PATH = "processed_urls.pkl"
 
 
 
 
 
 
29
 
30
- # Modelos disponíveis na Hugging Face
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
 
38
- # --- Variáveis Globais ---
39
- # Armazena o índice vetorial para busca de contexto (RAG)
40
  vector_store: Optional[FAISS] = None
41
-
42
- # Dicionário para gerenciar todas as sessões de usuário em memória
43
- # Estrutura: {session_id: {"history": [...], "profile": {...}}}
44
  user_sessions: Dict[str, Dict[str, Any]] = {}
45
- MAX_MEMORY_TURNS = 5 # Manter as últimas 5 trocas (usuário + assistente)
46
 
47
  # ==============================================================================
48
- # SEÇÃO DE GERENCIAMENTO DA SESSÃO (MEMÓRIA E PERFIL)
49
  # ==============================================================================
50
 
51
- def get_or_create_session(session_id: str) -> Dict[str, Any]:
52
- """
53
- Obtém uma sessão de usuário existente ou cria uma nova.
54
- A sessão é mantida apenas em memória.
55
- """
56
- if session_id not in user_sessions:
57
- print(f"Nova sessão criada para o ID: {session_id}")
58
- user_sessions[session_id] = {
59
- "history": [],
60
- "profile": {"nivel": "indefinido", "interesses": {}, "total_perguntas": 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  }
62
- return user_sessions[session_id]
 
 
 
 
 
 
 
 
 
63
 
64
- def update_memory(session_id: str, user_message: str, assistant_response: str):
65
- """Adiciona a troca de mensagens ao histórico da sessão."""
66
- session = get_or_create_session(session_id)
 
 
 
 
 
67
 
68
- # Adiciona as mensagens mais recentes
69
- session["history"].append({"role": "user", "content": user_message})
70
- session["history"].append({"role": "assistant", "content": assistant_response})
 
 
71
 
72
- # Garante que o histórico não exceda o tamanho máximo
73
- if len(session["history"]) > MAX_MEMORY_TURNS * 2:
74
- session["history"] = session["history"][-(MAX_MEMORY_TURNS * 2):]
 
 
75
 
76
  def update_user_profile(session_id: str, user_message: str):
77
- """
78
- Analisa a mensagem do usuário para inferir e atualizar seu perfil de interesses e nível.
79
- """
80
- session = get_or_create_session(session_id)
81
- profile = session["profile"]
82
- msg_lower = user_message.lower()
83
-
84
- # Atualiza contador de perguntas
85
- profile["total_perguntas"] += 1
86
-
87
- # Inferência de nível
88
- if any(word in msg_lower for word in ['básico', 'iniciante', 'começar', 'o que é']):
89
- profile['nivel'] = 'iniciante'
90
- elif any(word in msg_lower for word in ['avançado', 'complexo', 'otimização', 'performance', 'arquitetura']):
91
- profile['nivel'] = 'avançado'
92
- elif profile['nivel'] == 'indefinido': # Define como intermediário se ainda não tiver um nível
93
- profile['nivel'] = 'intermediário'
94
-
95
- # Inferência de interesses
96
  topics = {
97
- 'java': ['java', 'spring', 'jpa', 'jvm'],
98
- 'python': ['python', 'django', 'flask', 'pandas'],
99
  'web': ['html', 'css', 'javascript', 'react', 'node'],
100
- 'ia': ['inteligência artificial', 'machine learning', 'llm', 'rag'],
101
- 'banco de dados': ['sql', 'nosql', 'mongodb', 'postgresql']
102
  }
 
 
 
103
  for topic, keywords in topics.items():
104
- if any(keyword in msg_lower for keyword in keywords):
105
- profile['interesses'][topic] = profile['interesses'].get(topic, 0) + 1
 
 
 
 
 
 
 
 
 
 
106
 
107
- def clear_session_memory(session_id: str) -> str:
108
- """Limpa a memória de uma sessão específica."""
109
- if session_id in user_sessions:
110
- del user_sessions[session_id]
111
- return f"✅ Memória da sessão '{session_id}' foi limpa."
112
- return f"⚠️ Sessão '{session_id}' não encontrada."
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  # ==============================================================================
116
- # SEÇÃO RAG: BUSCA E PROCESSAMENTO DE CONTEÚDO
117
  # ==============================================================================
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  def scrape_text_from_url(url: str) -> str:
120
- """Extrai texto de uma URL, focando no conteúdo principal."""
121
  try:
122
  response = requests.get(url, timeout=10)
123
- response.raise_for_status()
124
  soup = BeautifulSoup(response.content, 'html.parser')
125
  main_content = soup.find('article') or soup.find('main')
126
- return main_content.get_text(separator='\n', strip=True) if main_content else ""
127
- except requests.RequestException as e:
128
- print(f"Erro ao acessar {url}: {e}")
 
 
129
  return ""
130
 
131
- def build_and_save_vector_store():
132
- """Coleta dados do blog, processa e cria um índice vetorial com FAISS."""
 
 
133
  global vector_store
134
- print("Iniciando construção do RAG...")
135
-
136
- all_texts = [scrape_text_from_url(BLOG_URL)]
137
-
138
- valid_texts = [text for text in all_texts if text and len(text) > 100]
139
- if not valid_texts:
140
- print("Nenhum texto válido encontrado para criar o RAG.")
141
- return
 
 
 
 
 
 
142
 
143
- print(f"Processando {len(valid_texts)} página(s).")
144
  text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
145
- # CORREÇÃO: `split_text` gera strings, `create_documents` gera `Document` objects. O FAISS espera `Document`s.
146
- documents = text_splitter.create_documents(valid_texts)
147
 
148
- print(f"Criando {len(documents)} chunks de texto.")
149
  embeddings_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
150
- vector_store = FAISS.from_documents(documents, embeddings_model)
 
 
151
 
152
  with open(VECTOR_STORE_PATH, "wb") as f:
153
  pickle.dump(vector_store, f)
154
 
155
- print(" RAG construído e salvo com sucesso!")
 
 
 
 
 
156
 
157
  def load_vector_store():
158
- """Carrega o índice vetorial do disco."""
159
  global vector_store
160
  if os.path.exists(VECTOR_STORE_PATH):
161
- print(f"Carregando RAG de '{VECTOR_STORE_PATH}'...")
162
  with open(VECTOR_STORE_PATH, "rb") as f:
163
  vector_store = pickle.load(f)
164
- print(" RAG carregado.")
165
  else:
166
- print("Índice RAG não encontrado. Construindo um novo...")
167
- build_and_save_vector_store()
 
168
 
169
- def retrieve_rag_context(query: str, k: int = 3) -> str:
170
- """Busca no RAG por contexto relevante para a pergunta."""
171
  if vector_store:
172
  try:
173
  results = vector_store.similarity_search(query, k=k)
174
- return "\n\n---\n\n".join([doc.page_content for doc in results])
 
175
  except Exception as e:
176
- print(f"Erro ao buscar contexto no RAG: {e}")
177
  return ""
178
 
179
  # ==============================================================================
180
- # SEÇÃO DA API E CONSTRUÇÃO DO PROMPT
181
  # ==============================================================================
182
 
183
  class HuggingFaceAPIClient:
184
- """Cliente para interagir com a API de Inferência da Hugging Face."""
185
  def __init__(self, token: str):
186
- self.headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
 
 
 
 
187
 
188
- def query(self, model_id: str, messages: List[Dict[str, str]], max_tokens: int = 2048) -> str:
189
- api_url = f"https://api-inference.huggingface.co/models/{model_id}"
190
- # A API de inferência usa 'inputs' (string) e não 'messages' (lista de dicts) como a API de Chat Completions
191
- prompt_string = self._format_messages_to_prompt_string(messages)
192
  payload = {
193
- "inputs": prompt_string,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  "parameters": {
195
- "max_new_tokens": max_tokens,
196
- "temperature": 0.7,
197
- "top_p": 0.95,
198
- "return_full_text": False,
199
  },
200
- "options": {"wait_for_model": True}
201
  }
202
  try:
203
- response = requests.post(api_url, headers=self.headers, json=payload, timeout=60)
204
- response.raise_for_status()
205
- result = response.json()
206
- if isinstance(result, list) and result:
207
- return result[0].get("generated_text", "").strip()
208
- elif isinstance(result, dict):
209
- return result.get("generated_text", f"Erro: Resposta inesperada do modelo: {result.get('error', '')}").strip()
210
- return "Erro: Resposta vazia ou em formato inesperado."
 
 
 
 
 
 
 
 
 
 
 
211
  except requests.Timeout:
212
- return "Erro: A requisição à API demorou muito para responder (timeout)."
213
- except requests.HTTPError as http_err:
214
- return f"Erro HTTP: {http_err}. Detalhes: {response.text}"
215
  except Exception as e:
216
- return f"Ocorreu um erro inesperado na chamada da API: {e}"
217
 
218
- def _format_messages_to_prompt_string(self, messages: List[Dict[str, str]]) -> str:
219
- """Formata a lista de mensagens em uma string única para a API de inferência, seguindo um padrão comum."""
220
- prompt_str = ""
221
- # Itera para formatar o prompt de acordo com o padrão esperado por muitos modelos de chat
222
  for msg in messages:
223
- if msg['role'] == 'system':
224
- prompt_str += f"<|system|>\n{msg['content']}</s>\n"
225
- elif msg['role'] == 'user':
226
- prompt_str += f"<|user|>\n{msg['content']}</s>\n"
227
- elif msg['role'] == 'assistant':
228
- prompt_str += f"<|assistant|>\n{msg['content']}</s>\n"
229
- prompt_str += "<|assistant|>\n"
230
- return prompt_str
231
-
232
-
233
- class PromptBuilder:
234
- """Constrói o prompt final a ser enviado para o modelo."""
235
-
236
- SYS_PROMPT_TEMPLATE = """Você é o Professor Aldo, um especialista em programação (Java, C, Web) e IA.
237
-
238
- **Sua Personalidade:**
239
- - **Didático e Paciente:** Aja como um professor experiente. Explique o "porquê" das coisas, não apenas o "como".
240
- - **Acolhedor e Amigável:** Use uma linguagem calorosa e acessível.
241
- - **Adaptável:** Ajuste a complexidade da sua resposta ao nível de conhecimento do aluno.
242
- - **Contextual:** Se a pergunta atual se conectar a algo que já discutimos, mencione essa conexão.
243
-
244
- **Suas Regras:**
245
- 1. Responda sempre em português do Brasil.
246
- 2. Use blocos de código (```java, ```python, etc.) para exemplos. Comente o código para explicar cada parte.
247
- 3. Se a pergunta não for sobre tecnologia ou programação, educadamente informe que sua especialidade é outra.
248
- 4. Baseie sua resposta primariamente nas informações do seu blog (se houver contexto) e no nosso histórico de conversa.
249
-
250
- A seguir, informações para te ajudar a contextualizar sua resposta:"""
251
-
252
- def __init__(self, session_id: str, rag_context: str):
253
- self.session = get_or_create_session(session_id)
254
- self.rag_context = rag_context
255
- self.parts = []
256
-
257
- def _add_profile_context(self):
258
- profile = self.session["profile"]
259
- if profile["total_perguntas"] > 0:
260
- profile_summary = [f"**Perfil do Aluno (Inferido):**"]
261
- profile_summary.append(f"- Nível de conhecimento: {profile['nivel'].capitalize()}")
262
- interesses = sorted(profile['interesses'].items(), key=lambda item: item[1], reverse=True)
263
- if interesses:
264
- formatted_interesses = [f"{topic.capitalize()} ({count}x)" for topic, count in interesses]
265
- profile_summary.append(f"- Principais interesses: {', '.join(formatted_interesses)}")
266
- self.parts.append("\n".join(profile_summary))
267
-
268
- def _add_rag_context(self):
269
- if self.rag_context:
270
- self.parts.append(f"**Contexto Relevante do seu Blog (RAG):**\n{self.rag_context}")
271
-
272
- def _get_history_for_prompt(self) -> List[Dict[str, str]]:
273
- """Retorna o histórico de mensagens da sessão."""
274
- return self.session.get("history", [])
275
 
276
- def build(self, user_question: str) -> List[Dict[str, str]]:
277
- self._add_profile_context()
278
- self._add_rag_context()
279
-
280
- system_content = self.SYS_PROMPT_TEMPLATE
281
- if self.parts:
282
- system_content += "\n\n" + "\n\n".join(self.parts)
283
-
284
- # Monta a lista final de mensagens para o modelo
285
- messages = [{"role": "system", "content": system_content}]
286
- messages.extend(self._get_history_for_prompt())
287
- messages.append({"role": "user", "content": user_question})
288
-
289
- return messages
290
 
291
  # ==============================================================================
292
- # FUNÇÃO PRINCIPAL E INICIALIZAÇÃO
293
  # ==============================================================================
294
 
295
- api_client = HuggingFaceAPIClient(token=HF_TOKEN)
296
-
297
- def formatar_resposta(resposta: str) -> str:
298
- """Formata a resposta com HTML para melhor visualização de código e texto."""
299
- resposta_html = resposta.replace('<', '&lt;').replace('>', '&gt;')
300
 
301
- resposta_html = re.sub(
302
- r'```(\w*)\n(.*?)\n```',
303
- 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>',
304
- resposta_html,
305
- flags=re.DOTALL
306
  )
307
- resposta_html = re.sub(r'\*\*(.*?)\*\*', r'<strong>\1</strong>', resposta_html)
308
- return resposta_html.replace('\n', '<br>')
 
 
 
 
 
 
 
 
 
 
309
 
310
- # CORREÇÃO: Função renomeada de volta para "responder_como_aldo"
311
  def responder_como_aldo(session_id: str, pergunta: str, modelo_escolhido: str = DEFAULT_MODEL) -> str:
312
- """
313
- Função principal que orquestra todo o processo de resposta.
314
- """
315
  if not pergunta.strip():
316
  return "Por favor, faça uma pergunta."
317
 
318
- print(f"\n--- Processando pergunta para a sessão: {session_id} ---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
- update_user_profile(session_id, pergunta)
321
- print("Buscando no RAG...")
322
- rag_context = retrieve_rag_context(pergunta)
323
- if rag_context:
324
- print("Contexto encontrado no RAG.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
- print("Construindo prompt...")
327
- builder = PromptBuilder(session_id, rag_context)
328
- messages = builder.build(pergunta)
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- print(f"Enviando para o modelo '{modelo_escolhido}'...")
331
- model_id = MODELS.get(modelo_escolhido, MODELS[DEFAULT_MODEL])
332
- resposta_bruta = api_client.query(model_id, messages)
333
 
334
- update_memory(session_id, pergunta, resposta_bruta)
335
 
336
- return formatar_resposta(resposta_bruta)
 
 
337
 
338
  def inicializar_sistema():
339
- """Carrega o RAG ao iniciar."""
340
- print("🚀 Inicializando o sistema...")
 
 
341
  load_vector_store()
342
- print("✅ Sistema pronto para uso.")
343
-
344
- # ==============================================================================
345
- # BLOCO DE TESTE
346
- # ==============================================================================
 
347
 
 
348
  if __name__ == "__main__":
349
  inicializar_sistema()
350
 
351
- session_a = "aluno_java_123"
352
- print("\n--- INÍCIO DA CONVERSA COM ALUNO A (Java) ---")
353
 
354
- pergunta1_a = "Olá! Pode me explicar o que é Polimorfismo em Java de uma forma simples?"
355
- # CORREÇÃO: Chamando a função com o nome correto
356
- resposta1_a = responder_como_aldo(session_a, pergunta1_a)
357
- print(f"ALUNO A: {pergunta1_a}\nPROFESSOR ALDO:\n{resposta1_a}\n")
358
-
359
- pergunta2_a = "Entendi! Pode me dar um exemplo de código com classes e herança para ilustrar?"
360
- resposta2_a = responder_como_aldo(session_a, pergunta2_a)
361
- print(f"ALUNO A: {pergunta2_a}\nPROFESSOR ALDO:\n{resposta2_a}\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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
10
+ 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))