File size: 7,511 Bytes
23d2c72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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
from .web_search import WebSearchHandler

# 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

        # 3. Web Search (Tavily)
        self.web_search_handler = WebSearchHandler()

        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, web_search=False, thinking_mode=False):
        """Processo principal de raciocínio: Buscar -> Lembrar -> Ver -> Pensar -> 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[:]
        
        # 0. Thinking Mode - Adiciona instrução de CoT
        if thinking_mode:
            thinking_prompt = {
                "role": "system", 
                "content": """MODO THINKING ATIVADO: Antes de dar sua resposta final, pense passo a passo.

Coloque todo seu raciocínio dentro de tags <thinking>...</thinking>.

Após fechar a tag </thinking>, dê sua resposta final de forma clara e direta.

Exemplo:

<thinking>

1. Primeiro, vou analisar...

2. Considerando que...

3. Portanto...

</thinking>



[Sua resposta final aqui]"""
            }
            messages.append(thinking_prompt)

        # 0. Buscar na Web (se habilitado)
        if web_search and self.web_search_handler.is_available():
            search_results = self.web_search_handler.search(user_input)
            if search_results:
                search_context = f"--- RESULTADOS DA BUSCA WEB ---\n{search_results}\n--- FIM DA BUSCA ---"
                messages.append({"role": "system", "content": search_context})
        
        # 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