Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# manifest.py - Protocolo Prometeo v0.1:
|
| 2 |
# Arquitectura por un asistente de IA, inspirado en la visión de un líder que fusiona a Altman, Jobs y Musk.
|
| 3 |
# "No estamos construyendo una app. Estamos construyendo el próximo modo de existencia cognitiva."
|
| 4 |
#
|
|
@@ -13,8 +13,11 @@ from datetime import datetime
|
|
| 13 |
import os
|
| 14 |
import asyncio
|
| 15 |
import logging
|
|
|
|
| 16 |
from typing import Dict, Any, List, Optional, Tuple
|
| 17 |
|
|
|
|
|
|
|
| 18 |
# ==============================================================================
|
| 19 |
# MÓDULO DE IMPORTACIONES DE IA Y BBDD VECTORIAL
|
| 20 |
# ==============================================================================
|
|
@@ -31,27 +34,26 @@ from sentence_transformers import SentenceTransformer
|
|
| 31 |
import firebase_admin
|
| 32 |
from firebase_admin import credentials, firestore
|
| 33 |
|
|
|
|
|
|
|
|
|
|
| 34 |
# ==============================================================================
|
| 35 |
# MÓDULO 1: CONFIGURACIÓN Y CONSTANTES DEL SISTEMA
|
| 36 |
# ==============================================================================
|
| 37 |
class Config:
|
| 38 |
-
APP_NAME = "Protocolo Prometeo v0.1"
|
| 39 |
-
APP_VERSION = "0.1.
|
| 40 |
|
| 41 |
-
# Firebase
|
| 42 |
FIREBASE_COLLECTION_USERS = "prometeo_users_v1"
|
| 43 |
-
|
| 44 |
-
# ChromaDB (Memoria Semántica)
|
| 45 |
CHROMA_PERSIST_PATH = "./prometeo_memory_db"
|
| 46 |
-
EMBEDDING_MODEL_NAME = 'all-MiniLM-L6-v2'
|
| 47 |
|
| 48 |
-
# Lógica de IA
|
| 49 |
DEFAULT_PSYCH_PROFILE = {
|
| 50 |
"openness": 0.0, "conscientiousness": 0.0, "extraversion": 0.0,
|
| 51 |
"agreeableness": 0.0, "neuroticism": 0.0
|
| 52 |
}
|
| 53 |
POINTS_PER_INSIGHT = 10
|
| 54 |
-
MAX_MEMORY_STREAM_ITEMS = 500
|
| 55 |
FRUSTRATION_KEYWORDS = ['tonto', 'inútil', 'bruto', 'estúpido', 'mierda', 'carajo', 'dale boludo', 'no servis']
|
| 56 |
META_QUESTION_KEYWORDS = ['para qué', 'de qué te sirve', 'por qué preguntas', 'cuál es el punto']
|
| 57 |
|
|
@@ -60,7 +62,6 @@ class Config:
|
|
| 60 |
# ==============================================================================
|
| 61 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 62 |
|
| 63 |
-
# --- Analizador de Sentimiento (Sistema Límbico Rápido) ---
|
| 64 |
sentiment_analyzer = None
|
| 65 |
try:
|
| 66 |
sentiment_analyzer = create_analyzer(task="sentiment", lang="es")
|
|
@@ -68,7 +69,6 @@ try:
|
|
| 68 |
except Exception as e:
|
| 69 |
logging.error(f"FALLO CRÍTICO: No se pudo cargar el analizador de sentimiento: {e}")
|
| 70 |
|
| 71 |
-
# --- Modelo de Embeddings (Córtex de Asociación) ---
|
| 72 |
embedding_model = None
|
| 73 |
try:
|
| 74 |
embedding_model = SentenceTransformer(Config.EMBEDDING_MODEL_NAME)
|
|
@@ -76,14 +76,15 @@ try:
|
|
| 76 |
except Exception as e:
|
| 77 |
logging.error(f"FALLO CRÍTICO: No se pudo cargar el modelo de embeddings: {e}")
|
| 78 |
|
| 79 |
-
# --- Firebase (Memoria a Largo Plazo) ---
|
| 80 |
db = None
|
| 81 |
try:
|
| 82 |
if not firebase_admin._apps:
|
| 83 |
-
# Reemplaza esta lógica por tu método preferido para cargar secretos
|
| 84 |
firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON')
|
| 85 |
if firebase_credentials_json:
|
| 86 |
cred_dict = json.loads(firebase_credentials_json)
|
|
|
|
|
|
|
|
|
|
| 87 |
cred = credentials.Certificate(cred_dict)
|
| 88 |
firebase_admin.initialize_app(cred, {'projectId': cred_dict['project_id']})
|
| 89 |
db = firestore.client()
|
|
@@ -96,7 +97,6 @@ try:
|
|
| 96 |
except Exception as e:
|
| 97 |
logging.error(f"FALLO CRÍTICO: Error al inicializar Firebase: {e}")
|
| 98 |
|
| 99 |
-
|
| 100 |
# ==============================================================================
|
| 101 |
# MÓDULO 3: MODELOS DE DATOS (LA ESENCIA DEL USUARIO)
|
| 102 |
# ==============================================================================
|
|
@@ -107,12 +107,10 @@ class User:
|
|
| 107 |
self.created_at: datetime = kwargs.get('created_at', datetime.now())
|
| 108 |
self.last_login: datetime = kwargs.get('last_login', datetime.now())
|
| 109 |
self.psych_profile: Dict[str, float] = kwargs.get('psych_profile', Config.DEFAULT_PSYCH_PROFILE.copy())
|
| 110 |
-
# El memory_stream ahora es el log persistente de todas las interacciones.
|
| 111 |
self.memory_stream: List[Dict[str, Any]] = kwargs.get('memory_stream', [])
|
| 112 |
self.connection_points: int = kwargs.get('connection_points', 0)
|
| 113 |
|
| 114 |
def to_dict(self) -> Dict[str, Any]:
|
| 115 |
-
# Convierte todo a formatos serializables para Firebase
|
| 116 |
return {
|
| 117 |
"user_id": self.user_id, "name": self.name,
|
| 118 |
"created_at": self.created_at.isoformat(),
|
|
@@ -156,16 +154,20 @@ class UserManager:
|
|
| 156 |
@staticmethod
|
| 157 |
async def create_user(name: str) -> Tuple[Optional[User], str]:
|
| 158 |
if not db: return None, "Error de base de datos."
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
@staticmethod
|
| 171 |
async def save_user(user: User) -> bool:
|
|
@@ -187,48 +189,36 @@ class CognitiveCore:
|
|
| 187 |
self.sentiment_analyzer = s_analyzer
|
| 188 |
self.embedding_model = e_model
|
| 189 |
|
| 190 |
-
# Inicialización del cerebro semántico (ChromaDB)
|
| 191 |
self.chroma_client = chromadb.Client(Settings(
|
| 192 |
persist_directory=Config.CHROMA_PERSIST_PATH,
|
| 193 |
is_persistent=True,
|
| 194 |
))
|
| 195 |
-
# Cada usuario tiene su propia "área" en el cerebro
|
| 196 |
self.memory_collection = self.chroma_client.get_or_create_collection(
|
| 197 |
-
name=f"prometeo_mind_{self.user.user_id}"
|
| 198 |
)
|
| 199 |
self._sync_semantic_memory()
|
| 200 |
|
| 201 |
def _sync_semantic_memory(self):
|
| 202 |
-
|
| 203 |
-
if not self.user.memory_stream:
|
| 204 |
-
return
|
| 205 |
-
|
| 206 |
logging.info("Sincronizando memoria persistente con el núcleo semántico...")
|
| 207 |
ids = [m['id'] for m in self.user.memory_stream]
|
| 208 |
documents = [m['content'] for m in self.user.memory_stream]
|
| 209 |
|
| 210 |
-
if ids:
|
| 211 |
-
# Usamos `upsert` para añadir o actualizar, evitando duplicados
|
| 212 |
self.memory_collection.upsert(ids=ids, documents=documents)
|
| 213 |
-
logging.info(f"Sincronización completa. {
|
| 214 |
|
| 215 |
async def _simulate_llm_reasoning(self, prompt: str) -> str:
|
| 216 |
-
"""
|
| 217 |
-
SIMULADOR DE LLM AVANZADO (RAG).
|
| 218 |
-
En una implementación real, esto sería una llamada a la API de HuggingFace,
|
| 219 |
-
OpenAI, o un modelo local con Ollama.
|
| 220 |
-
"""
|
| 221 |
logging.info("Iniciando ciclo de razonamiento profundo...")
|
| 222 |
-
await asyncio.sleep(random.uniform(0.
|
| 223 |
|
| 224 |
-
# Extraemos el input del usuario y el contexto para la simulación
|
| 225 |
user_input_match = re.search(r'El usuario ha dicho: "([^"]+)"', prompt)
|
| 226 |
user_input = user_input_match.group(1) if user_input_match else "algo"
|
| 227 |
|
| 228 |
context_match = re.search(r'Memorias pasadas relevantes:\n(.*?)\n\n', prompt, re.DOTALL)
|
| 229 |
context = context_match.group(1) if context_match else ""
|
| 230 |
|
| 231 |
-
if context:
|
| 232 |
first_memory = context.split('\n')[0].replace('- ', '')
|
| 233 |
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?"
|
| 234 |
else:
|
|
@@ -237,67 +227,43 @@ class CognitiveCore:
|
|
| 237 |
logging.info("Ciclo de razonamiento completado.")
|
| 238 |
return response
|
| 239 |
|
| 240 |
-
# --- CICLO O-R-A: OBSERVAR, REFLEXIONAR, ACTUAR ---
|
| 241 |
-
|
| 242 |
async def observe(self, text: str, type: str):
|
| 243 |
-
"""Paso 1: Añadir una nueva memoria al stream y al cerebro semántico."""
|
| 244 |
timestamp = datetime.now()
|
| 245 |
memory_id = f"{type}_{int(timestamp.timestamp() * 1000)}"
|
| 246 |
-
|
| 247 |
-
new_memory = {
|
| 248 |
-
"id": memory_id,
|
| 249 |
-
"type": type,
|
| 250 |
-
"content": text,
|
| 251 |
-
"timestamp": timestamp.isoformat()
|
| 252 |
-
}
|
| 253 |
|
| 254 |
self.user.memory_stream.append(new_memory)
|
| 255 |
if len(self.user.memory_stream) > Config.MAX_MEMORY_STREAM_ITEMS:
|
| 256 |
-
# FIFO: Remove the oldest memory if we exceed the limit
|
| 257 |
self.user.memory_stream.pop(0)
|
| 258 |
|
| 259 |
-
# Añadimos la nueva memoria al cerebro semántico en tiempo real
|
| 260 |
self.memory_collection.add(documents=[text], ids=[memory_id])
|
| 261 |
|
| 262 |
async def reflect(self, current_input: str) -> str:
|
| 263 |
-
"""Paso 2: Recuperar memorias y usar el LLM para generar un insight profundo."""
|
| 264 |
logging.info(f"Reflexionando sobre: '{current_input}'")
|
| 265 |
|
| 266 |
-
|
| 267 |
-
relevant_memories = self.memory_collection.query(
|
| 268 |
-
query_texts=[current_input],
|
| 269 |
-
n_results=3 # Pedimos 3 memorias más relevantes
|
| 270 |
-
)
|
| 271 |
|
| 272 |
context = "Memorias pasadas relevantes:\n"
|
| 273 |
-
if relevant_memories and relevant_memories['documents']:
|
| 274 |
for doc in relevant_memories['documents'][0]:
|
| 275 |
context += f"- {doc}\n"
|
| 276 |
else:
|
| 277 |
context += "Ninguna.\n"
|
| 278 |
|
| 279 |
-
# 2. Construir el prompt para el motor de razonamiento (LLM)
|
| 280 |
prompt = f"""
|
| 281 |
INSTRUCCIONES DE SISTEMA:
|
| 282 |
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.
|
| 283 |
-
|
| 284 |
CONTEXTO:
|
| 285 |
{context}
|
| 286 |
-
|
| 287 |
NUEVO INPUT:
|
| 288 |
El usuario ha dicho: "{current_input}"
|
| 289 |
-
|
| 290 |
TAREA:
|
| 291 |
Genera una respuesta que conecte ideas, cuestione suposiciones o identifique patrones ocultos basados en el nuevo input y el contexto de memorias pasadas.
|
| 292 |
"""
|
| 293 |
-
|
| 294 |
-
# 3. Generar el insight (usando nuestro simulador de LLM)
|
| 295 |
insight = await self._simulate_llm_reasoning(prompt)
|
| 296 |
return insight
|
| 297 |
|
| 298 |
async def act(self, message: str) -> str:
|
| 299 |
-
"""Paso 3: El ciclo completo que genera la respuesta final."""
|
| 300 |
-
# --- Sistema Límbico (Respuestas de Reflejo Rápido) ---
|
| 301 |
message_lower = message.lower()
|
| 302 |
sentiment = await asyncio.to_thread(self.sentiment_analyzer.predict, message)
|
| 303 |
|
|
@@ -314,14 +280,9 @@ class CognitiveCore:
|
|
| 314 |
await self.observe(f"Mi respuesta sobre el propósito: {response}", type="system_response")
|
| 315 |
return response
|
| 316 |
|
| 317 |
-
# --- Córtex Prefrontal (Razonamiento Profundo) ---
|
| 318 |
await self.observe(f"Usuario: {message}", type="user_input")
|
| 319 |
-
|
| 320 |
-
# Si no es un reflejo, activamos la cognición superior
|
| 321 |
response = await self.reflect(message)
|
| 322 |
-
|
| 323 |
await self.observe(f"Prometeo: {response}", type="system_response")
|
| 324 |
-
|
| 325 |
return response
|
| 326 |
|
| 327 |
# ==============================================================================
|
|
@@ -342,7 +303,6 @@ async def handle_login_or_creation(action: str, name: str, user_id: str) -> tupl
|
|
| 342 |
if not user:
|
| 343 |
msg = "ID de usuario no encontrado. Verifique o cree un nuevo perfil."
|
| 344 |
else:
|
| 345 |
-
# Aquí no necesitamos el saludo, se genera al cargar el chat
|
| 346 |
msg = f"Protocolo Prometeo activado para {user.name}."
|
| 347 |
|
| 348 |
if user:
|
|
@@ -359,37 +319,25 @@ async def handle_chat_message(user_state: User, message: str, chat_history: List
|
|
| 359 |
gr.Warning("Sistema inactivo. Inicie sesión o cree un perfil para continuar.")
|
| 360 |
return user_state, chat_history, "", gr.update()
|
| 361 |
|
| 362 |
-
# Añadimos el mensaje del usuario a la UI inmediatamente
|
| 363 |
chat_history.append({"role": "user", "content": message})
|
| 364 |
|
| 365 |
-
# Creamos una instancia del núcleo cognitivo con el estado actual del usuario
|
| 366 |
core = CognitiveCore(user_state, sentiment_analyzer, embedding_model)
|
| 367 |
-
|
| 368 |
-
# El núcleo se encarga de todo el ciclo de pensamiento
|
| 369 |
response = await core.act(message)
|
| 370 |
-
|
| 371 |
-
# Guardamos el estado del usuario DESPUÉS de la interacción completa
|
| 372 |
-
# El core.user contiene el memory_stream actualizado
|
| 373 |
await UserManager.save_user(core.user)
|
| 374 |
|
| 375 |
-
# Añadimos la respuesta del AI a la UI
|
| 376 |
chat_history.append({"role": "assistant", "content": response})
|
| 377 |
-
|
| 378 |
-
# Actualizamos el perfil en la UI con los nuevos datos (si hubiera)
|
| 379 |
profile_update = render_profile_info(core.user)
|
| 380 |
|
| 381 |
return core.user, chat_history, "", profile_update
|
| 382 |
|
| 383 |
def render_profile_info(user: Optional[User]) -> str:
|
| 384 |
-
"""Renderiza el panel de perfil en Markdown."""
|
| 385 |
if not user: return "Ningún perfil cargado."
|
| 386 |
profile_md = f"### Perfil Cognitivo: {user.name}\n"
|
| 387 |
profile_md += f"**ID de Acceso:** `{user.user_id}`\n"
|
| 388 |
profile_md += f"**Memorias Registradas:** {len(user.memory_stream)}\n\n"
|
| 389 |
profile_md += "#### Modelo Psicométrico Inferido:\n"
|
| 390 |
for trait, value in user.psych_profile.items():
|
| 391 |
-
|
| 392 |
-
bar_value = int((value + 1) * 5) # Escala de 0 a 10
|
| 393 |
bar = "█" * bar_value + "░" * (10 - bar_value)
|
| 394 |
profile_md += f"- **{trait.capitalize()}:** `{f'{value:.2f}'}` {bar}\n"
|
| 395 |
return profile_md
|
|
@@ -405,14 +353,15 @@ with gr.Blocks(theme=gr.themes.Monochrome(font=[gr.themes.GoogleFont("Roboto Mon
|
|
| 405 |
|
| 406 |
with gr.Row():
|
| 407 |
with gr.Column(scale=3):
|
| 408 |
-
# Panel de Chat (se muestra después del login)
|
| 409 |
with gr.Group(visible=False) as chat_panel:
|
| 410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
with gr.Row():
|
| 412 |
chat_input = gr.Textbox(show_label=False, placeholder="Input...", scale=5, container=False)
|
| 413 |
send_button = gr.Button("Ejecutar", variant="primary", scale=1)
|
| 414 |
-
|
| 415 |
-
# Panel de Login (se muestra al inicio)
|
| 416 |
with gr.Group(visible=True) as login_panel:
|
| 417 |
gr.Markdown("### **Acceso al Protocolo**")
|
| 418 |
with gr.Tabs():
|
|
@@ -428,31 +377,10 @@ with gr.Blocks(theme=gr.themes.Monochrome(font=[gr.themes.GoogleFont("Roboto Mon
|
|
| 428 |
gr.Markdown("### **Estado del Núcleo**")
|
| 429 |
profile_display = gr.Markdown("Ningún perfil cargado.", elem_id="profile-display")
|
| 430 |
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
fn=handle_login_or_creation,
|
| 436 |
-
inputs=[gr.State("login"), username_input, userid_input],
|
| 437 |
-
outputs=[current_user_state, chatbot_display, login_panel, chat_panel, profile_display]
|
| 438 |
-
)
|
| 439 |
-
create_button.click(
|
| 440 |
-
fn=handle_login_or_creation,
|
| 441 |
-
inputs=[gr.State("create"), username_input, userid_input],
|
| 442 |
-
outputs=[current_user_state, chatbot_display, login_panel, chat_panel, profile_display]
|
| 443 |
-
)
|
| 444 |
-
|
| 445 |
-
# Acciones de Chat
|
| 446 |
-
chat_input.submit(
|
| 447 |
-
fn=handle_chat_message,
|
| 448 |
-
inputs=[current_user_state, chat_input, chatbot_display],
|
| 449 |
-
outputs=[current_user_state, chatbot_display, chat_input, profile_display]
|
| 450 |
-
)
|
| 451 |
-
send_button.click(
|
| 452 |
-
fn=handle_chat_message,
|
| 453 |
-
inputs=[current_user_state, chat_input, chatbot_display],
|
| 454 |
-
outputs=[current_user_state, chatbot_display, chat_input, profile_display]
|
| 455 |
-
)
|
| 456 |
|
| 457 |
if __name__ == "__main__":
|
| 458 |
if not all([db, sentiment_analyzer, embedding_model]):
|
|
|
|
| 1 |
+
# manifest.py - Protocolo Prometeo v0.1.1: Núcleo Consciente (Interfaz Recalibrada)
|
| 2 |
# Arquitectura por un asistente de IA, inspirado en la visión de un líder que fusiona a Altman, Jobs y Musk.
|
| 3 |
# "No estamos construyendo una app. Estamos construyendo el próximo modo de existencia cognitiva."
|
| 4 |
#
|
|
|
|
| 13 |
import os
|
| 14 |
import asyncio
|
| 15 |
import logging
|
| 16 |
+
import re
|
| 17 |
from typing import Dict, Any, List, Optional, Tuple
|
| 18 |
|
| 19 |
+
from dotenv import load_dotenv
|
| 20 |
+
|
| 21 |
# ==============================================================================
|
| 22 |
# MÓDULO DE IMPORTACIONES DE IA Y BBDD VECTORIAL
|
| 23 |
# ==============================================================================
|
|
|
|
| 34 |
import firebase_admin
|
| 35 |
from firebase_admin import credentials, firestore
|
| 36 |
|
| 37 |
+
# --- Cargar secretos del entorno ---
|
| 38 |
+
load_dotenv()
|
| 39 |
+
|
| 40 |
# ==============================================================================
|
| 41 |
# MÓDULO 1: CONFIGURACIÓN Y CONSTANTES DEL SISTEMA
|
| 42 |
# ==============================================================================
|
| 43 |
class Config:
|
| 44 |
+
APP_NAME = "Protocolo Prometeo v0.1.1"
|
| 45 |
+
APP_VERSION = "0.1.1 (Interfaz Recalibrada)"
|
| 46 |
|
|
|
|
| 47 |
FIREBASE_COLLECTION_USERS = "prometeo_users_v1"
|
|
|
|
|
|
|
| 48 |
CHROMA_PERSIST_PATH = "./prometeo_memory_db"
|
| 49 |
+
EMBEDDING_MODEL_NAME = 'all-MiniLM-L6-v2'
|
| 50 |
|
|
|
|
| 51 |
DEFAULT_PSYCH_PROFILE = {
|
| 52 |
"openness": 0.0, "conscientiousness": 0.0, "extraversion": 0.0,
|
| 53 |
"agreeableness": 0.0, "neuroticism": 0.0
|
| 54 |
}
|
| 55 |
POINTS_PER_INSIGHT = 10
|
| 56 |
+
MAX_MEMORY_STREAM_ITEMS = 500
|
| 57 |
FRUSTRATION_KEYWORDS = ['tonto', 'inútil', 'bruto', 'estúpido', 'mierda', 'carajo', 'dale boludo', 'no servis']
|
| 58 |
META_QUESTION_KEYWORDS = ['para qué', 'de qué te sirve', 'por qué preguntas', 'cuál es el punto']
|
| 59 |
|
|
|
|
| 62 |
# ==============================================================================
|
| 63 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
| 64 |
|
|
|
|
| 65 |
sentiment_analyzer = None
|
| 66 |
try:
|
| 67 |
sentiment_analyzer = create_analyzer(task="sentiment", lang="es")
|
|
|
|
| 69 |
except Exception as e:
|
| 70 |
logging.error(f"FALLO CRÍTICO: No se pudo cargar el analizador de sentimiento: {e}")
|
| 71 |
|
|
|
|
| 72 |
embedding_model = None
|
| 73 |
try:
|
| 74 |
embedding_model = SentenceTransformer(Config.EMBEDDING_MODEL_NAME)
|
|
|
|
| 76 |
except Exception as e:
|
| 77 |
logging.error(f"FALLO CRÍTICO: No se pudo cargar el modelo de embeddings: {e}")
|
| 78 |
|
|
|
|
| 79 |
db = None
|
| 80 |
try:
|
| 81 |
if not firebase_admin._apps:
|
|
|
|
| 82 |
firebase_credentials_json = os.getenv('GOOGLE_APPLICATION_CREDENTIALS_JSON')
|
| 83 |
if firebase_credentials_json:
|
| 84 |
cred_dict = json.loads(firebase_credentials_json)
|
| 85 |
+
# Asegurarse de que el project_id está presente, es un error común en la carga de env vars
|
| 86 |
+
if 'project_id' not in cred_dict:
|
| 87 |
+
raise ValueError("El 'project_id' no se encuentra en las credenciales de Firebase. Verifique el contenido de la variable de entorno.")
|
| 88 |
cred = credentials.Certificate(cred_dict)
|
| 89 |
firebase_admin.initialize_app(cred, {'projectId': cred_dict['project_id']})
|
| 90 |
db = firestore.client()
|
|
|
|
| 97 |
except Exception as e:
|
| 98 |
logging.error(f"FALLO CRÍTICO: Error al inicializar Firebase: {e}")
|
| 99 |
|
|
|
|
| 100 |
# ==============================================================================
|
| 101 |
# MÓDULO 3: MODELOS DE DATOS (LA ESENCIA DEL USUARIO)
|
| 102 |
# ==============================================================================
|
|
|
|
| 107 |
self.created_at: datetime = kwargs.get('created_at', datetime.now())
|
| 108 |
self.last_login: datetime = kwargs.get('last_login', datetime.now())
|
| 109 |
self.psych_profile: Dict[str, float] = kwargs.get('psych_profile', Config.DEFAULT_PSYCH_PROFILE.copy())
|
|
|
|
| 110 |
self.memory_stream: List[Dict[str, Any]] = kwargs.get('memory_stream', [])
|
| 111 |
self.connection_points: int = kwargs.get('connection_points', 0)
|
| 112 |
|
| 113 |
def to_dict(self) -> Dict[str, Any]:
|
|
|
|
| 114 |
return {
|
| 115 |
"user_id": self.user_id, "name": self.name,
|
| 116 |
"created_at": self.created_at.isoformat(),
|
|
|
|
| 154 |
@staticmethod
|
| 155 |
async def create_user(name: str) -> Tuple[Optional[User], str]:
|
| 156 |
if not db: return None, "Error de base de datos."
|
| 157 |
+
try:
|
| 158 |
+
user_id = f"{name.lower().replace(' ', '_')}_{int(time.time())}"
|
| 159 |
+
new_user = User(user_id=user_id, name=name)
|
| 160 |
+
success = await UserManager.save_user(new_user)
|
| 161 |
+
if success:
|
| 162 |
+
msg = f"¡Bienvenido, {name}! Tu perfil ha sido forjado. Tu ID de acceso es: **{user_id}**"
|
| 163 |
+
logging.info(f"Nuevo usuario creado: {name} ({user_id})")
|
| 164 |
+
return new_user, msg
|
| 165 |
+
else:
|
| 166 |
+
return None, "Error inesperado al crear perfil en la base de datos."
|
| 167 |
+
except Exception as e:
|
| 168 |
+
logging.error(f"Error al crear usuario {name}: {e}")
|
| 169 |
+
return None, "Fallo catastrófico durante la creación del perfil."
|
| 170 |
+
|
| 171 |
|
| 172 |
@staticmethod
|
| 173 |
async def save_user(user: User) -> bool:
|
|
|
|
| 189 |
self.sentiment_analyzer = s_analyzer
|
| 190 |
self.embedding_model = e_model
|
| 191 |
|
|
|
|
| 192 |
self.chroma_client = chromadb.Client(Settings(
|
| 193 |
persist_directory=Config.CHROMA_PERSIST_PATH,
|
| 194 |
is_persistent=True,
|
| 195 |
))
|
|
|
|
| 196 |
self.memory_collection = self.chroma_client.get_or_create_collection(
|
| 197 |
+
name=f"prometeo_mind_{self.user.user_id.replace('_', '-')}" # Sanitize name for chromadb
|
| 198 |
)
|
| 199 |
self._sync_semantic_memory()
|
| 200 |
|
| 201 |
def _sync_semantic_memory(self):
|
| 202 |
+
if not self.user.memory_stream: return
|
|
|
|
|
|
|
|
|
|
| 203 |
logging.info("Sincronizando memoria persistente con el núcleo semántico...")
|
| 204 |
ids = [m['id'] for m in self.user.memory_stream]
|
| 205 |
documents = [m['content'] for m in self.user.memory_stream]
|
| 206 |
|
| 207 |
+
if ids and self.memory_collection.count() < len(ids):
|
|
|
|
| 208 |
self.memory_collection.upsert(ids=ids, documents=documents)
|
| 209 |
+
logging.info(f"Sincronización completa. {self.memory_collection.count()} memorias en el núcleo.")
|
| 210 |
|
| 211 |
async def _simulate_llm_reasoning(self, prompt: str) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
logging.info("Iniciando ciclo de razonamiento profundo...")
|
| 213 |
+
await asyncio.sleep(random.uniform(0.5, 1.0))
|
| 214 |
|
|
|
|
| 215 |
user_input_match = re.search(r'El usuario ha dicho: "([^"]+)"', prompt)
|
| 216 |
user_input = user_input_match.group(1) if user_input_match else "algo"
|
| 217 |
|
| 218 |
context_match = re.search(r'Memorias pasadas relevantes:\n(.*?)\n\n', prompt, re.DOTALL)
|
| 219 |
context = context_match.group(1) if context_match else ""
|
| 220 |
|
| 221 |
+
if context.strip() and "Ninguna" not in context:
|
| 222 |
first_memory = context.split('\n')[0].replace('- ', '')
|
| 223 |
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?"
|
| 224 |
else:
|
|
|
|
| 227 |
logging.info("Ciclo de razonamiento completado.")
|
| 228 |
return response
|
| 229 |
|
|
|
|
|
|
|
| 230 |
async def observe(self, text: str, type: str):
|
|
|
|
| 231 |
timestamp = datetime.now()
|
| 232 |
memory_id = f"{type}_{int(timestamp.timestamp() * 1000)}"
|
| 233 |
+
new_memory = {"id": memory_id, "type": type, "content": text, "timestamp": timestamp.isoformat()}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
|
| 235 |
self.user.memory_stream.append(new_memory)
|
| 236 |
if len(self.user.memory_stream) > Config.MAX_MEMORY_STREAM_ITEMS:
|
|
|
|
| 237 |
self.user.memory_stream.pop(0)
|
| 238 |
|
|
|
|
| 239 |
self.memory_collection.add(documents=[text], ids=[memory_id])
|
| 240 |
|
| 241 |
async def reflect(self, current_input: str) -> str:
|
|
|
|
| 242 |
logging.info(f"Reflexionando sobre: '{current_input}'")
|
| 243 |
|
| 244 |
+
relevant_memories = self.memory_collection.query(query_texts=[current_input], n_results=3)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
|
| 246 |
context = "Memorias pasadas relevantes:\n"
|
| 247 |
+
if relevant_memories and relevant_memories['documents'] and relevant_memories['documents'][0]:
|
| 248 |
for doc in relevant_memories['documents'][0]:
|
| 249 |
context += f"- {doc}\n"
|
| 250 |
else:
|
| 251 |
context += "Ninguna.\n"
|
| 252 |
|
|
|
|
| 253 |
prompt = f"""
|
| 254 |
INSTRUCCIONES DE SISTEMA:
|
| 255 |
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.
|
|
|
|
| 256 |
CONTEXTO:
|
| 257 |
{context}
|
|
|
|
| 258 |
NUEVO INPUT:
|
| 259 |
El usuario ha dicho: "{current_input}"
|
|
|
|
| 260 |
TAREA:
|
| 261 |
Genera una respuesta que conecte ideas, cuestione suposiciones o identifique patrones ocultos basados en el nuevo input y el contexto de memorias pasadas.
|
| 262 |
"""
|
|
|
|
|
|
|
| 263 |
insight = await self._simulate_llm_reasoning(prompt)
|
| 264 |
return insight
|
| 265 |
|
| 266 |
async def act(self, message: str) -> str:
|
|
|
|
|
|
|
| 267 |
message_lower = message.lower()
|
| 268 |
sentiment = await asyncio.to_thread(self.sentiment_analyzer.predict, message)
|
| 269 |
|
|
|
|
| 280 |
await self.observe(f"Mi respuesta sobre el propósito: {response}", type="system_response")
|
| 281 |
return response
|
| 282 |
|
|
|
|
| 283 |
await self.observe(f"Usuario: {message}", type="user_input")
|
|
|
|
|
|
|
| 284 |
response = await self.reflect(message)
|
|
|
|
| 285 |
await self.observe(f"Prometeo: {response}", type="system_response")
|
|
|
|
| 286 |
return response
|
| 287 |
|
| 288 |
# ==============================================================================
|
|
|
|
| 303 |
if not user:
|
| 304 |
msg = "ID de usuario no encontrado. Verifique o cree un nuevo perfil."
|
| 305 |
else:
|
|
|
|
| 306 |
msg = f"Protocolo Prometeo activado para {user.name}."
|
| 307 |
|
| 308 |
if user:
|
|
|
|
| 319 |
gr.Warning("Sistema inactivo. Inicie sesión o cree un perfil para continuar.")
|
| 320 |
return user_state, chat_history, "", gr.update()
|
| 321 |
|
|
|
|
| 322 |
chat_history.append({"role": "user", "content": message})
|
| 323 |
|
|
|
|
| 324 |
core = CognitiveCore(user_state, sentiment_analyzer, embedding_model)
|
|
|
|
|
|
|
| 325 |
response = await core.act(message)
|
|
|
|
|
|
|
|
|
|
| 326 |
await UserManager.save_user(core.user)
|
| 327 |
|
|
|
|
| 328 |
chat_history.append({"role": "assistant", "content": response})
|
|
|
|
|
|
|
| 329 |
profile_update = render_profile_info(core.user)
|
| 330 |
|
| 331 |
return core.user, chat_history, "", profile_update
|
| 332 |
|
| 333 |
def render_profile_info(user: Optional[User]) -> str:
|
|
|
|
| 334 |
if not user: return "Ningún perfil cargado."
|
| 335 |
profile_md = f"### Perfil Cognitivo: {user.name}\n"
|
| 336 |
profile_md += f"**ID de Acceso:** `{user.user_id}`\n"
|
| 337 |
profile_md += f"**Memorias Registradas:** {len(user.memory_stream)}\n\n"
|
| 338 |
profile_md += "#### Modelo Psicométrico Inferido:\n"
|
| 339 |
for trait, value in user.psych_profile.items():
|
| 340 |
+
bar_value = int((value + 1) * 5)
|
|
|
|
| 341 |
bar = "█" * bar_value + "░" * (10 - bar_value)
|
| 342 |
profile_md += f"- **{trait.capitalize()}:** `{f'{value:.2f}'}` {bar}\n"
|
| 343 |
return profile_md
|
|
|
|
| 353 |
|
| 354 |
with gr.Row():
|
| 355 |
with gr.Column(scale=3):
|
|
|
|
| 356 |
with gr.Group(visible=False) as chat_panel:
|
| 357 |
+
# ==============================================================
|
| 358 |
+
# <<< LÍNEA CORREGIDA >>>
|
| 359 |
+
# Se añadió type="messages" para compatibilidad y se eliminó el parámetro obsoleto.
|
| 360 |
+
chatbot_display = gr.Chatbot(label="Stream de Conciencia", height=600, type="messages", show_copy_button=True, avatar_images=("./user.png", "./bot.png"))
|
| 361 |
+
# ==============================================================
|
| 362 |
with gr.Row():
|
| 363 |
chat_input = gr.Textbox(show_label=False, placeholder="Input...", scale=5, container=False)
|
| 364 |
send_button = gr.Button("Ejecutar", variant="primary", scale=1)
|
|
|
|
|
|
|
| 365 |
with gr.Group(visible=True) as login_panel:
|
| 366 |
gr.Markdown("### **Acceso al Protocolo**")
|
| 367 |
with gr.Tabs():
|
|
|
|
| 377 |
gr.Markdown("### **Estado del Núcleo**")
|
| 378 |
profile_display = gr.Markdown("Ningún perfil cargado.", elem_id="profile-display")
|
| 379 |
|
| 380 |
+
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])
|
| 381 |
+
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])
|
| 382 |
+
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])
|
| 383 |
+
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])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
|
| 385 |
if __name__ == "__main__":
|
| 386 |
if not all([db, sentiment_analyzer, embedding_model]):
|