File size: 6,152 Bytes
831e835
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import json
import logging
import os
import sys
import time
import uuid

from groq import Groq

# Importa nossos módulos customizados
from .handlers import ImageHandler
from .tts import TTSPlayer
from .utils import slim_history
from .shorestone import ShoreStoneMemory
from .curator_heuristic import MemoryCuratorHeuristic

# Configura o logger principal
logging.basicConfig(level=logging.INFO, format="%(asctime)s - JADE - %(levelname)s - %(message)s")

class JadeAgent:
    def __init__(self, config_path="jade/config.json"):
        # Carrega configurações
        # Try to load from absolute path first, then relative
        try:
            with open(config_path) as f:
                self.cfg = json.load(f)
        except FileNotFoundError:
             # Fallback: try to find it relative to this file
            base_dir = os.path.dirname(os.path.abspath(__file__))
            config_path = os.path.join(base_dir, "config.json")
            with open(config_path) as f:
                self.cfg = json.load(f)

        # --- Configuração da API Groq ---
        logging.info("Iniciando J.A.D.E. em modo API (Groq)...")
        self.api_key = self._get_api_key()
        self.client = Groq(api_key=self.api_key)
        self.model_name = self.cfg.get("groq_model", "meta-llama/llama-4-maverick-17b-128e-instruct")

        # System Prompt Base
        self.system_prompt = {"role": "system", "content": "Você é J.A.D.E., uma IA multimodal calma e inteligente. Seja direta. Responda de forma concisa e natural. NÃO explique seu processo de pensamento. Apenas responda à pergunta."}
        
        # --- Inicialização dos Módulos ---
        logging.info("Carregando módulos de percepção e memória...")
        
        # Visão e Fala
        self.image_handler = ImageHandler(self.cfg.get("caption_model", "Salesforce/blip-image-captioning-large"))
        self.tts = TTSPlayer(lang=self.cfg.get("language", "pt"))
        
        # 1. Memória ShoreStone (Persistente)
        self.memory = ShoreStoneMemory()
        # Inicializa com sessão padrão, mas será trocada dinamicamente no respond()
        self.memory.load_or_create_session("sessao_padrao_gabriel")
        
        # 2. Curador Heurístico (Manutenção Automática)
        self.curator = MemoryCuratorHeuristic(shorestone_memory=self.memory)
        self.response_count = 0
        self.maintenance_interval = 10 # Executar a manutenção a cada 10 interações

        logging.info(f"J.A.D.E. pronta e conectada ao modelo {self.model_name}.")

    def _get_api_key(self):
        """Recupera a chave da API do ambiente de forma segura."""
        key = os.getenv("GROQ_API_KEY")
        if not key:
            logging.error("Chave GROQ_API_KEY não encontrada nas variáveis de ambiente.")
            # For development, try to warn but not crash if possible, but Groq needs it.
            # raise RuntimeError("❌ GROQ_API_KEY não encontrada. Defina a variável de ambiente.")
            print("WARNING: GROQ_API_KEY not found.")
        return key

    def _chat(self, messages):
        """Envia as mensagens para a Groq e retorna a resposta."""
        try:
            chat = self.client.chat.completions.create(
                messages=messages, 
                model=self.model_name,
                temperature=0.7, # Criatividade balanceada
                max_tokens=1024  # Limite de resposta razoável
            )
            return chat.choices[0].message.content.strip()
        except Exception as e:
            logging.error(f"Erro na comunicação com a Groq: {e}")
            return "Desculpe, tive um problema ao me conectar com meu cérebro na nuvem."

    def respond(self, history, user_input, user_id="default", vision_context=None):
        """Processo principal de raciocínio: Lembrar -> Ver -> Responder -> Memorizar -> Manter."""
        
        # TROCA A SESSÃO DA MEMÓRIA PARA O USUÁRIO ATUAL
        session_name = f"user_{user_id}"
        self.memory.load_or_create_session(session_name)
        
        messages = history[:]
        
        # 1. Lembrar (Recuperação de Contexto)
        memories = self.memory.remember(user_input)
        if memories:
            memory_context = f"--- MEMÓRIAS RELEVANTES (ShoreStone) ---\n{memories}\n--- FIM DAS MEMÓRIAS ---"
            # Inserimos as memórias como contexto de sistema para guiar a resposta
            messages.append({"role": "system", "content": memory_context})

        # 2. Ver (Contexto Visual)
        if vision_context:
            messages.append({"role": "system", "content": f"Contexto visual da imagem que o usuário enviou: {vision_context}"})

        # Adiciona a pergunta atual ao histórico temporário e ao prompt
        history.append({"role": "user", "content": user_input})
        messages.append({"role": "user", "content": user_input})

        # 3. Responder (Geração)
        resposta = self._chat(messages)
        
        # Atualiza histórico
        history.append({"role": "assistant", "content": resposta})
        history = slim_history(history, keep=self.cfg.get("max_context", 12))
        
        # 4. Memorizar (Armazenamento Persistente)
        self.memory.memorize(user_input, resposta)

        print(f"\n🤖 J.A.D.E.: {resposta}")
        
        # Falar (TTS) - Modified for Backend compatibility
        audio_path = None
        try:
            # Uses the TTSPlayer from tts.py which has save_audio_to_file
            audio_path = self.tts.save_audio_to_file(resposta)
        except Exception as e:
            logging.warning(f"TTS falhou (silenciado): {e}")
            
        # 5. Manter (Ciclo de Curadoria Automática)
        self.response_count += 1
        if self.response_count % self.maintenance_interval == 0:
            logging.info(f"Ciclo de manutenção agendado (interação {self.response_count}). Verificando saúde da memória...")
            try:
                self.curator.run_maintenance_cycle()
            except Exception as e:
                logging.error(f"Erro no Curador de Memória: {e}")
        
        return resposta, audio_path, history