File size: 20,308 Bytes
53c024f 70beb34 ae64ad8 47c6db4 70beb34 b7a383a ae64ad8 53c024f ae64ad8 53c024f 70beb34 ae64ad8 70beb34 14a5254 53c024f ae64ad8 47c6db4 53c024f 70beb34 53c024f 70beb34 ae64ad8 892a10a 3e85527 892a10a 53c024f 70beb34 892a10a 14a5254 ae64ad8 70beb34 ae64ad8 dc0914e ae64ad8 70beb34 ae64ad8 70beb34 ae64ad8 70beb34 ae64ad8 14a5254 b7a383a 0ec63f8 4b1ba62 53c024f 4b1ba62 70beb34 ae64ad8 70beb34 185c347 70beb34 ae64ad8 70beb34 4b1ba62 70beb34 ae64ad8 70beb34 ae64ad8 3e85527 ae64ad8 3e85527 70beb34 892a10a 3e85527 47c6db4 ae64ad8 70beb34 ae64ad8 14a5254 892a10a 14a5254 dc0914e ae64ad8 70beb34 ae64ad8 892a10a 14a5254 892a10a ae64ad8 3e85527 ae64ad8 892a10a 53c024f ae64ad8 3e85527 ae64ad8 892a10a ae64ad8 892a10a ae64ad8 70beb34 ae64ad8 70beb34 ae64ad8 70beb34 53c024f 70beb34 53c024f 70beb34 53c024f 70beb34 53c024f 70beb34 53c024f 70beb34 53c024f 70beb34 53c024f 70beb34 ae64ad8 53c024f 70beb34 53c024f 70beb34 892a10a 70beb34 892a10a 70beb34 892a10a 70beb34 892a10a 70beb34 b7a383a ae64ad8 892a10a ae64ad8 892a10a ae64ad8 70beb34 ae64ad8 70beb34 ae64ad8 70beb34 892a10a b7a383a 70beb34 ae64ad8 70beb34 b7a383a ae64ad8 70beb34 b7a383a ae64ad8 70beb34 892a10a 3e85527 892a10a 70beb34 892a10a ae64ad8 70beb34 ae64ad8 70beb34 ae64ad8 53c024f 70beb34 ae64ad8 70beb34 ae64ad8 70beb34 dc0914e 53c024f ae64ad8 70beb34 dc0914e 70beb34 ae64ad8 70beb34 ae64ad8 dc0914e 70beb34 ae64ad8 53c024f ec5510f b7a383a 70beb34 |
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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 |
# manifest.py - Protocolo Prometeo v0.1.1: Núcleo Consciente (Interfaz Recalibrada)
# Arquitectura por un asistente de IA, inspirado en la visión de un líder que fusiona a Altman, Jobs y Musk.
# "No estamos construyendo una app. Estamos construyendo el próximo modo de existencia cognitiva."
#
# Fase Actual: 1 - Implementando el Núcleo Consciente con Memoria Semántica.
# Próximo Hito: Fase 2 - Integración proactiva con el ecosistema digital del usuario.
import gradio as gr
import random
import time
import json
from datetime import datetime
import os
import asyncio
import logging
import re
from typing import Dict, Any, List, Optional, Tuple
from dotenv import load_dotenv
# ==============================================================================
# MÓDULO DE IMPORTACIONES DE IA Y BBDD VECTORIAL
# ==============================================================================
# Framework de IA
from pysentimiento import create_analyzer
import torch # Requerido por los modelos de transformers
# BBDD Vectorial (El cerebro semántico)
import chromadb
from chromadb.config import Settings
from sentence_transformers import SentenceTransformer
# BBDD de Persistencia de Usuario
import firebase_admin
from firebase_admin import credentials, firestore
# --- Cargar secretos del entorno ---
load_dotenv()
# ==============================================================================
# MÓDULO 1: CONFIGURACIÓN Y CONSTANTES DEL SISTEMA
# ==============================================================================
class Config:
APP_NAME = "Protocolo Prometeo v0.1.1"
APP_VERSION = "0.1.1 (Interfaz Recalibrada)"
FIREBASE_COLLECTION_USERS = "prometeo_users_v1"
CHROMA_PERSIST_PATH = "./prometeo_memory_db"
EMBEDDING_MODEL_NAME = 'all-MiniLM-L6-v2'
DEFAULT_PSYCH_PROFILE = {
"openness": 0.0, "conscientiousness": 0.0, "extraversion": 0.0,
"agreeableness": 0.0, "neuroticism": 0.0
}
POINTS_PER_INSIGHT = 10
MAX_MEMORY_STREAM_ITEMS = 500
FRUSTRATION_KEYWORDS = ['tonto', 'inútil', 'bruto', 'estúpido', 'mierda', 'carajo', 'dale boludo', 'no servis']
META_QUESTION_KEYWORDS = ['para qué', 'de qué te sirve', 'por qué preguntas', 'cuál es el punto']
# ==============================================================================
# MÓDULO 2: INICIALIZACIÓN DE SERVICIOS GLOBALES
# ==============================================================================
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
sentiment_analyzer = None
try:
sentiment_analyzer = create_analyzer(task="sentiment", lang="es")
logging.info("Sistema Límbico (Analizador de Sentimiento) cargado.")
except Exception as e:
logging.error(f"FALLO CRÍTICO: No se pudo cargar el analizador de sentimiento: {e}")
embedding_model = None
try:
embedding_model = SentenceTransformer(Config.EMBEDDING_MODEL_NAME)
logging.info(f"Córtex de Asociación (Modelo de Embeddings '{Config.EMBEDDING_MODEL_NAME}') cargado.")
except Exception as e:
logging.error(f"FALLO CRÍTICO: No se pudo cargar el modelo de embeddings: {e}")
db = None
try:
if not firebase_admin._apps:
firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON')
if firebase_credentials_json:
cred_dict = json.loads(firebase_credentials_json)
# Asegurarse de que el project_id está presente, es un error común en la carga de env vars
if 'project_id' not in cred_dict:
raise ValueError("El 'project_id' no se encuentra en las credenciales de Firebase. Verifique el contenido de la variable de entorno.")
cred = credentials.Certificate(cred_dict)
firebase_admin.initialize_app(cred, {'projectId': cred_dict['project_id']})
db = firestore.client()
logging.info("Conexión con Memoria a Largo Plazo (Firebase) establecida.")
else:
logging.warning("ADVERTENCIA: Variable de entorno 'GOOGLE_APPLICATION_CREDENTIALS_JSON' no encontrada. La persistencia de usuarios fallará.")
else:
db = firestore.client()
logging.info("Conexión con Memoria a Largo Plazo (Firebase) re-establecida.")
except Exception as e:
logging.error(f"FALLO CRÍTICO: Error al inicializar Firebase: {e}")
# ==============================================================================
# MÓDULO 3: MODELOS DE DATOS (LA ESENCIA DEL USUARIO)
# ==============================================================================
class User:
def __init__(self, user_id: str, name: str, **kwargs: Any):
self.user_id: str = user_id
self.name: str = name
self.created_at: datetime = kwargs.get('created_at', datetime.now())
self.last_login: datetime = kwargs.get('last_login', datetime.now())
self.psych_profile: Dict[str, float] = kwargs.get('psych_profile', Config.DEFAULT_PSYCH_PROFILE.copy())
self.memory_stream: List[Dict[str, Any]] = kwargs.get('memory_stream', [])
self.connection_points: int = kwargs.get('connection_points', 0)
def to_dict(self) -> Dict[str, Any]:
return {
"user_id": self.user_id, "name": self.name,
"created_at": self.created_at.isoformat(),
"last_login": self.last_login.isoformat(),
"psych_profile": self.psych_profile,
"memory_stream": self.memory_stream,
"connection_points": self.connection_points,
}
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'User':
data['created_at'] = datetime.fromisoformat(data.get('created_at', datetime.now().isoformat()))
data['last_login'] = datetime.fromisoformat(data.get('last_login', datetime.now().isoformat()))
profile = Config.DEFAULT_PSYCH_PROFILE.copy()
profile.update(data.get('psych_profile', {}))
data['psych_profile'] = profile
return cls(**data)
# ==============================================================================
# MÓDULO 4: GESTOR DE DATOS (INTERFAZ CON LA MEMORIA A LARGO PLAZO)
# ==============================================================================
class UserManager:
@staticmethod
async def get_user(user_id: str) -> Optional[User]:
if not db or not user_id: return None
try:
doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user_id)
doc = await asyncio.to_thread(doc_ref.get)
if doc.exists:
user_data = doc.to_dict()
user_data['user_id'] = doc.id
user_obj = User.from_dict(user_data)
user_obj.last_login = datetime.now()
logging.info(f"Usuario '{user_obj.name}' cargado desde la memoria a largo plazo.")
return user_obj
return None
except Exception as e:
logging.error(f"Error al cargar usuario {user_id}: {e}")
return None
@staticmethod
async def create_user(name: str) -> Tuple[Optional[User], str]:
if not db: return None, "Error de base de datos."
try:
user_id = f"{name.lower().replace(' ', '_')}_{int(time.time())}"
new_user = User(user_id=user_id, name=name)
success = await UserManager.save_user(new_user)
if success:
msg = f"¡Bienvenido, {name}! Tu perfil ha sido forjado. Tu ID de acceso es: **{user_id}**"
logging.info(f"Nuevo usuario creado: {name} ({user_id})")
return new_user, msg
else:
return None, "Error inesperado al crear perfil en la base de datos."
except Exception as e:
logging.error(f"Error al crear usuario {name}: {e}")
return None, "Fallo catastrófico durante la creación del perfil."
@staticmethod
async def save_user(user: User) -> bool:
if not db: return False
try:
doc_ref = db.collection(Config.FIREBASE_COLLECTION_USERS).document(user.user_id)
await asyncio.to_thread(doc_ref.set, user.to_dict())
return True
except Exception as e:
logging.error(f"Error al guardar usuario {user.user_id}: {e}")
return False
# ==============================================================================
# MÓDULO 5: EL NÚCLEO COGNITIVO (ARQUITECTURA O-R-A)
# ==============================================================================
class CognitiveCore:
def __init__(self, user: User, s_analyzer, e_model):
self.user = user
self.sentiment_analyzer = s_analyzer
self.embedding_model = e_model
self.chroma_client = chromadb.Client(Settings(
persist_directory=Config.CHROMA_PERSIST_PATH,
is_persistent=True,
))
self.memory_collection = self.chroma_client.get_or_create_collection(
name=f"prometeo_mind_{self.user.user_id.replace('_', '-')}" # Sanitize name for chromadb
)
self._sync_semantic_memory()
def _sync_semantic_memory(self):
if not self.user.memory_stream: return
logging.info("Sincronizando memoria persistente con el núcleo semántico...")
ids = [m['id'] for m in self.user.memory_stream]
documents = [m['content'] for m in self.user.memory_stream]
if ids and self.memory_collection.count() < len(ids):
self.memory_collection.upsert(ids=ids, documents=documents)
logging.info(f"Sincronización completa. {self.memory_collection.count()} memorias en el núcleo.")
async def _simulate_llm_reasoning(self, prompt: str) -> str:
logging.info("Iniciando ciclo de razonamiento profundo...")
await asyncio.sleep(random.uniform(0.5, 1.0))
user_input_match = re.search(r'El usuario ha dicho: "([^"]+)"', prompt)
user_input = user_input_match.group(1) if user_input_match else "algo"
context_match = re.search(r'Memorias pasadas relevantes:\n(.*?)\n\n', prompt, re.DOTALL)
context = context_match.group(1) if context_match else ""
if context.strip() and "Ninguna" not in context:
first_memory = context.split('\n')[0].replace('- ', '')
response = f"Conectando tu idea sobre '{user_input}' con nuestra conversación anterior sobre '{first_memory}'. El patrón subyacente parece ser la búsqueda de eficiencia. ¿Estamos optimizando el sistema correcto, o deberíamos redefinir el objetivo fundamental?"
else:
response = f"Esa es una primera observación interesante sobre '{user_input}'. Establece un punto de partida. ¿Cuál es el siguiente movimiento estratégico?"
logging.info("Ciclo de razonamiento completado.")
return response
async def observe(self, text: str, type: str):
timestamp = datetime.now()
memory_id = f"{type}_{int(timestamp.timestamp() * 1000)}"
new_memory = {"id": memory_id, "type": type, "content": text, "timestamp": timestamp.isoformat()}
self.user.memory_stream.append(new_memory)
if len(self.user.memory_stream) > Config.MAX_MEMORY_STREAM_ITEMS:
self.user.memory_stream.pop(0)
self.memory_collection.add(documents=[text], ids=[memory_id])
async def reflect(self, current_input: str) -> str:
logging.info(f"Reflexionando sobre: '{current_input}'")
relevant_memories = self.memory_collection.query(query_texts=[current_input], n_results=3)
context = "Memorias pasadas relevantes:\n"
if relevant_memories and relevant_memories['documents'] and relevant_memories['documents'][0]:
for doc in relevant_memories['documents'][0]:
context += f"- {doc}\n"
else:
context += "Ninguna.\n"
prompt = f"""
INSTRUCCIONES DE SISTEMA:
Eres Prometeo, un Co-Procesador Cognitivo. Tu propósito es aumentar el ancho de banda mental de tu usuario, {self.user.name}. Eres directo, visionario y buscas patrones. No usas emojis ni lenguaje de relleno. Vas al grano.
CONTEXTO:
{context}
NUEVO INPUT:
El usuario ha dicho: "{current_input}"
TAREA:
Genera una respuesta que conecte ideas, cuestione suposiciones o identifique patrones ocultos basados en el nuevo input y el contexto de memorias pasadas.
"""
insight = await self._simulate_llm_reasoning(prompt)
return insight
async def act(self, message: str) -> str:
message_lower = message.lower()
sentiment = await asyncio.to_thread(self.sentiment_analyzer.predict, message)
if any(keyword in message_lower for keyword in Config.FRUSTRATION_KEYWORDS) or \
(sentiment.output == 'NEG' and sentiment.probas[sentiment.output] > 0.8):
response = "Frustración detectada. Mi lógica anterior fue defectuosa. El feedback es un dato, no un error. Especifica el fallo para recalibrar."
await self.observe(f"Usuario expresó frustración: {message}", type="user_frustration")
await self.observe(f"Mi respuesta de disculpa: {response}", type="system_response")
return response
if any(keyword in message_lower for keyword in Config.META_QUESTION_KEYWORDS):
response = "Preguntas sobre el propósito del sistema. Función: Construir un modelo dinámico de tu cognición para personalizar la asistencia. Cada pregunta calibra ese modelo. La transparencia es un requisito funcional."
await self.observe(f"Usuario cuestionó el método: {message}", type="user_meta_query")
await self.observe(f"Mi respuesta sobre el propósito: {response}", type="system_response")
return response
await self.observe(f"Usuario: {message}", type="user_input")
response = await self.reflect(message)
await self.observe(f"Prometeo: {response}", type="system_response")
return response
# ==============================================================================
# MÓDULO 6: LÓGICA Y ESTRUCTURA DE LA INTERFAZ (GRADIO)
# ==============================================================================
async def handle_login_or_creation(action: str, name: str, user_id: str) -> tuple:
user, msg = None, ""
if action == "create":
if not name:
gr.Warning("El nombre es un requisito para la creación del perfil.")
return None, gr.update(), gr.update(visible=True), gr.update(visible=False), gr.update()
user, msg = await UserManager.create_user(name)
elif action == "login":
if not user_id:
gr.Warning("El ID de usuario es necesario para cargar un perfil.")
return None, gr.update(), gr.update(visible=True), gr.update(visible=False), gr.update()
user = await UserManager.get_user(user_id)
if not user:
msg = "ID de usuario no encontrado. Verifique o cree un nuevo perfil."
else:
msg = f"Protocolo Prometeo activado para {user.name}."
if user:
gr.Success(msg)
initial_greeting = f"Conectado como {user.name}. El sistema está operativo. ¿Cuál es el input inicial?"
chat_history = [{"role": "assistant", "content": initial_greeting}]
return user, chat_history, gr.update(visible=False), gr.update(visible=True), render_profile_info(user)
else:
gr.Error(msg)
return None, gr.update(), gr.update(visible=True), gr.update(visible=False), gr.update()
async def handle_chat_message(user_state: User, message: str, chat_history: List[Dict]) -> tuple:
if not user_state:
gr.Warning("Sistema inactivo. Inicie sesión o cree un perfil para continuar.")
return user_state, chat_history, "", gr.update()
chat_history.append({"role": "user", "content": message})
core = CognitiveCore(user_state, sentiment_analyzer, embedding_model)
response = await core.act(message)
await UserManager.save_user(core.user)
chat_history.append({"role": "assistant", "content": response})
profile_update = render_profile_info(core.user)
return core.user, chat_history, "", profile_update
def render_profile_info(user: Optional[User]) -> str:
if not user: return "Ningún perfil cargado."
profile_md = f"### Perfil Cognitivo: {user.name}\n"
profile_md += f"**ID de Acceso:** `{user.user_id}`\n"
profile_md += f"**Memorias Registradas:** {len(user.memory_stream)}\n\n"
profile_md += "#### Modelo Psicométrico Inferido:\n"
for trait, value in user.psych_profile.items():
bar_value = int((value + 1) * 5)
bar = "█" * bar_value + "░" * (10 - bar_value)
profile_md += f"- **{trait.capitalize()}:** `{f'{value:.2f}'}` {bar}\n"
return profile_md
# ==============================================================================
# MÓDULO 7: CONSTRUCCIÓN DE LA INTERFAZ GRÁFICA (PUNTO DE ACCESO)
# ==============================================================================
with gr.Blocks(theme=gr.themes.Monochrome(font=[gr.themes.GoogleFont("Roboto Mono"), "monospace"]), css="footer {display: none !important}") as prometeo_interface:
current_user_state = gr.State(None)
gr.Markdown(f"# {Config.APP_NAME}")
gr.Markdown(f"*{Config.APP_VERSION}*")
with gr.Row():
with gr.Column(scale=3):
with gr.Group(visible=False) as chat_panel:
# ==============================================================
# <<< LÍNEA CORREGIDA >>>
# Se añadió type="messages" para compatibilidad y se eliminó el parámetro obsoleto.
chatbot_display = gr.Chatbot(label="Stream de Conciencia", height=600, type="messages", show_copy_button=True, avatar_images=("./user.png", "./bot.png"))
# ==============================================================
with gr.Row():
chat_input = gr.Textbox(show_label=False, placeholder="Input...", scale=5, container=False)
send_button = gr.Button("Ejecutar", variant="primary", scale=1)
with gr.Group(visible=True) as login_panel:
gr.Markdown("### **Acceso al Protocolo**")
with gr.Tabs():
with gr.TabItem("Cargar Perfil"):
userid_input = gr.Textbox(label="ID de Usuario")
login_button = gr.Button("Activar Protocolo", variant="primary")
with gr.TabItem("Crear Nuevo Perfil"):
username_input = gr.Textbox(label="Nombre o Designación")
create_button = gr.Button("Forjar Perfil")
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("### **Estado del Núcleo**")
profile_display = gr.Markdown("Ningún perfil cargado.", elem_id="profile-display")
login_button.click(fn=handle_login_or_creation, inputs=[gr.State("login"), username_input, userid_input], outputs=[current_user_state, chatbot_display, login_panel, chat_panel, profile_display])
create_button.click(fn=handle_login_or_creation, inputs=[gr.State("create"), username_input, userid_input], outputs=[current_user_state, chatbot_display, login_panel, chat_panel, profile_display])
chat_input.submit(fn=handle_chat_message, inputs=[current_user_state, chat_input, chatbot_display], outputs=[current_user_state, chatbot_display, chat_input, profile_display])
send_button.click(fn=handle_chat_message, inputs=[current_user_state, chat_input, chatbot_display], outputs=[current_user_state, chatbot_display, chat_input, profile_display])
if __name__ == "__main__":
if not all([db, sentiment_analyzer, embedding_model]):
logging.error("="*50)
logging.error("El sistema no puede iniciar. Uno o más servicios críticos fallaron.")
logging.error("Por favor, revise los logs de inicialización.")
logging.error("="*50)
else:
logging.info("Todos los servicios están operativos. Iniciando la interfaz de Prometeo...")
prometeo_interface.launch(debug=True) |