jade_port / jade /curator_heuristic.py
Madras1's picture
Upload 13 files
831e835 verified
import time
import logging
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# Configura um logger específico para o Curador
logging.basicConfig(level=logging.INFO, format="%(asctime)s - CURATOR - %(levelname)s - %(message)s")
class MemoryCuratorHeuristic:
"""
Implementa a curadoria de memória baseada em uma fórmula heurística (RFR-Score),
conforme a arquitetura proposta por Ada.
Este sistema é determinístico, rápido e escalável.
"""
def __init__(self, shorestone_memory, llm_agent=None, alpha=0.5, beta=0.3, gamma=0.2, lambda_decay=0.01):
"""
Inicializa o Curador.
:param shorestone_memory: A instância ativa do sistema de memória ShoreStone.
:param llm_agent: (Opcional) A instância do agente LLM para o modo híbrido.
:param alpha: Peso da Frequência (F).
:param beta: Peso da Recência (R).
:param gamma: Peso da Relevância Geométrica (G).
:param lambda_decay: Taxa de decaimento para o score de Recência. Controla quão rápido o esquecimento acontece.
"""
self.memory = shorestone_memory
self.llm_agent = llm_agent
# Pesos da fórmula de Score: S = α*F + β*R + γ*G
self.ALPHA = alpha
self.BETA = beta
self.GAMMA = gamma
self.LAMBDA = lambda_decay # Um lambda pequeno significa um esquecimento mais lento
logging.info("Curador de Memória Heurístico iniciado com sucesso.")
def _calculate_score(self, mem_meta, mem_embedding, all_embeddings, all_ids):
"""Calcula o RFR-Score para uma única memória."""
# 1. Frequência (F) - Usando escala logarítmica
access_count = mem_meta.get('access_count', 0)
f_score = np.log(1 + access_count)
# 2. Recência (R) - Usando decaimento exponencial
now = time.time()
last_accessed = mem_meta.get('last_accessed_at', now)
days_since_access = (now - last_accessed) / (24 * 60 * 60)
r_score = np.exp(-self.LAMBDA * days_since_access)
# 3. Relevância Geométrica (G) - Coerência com vizinhos próximos (k-NN)
# Esta é uma abordagem robusta: uma memória é relevante se estiver em um "bairro" coeso.
# Encontra os 5 vizinhos mais próximos (excluindo ela mesma).
similarities = cosine_similarity(mem_embedding.reshape(1, -1), all_embeddings)[0]
# Pega os índices dos mais similares, em ordem decrescente
nearest_indices = np.argsort(similarities)[::-1][1:6] # [1:6] para ignorar a si mesma
if len(nearest_indices) > 0:
# A relevância é a similaridade média com seus vizinhos mais próximos
g_score = np.mean(similarities[nearest_indices])
else:
g_score = 0 # Se não tiver vizinhos, a relevância é zero
# Fórmula Final Ponderada
final_score = (self.ALPHA * f_score) + (self.BETA * r_score) + (self.GAMMA * g_score)
return final_score
def run_maintenance_cycle(self, t_delete=0.2, t_archive=0.4):
"""
Executa um ciclo de verificação, pontuação e poda da memória.
"""
logging.info("Iniciando ciclo de manutenção da memória...")
collection = self.memory.collection
if not collection or collection.count() == 0:
logging.info("Nenhuma memória para manter.")
return
# Pega TODAS as memórias, incluindo seus embeddings e metadados. Essencial para o cálculo de relevância.
all_mems = collection.get(include=["metadatas", "embeddings"])
if len(all_mems['ids']) < 10: # Não roda a manutenção em memórias muito recentes
logging.info("Poucas memórias, pulando ciclo de manutenção.")
return
all_ids = all_mems['ids']
all_embeddings = np.array(all_mems['embeddings'])
all_metadatas = all_mems['metadatas']
ids_to_delete = []
logging.info(f"Avaliando {len(all_ids)} memórias...")
for i in range(len(all_ids)):
mem_id = all_ids[i]
mem_meta = all_metadatas[i]
mem_embedding = all_embeddings[i]
score = self._calculate_score(mem_meta, mem_embedding, all_embeddings, all_ids)
# --- Lógica de Decisão ---
if score < t_delete:
# MODO HÍBRIDO: Verificação de segurança para memórias fundamentais
if "meu criador" in mem_meta.get('text', '').lower() or "gabriel yogi" in mem_meta.get('text', '').lower():
logging.warning(f"SCORE BAIXO [{score:.2f}] para memória potencialmente fundamental '{mem_id}'. IGNORANDO EXCLUSÃO.")
continue
logging.info(f"Memória '{mem_id}' marcada para exclusão com score [{score:.2f}]")
ids_to_delete.append(mem_id)
# A lógica de arquivamento (mover para outra coleção) pode ser adicionada aqui.
# elif t_delete <= score < t_archive:
# ...
if ids_to_delete:
logging.info(f"Excluindo {len(ids_to_delete)} memórias obsoletas...")
collection.delete(ids=ids_to_delete)
logging.info("Exclusão concluída.")
else:
logging.info("Nenhuma memória atingiu o threshold para exclusão. Ciclo concluído.")