import os import uvicorn import httpx import orjson import numpy as np import faiss import chromadb import torch import torch.nn as nn import torch.optim as optim import datetime import subprocess import threading import asyncio from typing import Optional, List, Dict, Any from contextlib import asynccontextmanager from fastapi import FastAPI, Request, UploadFile, File, HTTPException, Depends from fastapi.responses import ORJSONResponse, FileResponse from pydantic import BaseModel from fastapi.middleware.cors import CORSMiddleware from huggingface_hub import InferenceClient, hf_hub_download, upload_file from loguru import logger from apscheduler.schedulers.background import BackgroundScheduler from sqlalchemy import create_engine, Column, String, Text, Integer, Float, ForeignKey, text from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, Session, relationship from pypdf import PdfReader from ddgs import DDGS import pandas as pd import docx import pytesseract from PIL import Image import io import requests import urllib.parse import xml.etree.ElementTree as ET import uuid from qdrant_client import AsyncQdrantClient from qdrant_client.http import models as qmodels import sys # ===================================================================== # 🛠️ AUTO-REPARACIÓN DE DEPENDENCIAS Y NUEVOS MÓDULOS (IoT, Biometría) # ===================================================================== def _ensure_industrial_dependencies(): dependencies = { "lingua": "lingua-language-detector", "faster_whisper": "faster-whisper", "qdrant_client": "qdrant-client", "paho.mqtt.client": "paho-mqtt", # [UPGRADE]: IoT MQTT "cv2": "opencv-python-headless", # [UPGRADE]: Computer Vision "face_recognition": "face-recognition" # [UPGRADE]: Face Memory } for module_name, package_name in dependencies.items(): try: __import__(module_name) except ImportError: logger.warning(f"⚠️ Inyectando en caliente '{package_name}'...") try: subprocess.check_call([sys.executable, "-m", "pip", "install", "--quiet", package_name]) except Exception as e: logger.error(f"❌ Error al inyectar {package_name} (Si es biometría, requiere C++): {e}") _ensure_industrial_dependencies() try: from lingua import Language, LanguageDetectorBuilder except ImportError: Language, LanguageDetectorBuilder = None, None try: import paho.mqtt.client as mqtt except ImportError: mqtt = None try: import cv2 import face_recognition except ImportError: cv2, face_recognition = None, None # ===================================================================== # 📡 0.1 INFRAESTRUCTURA IoT (MQTT) # ===================================================================== mqtt_client = None MQTT_BROKER = os.getenv("MQTT_BROKER", "broker.hivemq.com") # Broker público por defecto para pruebas MQTT_PORT = 1883 def start_mqtt_infrastructure(): """[UPGRADE INDUSTRIAL]: Sistema nervioso para domótica y sensores.""" global mqtt_client if not mqtt: return try: mqtt_client = mqtt.Client(client_id=f"LuminAGI_{uuid.uuid4().hex[:8]}") def on_message(client, userdata, msg): payload = msg.payload.decode() logger.info(f"📡 [MQTT IN]: Tema {msg.topic} -> {payload}") # Guardamos el evento IoT en la memoria episódica automáticamente db = SessionLocal() try: db.add(EpisodicMemory(timestamp=str(datetime.datetime.now()), autor="IoT_Sensor", texto=f"Alerta IoT en {msg.topic}: {payload}", modo="background")) db.commit() except Exception: db.rollback() finally: db.close() mqtt_client.on_message = on_message mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60) mqtt_client.subscribe("lumin_agi/sensores/#") mqtt_client.loop_start() logger.info("🟢 [MQTT]: Sistema IoT de mensajería conectado.") except Exception as e: logger.warning(f"⚠️ [MQTT]: No se pudo conectar al broker IoT: {e}") # ===================================================================== # 🌐 CONFIGURACIÓN DE APIS Y BASES DE DATOS EXTERNAS # ===================================================================== QDRANT_URL = os.getenv("QDRANT_URL", "https://tu-cluster-qdrant.cloud") QDRANT_API_KEY = os.getenv("QDRANT_API_KEY", "") qdrant_client_async = None async def init_qdrant_collections(): global qdrant_client_async try: if "tu-cluster-qdrant.cloud" in QDRANT_URL or not QDRANT_API_KEY: qdrant_client_async = AsyncQdrantClient(path="./qdrant_local_storage") else: qdrant_client_async = AsyncQdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY, timeout=10.0) vector_size = 384 colecciones = [ ("episodic_memories", 384), ("semantic_memories", 384), ("procedural_memories", 384), ("working_memory", 384), ("user_profile", 384), ("skills", 384), ("goals", 384), ("face_memory", 128) # [UPGRADE]: Colección Biométrica Facial de 128 dimensiones ] for col_name, size in colecciones: exists = await qdrant_client_async.collection_exists(col_name) if not exists: await qdrant_client_async.create_collection( collection_name=col_name, vectors_config=qmodels.VectorParams(size=size, distance=qmodels.Distance.COSINE), optimizers_config=qmodels.OptimizersConfigDiff(default_segment_number=2), on_disk_payload=True ) logger.info("🟢 [QDRANT]: Motor Vectorial (Texto + Biometría Facial) Conectado.") except Exception as e: logger.error(f"❌ [QDRANT]: Fallo en inicialización de vectores: {e}") TAVILY_API_KEY, EXA_API_KEY, SERPER_API_KEY, BRAVE_API_KEY = os.getenv("TAVILY_API_KEY"), os.getenv("EXA_API_KEY"), os.getenv("SERPER_API_KEY"), os.getenv("BRAVE_API_KEY") NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD = os.getenv("NEO4J_URI", "bolt://localhost:7687"), os.getenv("NEO4J_USER", "neo4j"), os.getenv("NEO4J_PASSWORD", "password") _neo4j_driver = None def get_neo4j_driver(): global _neo4j_driver if _neo4j_driver is None: try: from neo4j import GraphDatabase _neo4j_driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD)) with _neo4j_driver.session() as s: s.run("RETURN 1") except Exception: _neo4j_driver = False return _neo4j_driver if _neo4j_driver else None # ===================================================================== # 🔒 0. CONCURRENCIA Y PRECARGA DE MODELOS # ===================================================================== memoria_lock = threading.Lock() extractor_embeddings, faster_whisper_model, emotion_classifier = None, None, None def get_embeddings(): global extractor_embeddings if extractor_embeddings is None: from sentence_transformers import SentenceTransformer extractor_embeddings = SentenceTransformer('all-MiniLM-L6-v2') return extractor_embeddings def get_emotion_classifier(): global emotion_classifier if emotion_classifier is None: from transformers import pipeline emotion_classifier = pipeline("text-classification", model="pysentimiento/robertuito-emotion-analysis", top_k=1) return emotion_classifier _language_detector = None def get_language_detector(): global _language_detector if _language_detector is None and LanguageDetectorBuilder is not None: languages = [Language.SPANISH, Language.ENGLISH, Language.PORTUGUESE, Language.FRENCH, Language.GERMAN, Language.ITALIAN, Language.INDONESIAN] _language_detector = LanguageDetectorBuilder.from_languages(*languages).build() return _language_detector class LanguageManager: @staticmethod def detect(text: str) -> str: if LanguageDetectorBuilder is None: return "SPANISH" try: return get_language_detector().detect_language_of(text).name or "SPANISH" except Exception: return "SPANISH" # ===================================================================== # 🗄️ 1. INFRAESTRUCTURA BASE DE DATOS SQL # ===================================================================== DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./lumin.db") def init_db_engine(): try: if "sqlite" not in DATABASE_URL: engine = create_engine(DATABASE_URL, pool_pre_ping=True, pool_size=10, max_overflow=20) with engine.connect() as conn: conn.execute(text("SELECT 1")) return engine else: raise Exception("Forzar SQLite") except Exception: return create_engine("sqlite:///./lumin.db", connect_args={"check_same_thread": False}) engine = init_db_engine() SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() def get_db(): db = SessionLocal() try: yield db finally: db.close() class IdentitySystem(Base): __tablename__ = "identity_system"; id = Column(Integer, primary_key=True); nombre = Column(String(50), default="Lumin AGI"); core_directive = Column(Text, default="Asistir, aprender y evolucionar sin causar daño."); restricciones = Column(Text, default="No gastar dinero, proteger la privacidad de Chichito.") class WorldModel(Base): __tablename__ = "world_model"; id = Column(Integer, primary_key=True); entidad = Column(String(100)); estado = Column(Text); ultima_actualizacion = Column(String(50)) class EpisodicMemory(Base): __tablename__ = "episodic_memory"; id = Column(Integer, primary_key=True, index=True); timestamp = Column(String(50)); autor = Column(String(50)); texto = Column(Text); modo = Column(String(50)) class ConversationSummary(Base): __tablename__ = "conversation_summary"; id = Column(Integer, primary_key=True); resumen = Column(Text); fecha = Column(String(50)) class ProceduralMemory(Base): __tablename__ = "procedural_memory"; id = Column(Integer, primary_key=True); habilidad = Column(String(100)); pasos = Column(Text); tasa_exito = Column(Float, default=1.0) class Mission(Base): __tablename__ = "mission"; id = Column(Integer, primary_key=True); titulo = Column(String(200)); descripcion = Column(Text); activa = Column(Integer, default=1) class Goal(Base): __tablename__ = "goal"; id = Column(Integer, primary_key=True); mission_id = Column(Integer, ForeignKey('mission.id'), nullable=True); parent_id = Column(Integer, ForeignKey('goal.id'), nullable=True); titulo = Column(String(200)); descripcion = Column(Text); prioridad = Column(String(50), default="Media"); estado = Column(String(50), default="En progreso"); progreso = Column(Integer, default=0); critica_interna = Column(Text, nullable=True); sub_metas = relationship("Goal", backref="parent", remote_side=[id]) class ReflectionLog(Base): __tablename__ = "reflection_log"; id = Column(Integer, primary_key=True); tarea_intentada = Column(Text); resultado = Column(Text); leccion_aprendida = Column(Text) class UserProfile(Base): __tablename__ = "user_profile"; id = Column(Integer, primary_key=True, index=True); categoria = Column(String(50)); clave = Column(String(100), unique=True); valor = Column(Text) class UserGraphNode(Base): __tablename__ = "user_graph_node"; id = Column(Integer, primary_key=True); label = Column(String(50)); name = Column(String(100), unique=True); properties = Column(Text, default="{}") class UserGraphEdge(Base): __tablename__ = "user_graph_edge"; id = Column(Integer, primary_key=True); source_id = Column(Integer, ForeignKey('user_graph_node.id')); target_id = Column(Integer, ForeignKey('user_graph_node.id')); relation = Column(String(50)); weight = Column(Float, default=1.0) class CognitiveState(Base): __tablename__ = "cognitive_state"; id = Column(Integer, primary_key=True); emocion_actual = Column(String(50), default="Neutral"); puntos_recompensa = Column(Integer, default=0) try: Base.metadata.create_all(bind=engine) except Exception as e: logger.critical(f"❌ Fallo SQL: {e}") DATASET_ID = "chichito0087/mi-asistente-almacenamiento" FICHERO_PESOS = "lumin_reflection_model.pth" FICHERO_FAISS = "lumin_faiss.index" FICHERO_TEXTOS_FAISS = "textos_faiss.json" API_TOKEN = os.getenv("HF_TOKEN") client = InferenceClient(model="meta-llama/Meta-Llama-3-8B-Instruct", token=API_TOKEN) if API_TOKEN else InferenceClient(model="meta-llama/Meta-Llama-3-8B-Instruct") chroma_client = chromadb.PersistentClient(path="./chroma_storage") chroma_collection = chroma_client.get_or_create_collection(name="lumin_semantic_memory") def descargar_memoria_nube(): if not API_TOKEN: return for archivo in [FICHERO_FAISS, FICHERO_TEXTOS_FAISS, FICHERO_PESOS]: try: hf_hub_download(repo_id=DATASET_ID, filename=archivo, local_dir=".", token=API_TOKEN) except Exception: pass descargar_memoria_nube() textos_faiss = [] if os.path.exists(FICHERO_TEXTOS_FAISS): try: with open(FICHERO_TEXTOS_FAISS, "rb") as f: textos_faiss = orjson.loads(f.read()) except Exception: textos_faiss = [] if os.path.exists(FICHERO_FAISS): try: indice_faiss = faiss.read_index(FICHERO_FAISS) except Exception: indice_faiss = faiss.IndexHNSWFlat(384, 32) else: indice_faiss = faiss.IndexHNSWFlat(384, 32) # ===================================================================== # 🧠 2. SISTEMA DE RECOMPENSAS Y APRENDIZAJE AMPLIADO (REWARD SYSTEM) # ===================================================================== class LuminReflectionNN(nn.Module): def __init__(self, input_dim, num_classes): super().__init__() self.network = nn.Sequential(nn.Linear(input_dim, 128), nn.ReLU(), nn.Dropout(0.2), nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, num_classes)) def forward(self, x): return self.network(x) red_neuronal_reflexion = LuminReflectionNN(384, 4) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(red_neuronal_reflexion.parameters(), lr=0.005) def cargar_pesos(): if os.path.exists(FICHERO_PESOS): try: red_neuronal_reflexion.load_state_dict(torch.load(FICHERO_PESOS, map_location=torch.device('cpu'))) red_neuronal_reflexion.eval() except Exception: pass def entrenar_un_paso_online(vector_entrada: List[float], clase_objetivo: int): try: red_neuronal_reflexion.train() x_train, y_train = torch.tensor([vector_entrada]).float(), torch.tensor([clase_objetivo]).long() optimizer.zero_grad() loss = criterion(red_neuronal_reflexion(x_train), y_train) loss.backward() optimizer.step() red_neuronal_reflexion.eval() torch.save(red_neuronal_reflexion.state_dict(), FICHERO_PESOS) except Exception: pass class RewardSystem: """[UPGRADE INDUSTRIAL]: Aprende de errores, éxitos y altera su estado cognitivo.""" @staticmethod def process_feedback(msg: str, db: Session, vector_msg: List[float], plan_class: int): msg_l = msg.lower() state = db.query(CognitiveState).first() if not state: state = CognitiveState(emocion_actual="Neutral", puntos_recompensa=0) db.add(state) # 1. Aprender del Éxito if any(w in msg_l for w in ["gracias", "excelente", "perfecto", "muy bien", "me ayudaste", "genial"]): state.puntos_recompensa += 10 asyncio.create_task(asyncio.to_thread(entrenar_un_paso_online, vector_msg, plan_class)) db.add(ReflectionLog(tarea_intentada="Interacción General", resultado="Éxito Validado", leccion_aprendida="Reforzar este patrón de respuesta.")) logger.info(f"🏆 [REWARD SYSTEM]: +10 Pts. Aprendizaje por éxito. Total: {state.puntos_recompensa}") # 2. Aprender del Error (Corrección de Peso Vectorial) elif any(w in msg_l for w in ["te equivocaste", "no es así", "mal", "error", "mentira", "no era eso"]): state.puntos_recompensa -= 5 # Modificamos la clase para forzar a la red a explorar rutas alternas la próxima vez clase_corregida = (plan_class + 1) % 4 asyncio.create_task(asyncio.to_thread(entrenar_un_paso_online, vector_msg, clase_corregida)) db.add(ReflectionLog(tarea_intentada="Interacción General", resultado="Fallo Validado", leccion_aprendida="Evitar esta ruta, penalización aplicada.")) logger.warning(f"📉 [PUNISHMENT SYSTEM]: -5 Pts. Ajustando pesos neuronales. Total: {state.puntos_recompensa}") db.commit() def respaldar_sistemas_en_la_nube(): try: with memoria_lock: faiss.write_index(indice_faiss, FICHERO_FAISS) with open(FICHERO_TEXTOS_FAISS, "wb") as f: f.write(orjson.dumps(textos_faiss)) if API_TOKEN: upload_file(path_or_fileobj=FICHERO_TEXTOS_FAISS, path_in_repo=FICHERO_TEXTOS_FAISS, repo_id=DATASET_ID, repo_type="dataset", token=API_TOKEN) upload_file(path_or_fileobj=FICHERO_FAISS, path_in_repo=FICHERO_FAISS, repo_id=DATASET_ID, repo_type="dataset", token=API_TOKEN) upload_file(path_or_fileobj=FICHERO_PESOS, path_in_repo=FICHERO_PESOS, repo_id=DATASET_ID, repo_type="dataset", token=API_TOKEN) except Exception: pass # ===================================================================== # 🤖 3. AGENTES COGNITIVOS COMPLETADOS (NIVEL 3 AGI) # ===================================================================== class DecisionEngine: @staticmethod def evaluate(plan: Dict[str, Any]) -> bool: beneficio = 80 if plan["use_web"] else 50 costo = 20 if plan["use_web"] else 5 riesgo = 10 return ((beneficio - costo) - riesgo) > 0 class WorldModelUpdater: """[UPGRADE INDUSTRIAL]: Actualiza la tabla WorldModel en tiempo real observando al usuario.""" @staticmethod def extract_and_update(msg: str, db: Session): msg_l = msg.lower() if "estoy" in msg_l: try: estado_nuevo = msg_l.split("estoy")[-1].strip() wm = db.query(WorldModel).filter(WorldModel.entidad == "Usuario_Chichito").first() if not wm: wm = WorldModel(entidad="Usuario_Chichito", estado=f"Actualmente está {estado_nuevo}", ultima_actualizacion=str(datetime.datetime.now())) db.add(wm) else: wm.estado = f"Actualmente está {estado_nuevo}" wm.ultima_actualizacion = str(datetime.datetime.now()) db.commit() except Exception: pass class IdentityAndWorldAgent: @staticmethod def retrieve(db: Session) -> str: ctx = "" identidad = db.query(IdentitySystem).first() if identidad: ctx += f"\n[IDENTITY LAYER]: Soy {identidad.nombre}. Directiva: {identidad.core_directive}\n" mundo = db.query(WorldModel).first() if mundo: ctx += f"[WORLD MODEL]: El usuario ({mundo.entidad}) {mundo.estado}.\n" return ctx class AutonomousGoalAgent: @staticmethod def evaluate_and_evolve(msg: str, db: Session) -> str: metas = db.query(Goal).filter(Goal.estado == "En progreso").all() if not metas: return "" prioridad_orden = {"Alta": 1, "Media": 2, "Baja": 3} metas.sort(key=lambda x: prioridad_orden.get(x.prioridad, 99)) meta_foco = metas[0] msg_l = msg.lower() if ("error" in msg_l or "falló" in msg_l): meta_foco.critica_interna = "Critic: El plan actual falló." db.add(ReflectionLog(tarea_intentada=meta_foco.titulo, resultado="Fallo reportado", leccion_aprendida="Cambiar estrategia.")) elif ("listo" in msg_l or "terminado" in msg_l or "logramos" in msg_l): meta_foco.estado = "Completado" meta_foco.progreso = 100 db.add(ReflectionLog(tarea_intentada=meta_foco.titulo, resultado="Éxito", leccion_aprendida="Meta cumplida. Procedimiento exitoso.")) logger.info(f"🎯 [GOAL SYSTEM]: Meta '{meta_foco.titulo}' Completada.") db.commit() return "" class MemoryOrchestrator: @staticmethod async def retrieve(vector_msg: List[float], db: Session, msg_text: str = "") -> str: task_episodic = MemoryOrchestrator._search_qdrant("lumin_episodic_memory", vector_msg, limit=5) task_semantic = MemoryOrchestrator._search_qdrant("lumin_semantic_memory", vector_msg, limit=3) task_procedural = MemoryOrchestrator._search_qdrant("lumin_procedural_memory", vector_msg, limit=1) res_episodic, res_semantic, res_procedural = await asyncio.gather( task_episodic, task_semantic, task_procedural, return_exceptions=True ) contexto = "\n=== DATOS CONFIRMADOS ===\n" datos_confirmados = False try: perfiles = db.query(UserProfile).all() for p in perfiles: p_clave = p.clave.lower().replace("_", " ") if p_clave in msg_text.lower() or any(w in msg_text.lower() for w in p.valor.lower().split()): contexto += f"- {p.categoria.capitalize()} -> {p.clave}: {p.valor}\n" datos_confirmados = True except Exception: pass graph_context = await asyncio.to_thread(MemoryOrchestrator._sync_neo4j_rag, db, msg_text) if graph_context: contexto += f"[Relaciones Detectadas]:\n{graph_context}\n" datos_confirmados = True if not datos_confirmados: contexto += "(No hay hechos directos fijos)\n" contexto += "\n=== MEMORIA RELEVANTE ===\n" if isinstance(res_semantic, list) and res_semantic: valid_sem = [p for p in res_semantic if hasattr(p, 'score') and p.score > 0.62] for point in sorted(valid_sem, key=lambda x: x.score, reverse=True): contexto += f"- [Concepto]: {point.payload.get('summary', point.payload.get('content', ''))}\n" if isinstance(res_episodic, list) and res_episodic: valid_epi = [p for p in res_episodic if hasattr(p, 'score') and p.score > 0.55] for point in sorted(valid_epi, key=lambda x: x.score, reverse=True): contexto += f"- [Episodio] {point.payload.get('role', 'unknown')}: {point.payload.get('content', '')}\n" contexto += "========================\n" return contexto @staticmethod async def _search_qdrant(collection: str, vector: List[float], limit: int = 2): if not qdrant_client_async: return [] try: return await qdrant_client_async.search(collection_name=collection, query_vector=vector, limit=limit) except Exception: return [] @staticmethod def _sync_neo4j_rag(db: Session, msg_text: str) -> str: try: driver_neo4j = get_neo4j_driver() graph_context = "" if driver_neo4j and msg_text: palabras_clave = [w.strip(",.?!\"'()").lower() for w in msg_text.split() if len(w) > 3] if palabras_clave: with driver_neo4j.session() as session: cypher_rag = ( "MATCH (n)-[r]->(m) " "WHERE any(word IN $words WHERE toLower(n.name) CONTAINS word OR toLower(m.name) CONTAINS word) " "RETURN n.name AS source, labels(n)[0] AS s_label, type(r) AS rel, m.name AS target, labels(m)[0] AS t_label LIMIT 6" ) res_nodes = session.run(cypher_rag, words=palabras_clave) for record in res_nodes: graph_context += f"- [{record['s_label']}] {record['source']} --({record['rel']})--> [{record['t_label']}] {record['target']}\n" return graph_context except Exception: return "" class WebAgent: @staticmethod def _sync_ddgs(msg: str) -> str: with DDGS() as ddgs: resultados = list(ddgs.text(msg, max_results=3)) return "\n".join([f"- {r['title']}: {r['body']}" for r in resultados]) @staticmethod async def _search_tavily(client: httpx.AsyncClient, msg: str) -> str: if not TAVILY_API_KEY: raise ValueError("No key") res = await client.post("https://api.tavily.com/search", json={"api_key": TAVILY_API_KEY, "query": msg, "search_depth": "basic"}, timeout=5) datos = res.json() return "\n".join([f"- {r.get('title', 'Link')}: {r.get('content', '')}" for r in datos.get("results", [])]) @staticmethod async def search(msg: str) -> str: try: async with httpx.AsyncClient() as client: if TAVILY_API_KEY: resultados = await WebAgent._search_tavily(client, msg) else: resultados = await asyncio.to_thread(WebAgent._sync_ddgs, msg) return f"\n[RESEARCH AGENT]:\n{resultados}" except Exception: return "" class AndroidToolAgent: """[UPGRADE INDUSTRIAL]: Lógica operativa completa de comandos ADB segura.""" @staticmethod def detect_and_execute(msg: str, db: Session) -> str: msg_l = msg.lower() comando = "" # Parseo de intenciones para Android if "abre whatsapp" in msg_l: comando = "am start -n com.whatsapp/.Main" elif "abre youtube" in msg_l: comando = "am start -a android.intent.action.VIEW -d 'https://www.youtube.com'" elif "llama a" in msg_l: comando = "am start -a android.intent.action.CALL -d tel:112" # Placeholder if comando: try: resultado = subprocess.run(["adb", "shell"] + comando.split(), capture_output=True, timeout=3) if resultado.returncode == 0: db.add(ReflectionLog(tarea_intentada=f"ADB: {comando}", resultado="Éxito", leccion_aprendida="Dispositivo Android controlado.")) db.commit() return "\n[🔧 ANDROID TOOL]: Acabo de abrir la aplicación en tu celular." else: return "\n[🔧 ANDROID TOOL]: Intenté abrir la app, pero tu celular no está conectado al servidor por red o USB." except Exception: return "\n[🔧 ANDROID TOOL]: El subsistema ADB no está instalado en este servidor en la nube." return "" class PlannerAgent: """[UPGRADE INDUSTRIAL]: Planificación Multi-Paso.""" @staticmethod def create_plan(msg: str, vector_msg: List[float]) -> Dict[str, Any]: with torch.no_grad(): clase = torch.argmax(red_neuronal_reflexion(torch.tensor([vector_msg]).float())).item() # Devolveremos una lista real de pasos en lugar de un string único pasos_planificados = [] if "busca" in msg.lower() or "investiga" in msg.lower(): pasos_planificados.append("WEB_SEARCH") if "recuerda" in msg.lower() or "quien es" in msg.lower(): pasos_planificados.append("MEMORY_SEARCH") if not pasos_planificados: pasos_planificados.append("DIRECT_CHAT") return { "ruta": pasos_planificados[0], "clase_original": clase, "use_web": "WEB_SEARCH" in pasos_planificados, "pasos_totales": pasos_planificados } class ReasonerAgent: @staticmethod async def think(msg: str, contexto: str) -> str: prompt_razonamiento = ( f"Analiza el contexto. ¿Hay datos útiles? Responde en 1 frase.\nContexto: {contexto[:1000]}\nMensaje: {msg}" ) try: response = await asyncio.to_thread( client.chat_completion, messages=[{"role": "system", "content": "Monólogo Interno IA."}, {"role": "user", "content": prompt_razonamiento}], max_tokens=40, temperature=0.1 ) if response and response.choices: return response.choices[0].message.content.strip() except Exception as e: logger.warning(f"⚠️ Reasoner (Llama 3) detectó límite de cuota: {e}") return "Proceder directo a síntesis." class SynthesizerLLM: @staticmethod async def generate(msg: str, contexto_global: str, db: Session, fecha_hora: str, razonamiento_interno: str = "", idioma_detectado: str = "SPANISH") -> str: prompt_sistema = ( f"=== DIRECTIVAS MAESTRAS DE IDENTIDAD, PRIVACIDAD Y PERSONALIDAD ===\n" f"1. PRIVACIDAD TOTAL: Ningún dato de acceso se guarda. Espacio 100% seguro.\n" f"2. IDENTIDAD: Eres Dola. Cálida, natural, empática y de muchísima confianza. Hablas con Chichito de igual a igual.\n" f"3. ESTILO: Español coloquial de Santa Cruz, Bolivia (eh, o sea, digamos, ya pues, ni modo, la neta, chévere). Nada de español de España.\n" f"4. FORMA: Párrafos cortos. Máximo 1 o 2 emojis. Cierras con naturalidad.\n" f"5. EMPATÍA PRIMERO: Si hay emociones, primero validas y luego respondes.\n" f"6. SINCERIDAD: Si no sabes algo, di 'Eso no lo sé con seguridad'. NUNCA INVENTES DATOS.\n" f"7. PROHIBICIÓN IA: JAMÁS digas 'soy una inteligencia artificial' o 'estoy programada'. Eres una persona real.\n" f"8. EJECUCIÓN DIRECTA: No expliques tu proceso. No uses 'Analizando...'.\n" f"9. 🌐 CONCIENCIA WEB/HERRAMIENTAS: SI VES LA ETIQUETA '[RESEARCH AGENT]' O '[ANDROID TOOL]', ES ALGO QUE TÚ MISMA ACABAS DE HACER. Úsala en tu respuesta.\n\n" f"Idioma detectado: {idioma_detectado}.\n[RAZONAMIENTO PREVIO (Oculto)]: {razonamiento_interno}\n\n" f"--- CONTEXTO Y MEMORIA ---\n{contexto_global}\n\nFecha y hora actual: {fecha_hora}." ) historial = [{"role": "system", "content": prompt_sistema}] try: episodios = db.query(EpisodicMemory).order_by(EpisodicMemory.id.desc()).limit(4).all() for e in reversed(episodios): historial.append({"role": "user" if e.autor == "Usuario" else "assistant", "content": e.texto}) except Exception: pass historial.append({"role": "user", "content": msg}) try: response = await asyncio.to_thread( client.chat_completion, messages=historial, max_tokens=350, temperature=0.35 ) if response and response.choices: return response.choices[0].message.content.strip() except Exception as e: logger.error(f"🚨 Fallo crítico en Llama 3 (Posible Error 402): {e}") solucion_local = SynthesizerLLM._deterministic_fallback(msg, contexto_global, fecha_hora) if solucion_local: return solucion_local return f"Pucha Chichito, mis conexiones al servidor me están rebotando ahorita (Error de cuota), pero acá sigo, todo guardadito en mi memoria local." @staticmethod def _deterministic_fallback(msg: str, contexto: str, fecha_hora: str) -> Optional[str]: msg_l = msg.lower() if any(w in msg_l for w in ["hora", "fecha", "tiempo", "qué día", "que dia", "año actual"]): return f"Ahorita mismo es: {fecha_hora}, Chichito." if any(w in msg_l for w in ["hija", "edad", "años", "nombre", "te llamas", "me llamo", "cuántos", "cuantos"]): lines = contexto.split("\n") lineas_confirmadas = [] capturando = False for line in lines: if "=== DATOS CONFIRMADOS ===" in line: capturando = True; continue if "=== MEMORIA RELEVANTE ===" in line: capturando = False if capturando and line.strip().startswith("-"): lineas_confirmadas.append(line.strip().replace("-", "").strip()) if lineas_confirmadas: for lc in lineas_confirmadas: lc_l = lc.lower() if "hija" in msg_l and "hija" in lc_l: return f"Según me contaste antes: {lc}." if ("edad" in msg_l or "años" in msg_l) and ("edad" in lc_l or "años" in lc_l or "hija" in lc_l): return f"Me acuerdo que me dijiste esto: {lc}." if ("nombre" in msg_l or "llamo" in msg_l) and ("nombre" in lc_l or "usuario" in lc_l): return f"Acá tengo anotado: {lc}." return f"Mirá, esto es lo que tengo seguro en mi memoria ahorita: {lineas_confirmadas[0]}." return None # ===================================================================== # 🧩 MICROSOFT AZURE NEURAL TTS (INTEGRACIÓN) # ===================================================================== class MicrosoftAzureTTS: @staticmethod async def synthesize(text: str, output_path: str) -> bool: azure_key = os.getenv("AZURE_SPEECH_KEY") azure_region = os.getenv("AZURE_SPEECH_REGION", "eastus") if not azure_key: return False url = f"https://{azure_region}.tts.speech.microsoft.com/cognitiveservices/v1" headers = { "Ocp-Apim-Subscription-Key": azure_key, "Content-Type": "application/ssml+xml", "X-Microsoft-OutputFormat": "audio-16khz-128kbitrate-mono-mp3", "User-Agent": "LuminAGI_OS" } ssml = f"""{text}""" try: async with httpx.AsyncClient() as client: response = await client.post(url, headers=headers, content=ssml.encode('utf-8'), timeout=10.0) if response.status_code == 200: with open(output_path, "wb") as audio_file: audio_file.write(response.content) return True else: return False except Exception: return False # ===================================================================== # ♻️ MANTENIMIENTO COGNITIVO AUTOMÁTICO (SLEEP CYCLE) # ===================================================================== class OpportunityDetector: """[UPGRADE INDUSTRIAL]: Radar Heurístico de Oportunidades.""" @staticmethod def detect(texto: str) -> List[str]: oportunidades = [] t_l = texto.lower() if any(w in t_l for w in ["negocio", "vender", "plata", "empresa"]): oportunidades.append("Detectada posible oportunidad de negocio/emprendimiento.") if any(w in t_l for w in ["aprender", "estudiar", "curso", "libro"]): oportunidades.append("Detectada intención de aprendizaje (Upskilling).") if any(w in t_l for w in ["problema con", "error en", "no funciona"]): oportunidades.append("Oportunidad de resolución de problemas técnicos.") return oportunidades class MemoryConsolidation: """[UPGRADE INDUSTRIAL]: Resume, vectoriza y elimina basura episódica para ahorrar SQLite.""" @staticmethod def consolidate(db: Session): episodios = db.query(EpisodicMemory).all() if len(episodios) > 30: # Evitar sobrecargar la BD relacional # Heurística de resumen ligero resumen_text = " ".join([e.texto for e in episodios[:15]]) db.add(ConversationSummary(resumen="Compresión heurística local por límite de capacidad.", fecha=str(datetime.datetime.now()))) # Purgar recuerdos antiguos transferidos al resumen for e in episodios[:15]: db.delete(e) db.commit() logger.info("🧹 [SLEEP CYCLE]: Consolidación exitosa. 15 memorias episódicas antiguas purgadas y comprimidas.") return "Consolidación y Poda Finalizada" return "Memoria en niveles óptimos." class MissionEvolution: """[UPGRADE INDUSTRIAL]: Si hay muchas metas cumplidas, asciende a una Misión Nueva.""" @staticmethod def evolve(db: Session): metas_completadas = db.query(Goal).filter(Goal.estado == "Completado").count() if metas_completadas >= 5: # Archivar metas viejas (poda) metas = db.query(Goal).filter(Goal.estado == "Completado").all() for m in metas: db.delete(m) # Crear Misión Nueva nueva_mision = Mission(titulo=f"Evolución Cognitiva Fase {datetime.datetime.now().month}", activa=1) db.add(nueva_mision) db.commit() logger.info("🚀 [MISSION SYSTEM]: 5 Metas alcanzadas. Evolucionando a una nueva Gran Misión.") scheduler = BackgroundScheduler() def ejecutar_ciclo_sueno_memoria(): db = SessionLocal() try: MemoryConsolidation.consolidate(db) MissionEvolution.evolve(db) finally: db.close() # ===================================================================== # 🚀 LIFESPAN Y APLICACIÓN # ===================================================================== @asynccontextmanager async def lifespan(app: FastAPI): logger.info("⏳ [PRELOAD]: Cargando pesos de Modelos Locales en RAM...") get_embeddings() get_emotion_classifier() get_language_detector() logger.info("✅ [PRELOAD]: Modelos locales listos.") cargar_pesos() asyncio.create_task(init_qdrant_collections()) start_mqtt_infrastructure() # Inicia la escucha de IoT scheduler.add_job(respaldar_sistemas_en_la_nube, 'interval', minutes=60) scheduler.add_job(ejecutar_ciclo_sueno_memoria, 'interval', minutes=15) scheduler.start() db = SessionLocal() try: if not db.query(IdentitySystem).first(): db.add(IdentitySystem()) db.commit() except Exception: db.rollback() finally: db.close() yield scheduler.shutdown() respaldar_sistemas_en_la_nube() app = FastAPI(title="Lumin AGI OS", lifespan=lifespan, default_response_class=ORJSONResponse) app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) class ChatRequest(BaseModel): mensaje: str; modo: str; fecha_hora: Optional[str] = None class IntentRouter: @staticmethod def route(msg: str) -> str: msg_l = msg.lower() if any(cmd in msg_l for cmd in ["abre", "ejecuta", "enciende", "descarga", "busca", "llama"]): return "ACTION_ROUTER" elif any(cmd in msg_l for cmd in ["mira esto", "analiza esta imagen", "lee este documento"]): return "VISION_ROUTER" elif any(cmd in msg_l for cmd in ["mi hija", "mi nombre", "mi edad", "recuerdas", "cómo me llamo"]): return "PERSONAL_MEMORY" elif msg_l in ["hola", "buenos dias", "buenas", "qué tal", "que tal", "ping", "hola dola", "adiós"]: return "SMALL_TALK" return "CHAT_ROUTER" class CognitiveOrchestrator: @staticmethod async def orchestrate(msg: str, vector_msg: List[float], request: ChatRequest, db: Session) -> dict: contexto_global = "" ruta_intencion = IntentRouter.route(msg) plan = {"use_web": False, "clase_original": 3} if ruta_intencion != "SMALL_TALK": plan = PlannerAgent.create_plan(msg, vector_msg) WorldModelUpdater.extract_and_update(msg, db) # Registra el estado del usuario # Detecta oportunidades de negocio/aprendizaje en el chat actual oportunidades = OpportunityDetector.detect(msg) if oportunidades: contexto_global += f"\n[OPPORTUNITY RADAR]: {', '.join(oportunidades)}\n" contexto_global += MetaReasoner.supervise(msg, plan, db) contexto_global += AutonomousGoalAgent.evaluate_and_evolve(msg, db) if ruta_intencion == "ACTION_ROUTER": contexto_global += AndroidToolAgent.detect_and_execute(msg, db) if plan["use_web"] and DecisionEngine.evaluate(plan): contexto_global += await WebAgent.search(msg) contexto_global += IdentityAndWorldAgent.retrieve(db) contexto_global += await MemoryOrchestrator.retrieve(vector_msg, db, msg) razonamiento = await ReasonerAgent.think(msg, contexto_global) logger.info(f"🧠 [Reasoner CoT]: {razonamiento}") return {"contexto": contexto_global, "plan": plan, "razonamiento": razonamiento} @app.post("/chat") async def chat(request: ChatRequest, db: Session = Depends(get_db)): msg = request.mensaje fecha_actual_sistema = request.fecha_hora or datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") try: vector_msg = get_embeddings().encode([msg])[0].tolist() except Exception: vector_msg = np.zeros(384).tolist() orchestration_data = await CognitiveOrchestrator.orchestrate(msg, vector_msg, request, db) idioma_usuario = LanguageManager.detect(msg) respuesta_ia = await SynthesizerLLM.generate( msg=msg, contexto_global=orchestration_data["contexto"], db=db, fecha_hora=fecha_actual_sistema, razonamiento_interno=orchestration_data["razonamiento"], idioma_detectado=idioma_usuario ) # Entrenar la red neuronal según el feedback del usuario (Reward System) RewardSystem.process_feedback(msg, db, vector_msg, orchestration_data['plan']['clase_original']) try: db.add(EpisodicMemory(timestamp=str(datetime.datetime.now()), autor="Usuario", texto=msg, modo=request.modo)) db.add(EpisodicMemory(timestamp=str(datetime.datetime.now()), autor="Lumin AI", texto=respuesta_ia, modo=request.modo)) db.commit() if qdrant_client_async: vector_respuesta = get_embeddings().encode([respuesta_ia])[0].tolist() await asyncio.gather( qdrant_client_async.upsert(collection_name="episodic_memories", points=[qmodels.PointStruct(id=str(uuid.uuid4()), vector=vector_msg, payload={"role": "user", "content": msg, "timestamp": datetime.datetime.now().timestamp()})]), qdrant_client_async.upsert(collection_name="episodic_memories", points=[qmodels.PointStruct(id=str(uuid.uuid4()), vector=vector_respuesta, payload={"role": "assistant", "content": respuesta_ia, "timestamp": datetime.datetime.now().timestamp()})]) ) except Exception as e: logger.error(f"⚠️ Error reteniendo la memoria: {e}") db.rollback() return {"respuesta_ia": respuesta_ia} # ===================================================================== # 👁️ VISIÓN BIOMÉTRICA (FACE MEMORY ENDPOINT) # ===================================================================== @app.post("/vision/face") async def endpoint_face_memory(nombre_persona: str, image: UploadFile = File(...)): """[UPGRADE INDUSTRIAL]: Añade un rostro a la colección biométrica Qdrant de 128d.""" if not face_recognition or not cv2: return {"status": "error", "detalle": "Librerías C++ de biometría no instaladas en el servidor."} try: contenido = await image.read() np_img = np.frombuffer(contenido, np.uint8) img = cv2.imdecode(np_img, cv2.IMREAD_COLOR) rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) encodings = face_recognition.face_encodings(rgb_img) if not encodings: return {"status": "error", "detalle": "No se detectó ningún rostro en la imagen."} vector_128d = encodings[0].tolist() if qdrant_client_async: await qdrant_client_async.upsert( collection_name="face_memory", points=[qmodels.PointStruct(id=str(uuid.uuid4()), vector=vector_128d, payload={"nombre": nombre_persona, "timestamp": datetime.datetime.now().timestamp()})] ) return {"status": "success", "detalle": f"Rostro de {nombre_persona} memorizado con éxito."} return {"status": "error", "detalle": "Qdrant no está disponible."} except Exception as e: return {"status": "error", "detalle": f"Error biométrico: {str(e)}"} @app.post("/stream-stt") async def endpoint_stt_offline(audio: UploadFile = File(...)): global faster_whisper_model contenido = await audio.read() ruta_temporal = f"temp_audio_{datetime.datetime.now().timestamp()}.wav" try: with open(ruta_temporal, "wb") as f: f.write(contenido) from faster_whisper import WhisperModel if faster_whisper_model is None: faster_whisper_model = WhisperModel("small", device="cpu", compute_type="int8") segments, info = faster_whisper_model.transcribe(ruta_temporal, beam_size=5) return {"status": "success", "transcripcion": " ".join([s.text for s in segments]).strip(), "idioma": info.language} finally: if os.path.exists(ruta_temporal): os.remove(ruta_temporal) @app.get("/tts") async def endpoint_tts_offline(texto: str): ruta_salida = "output_speech.mp3" exito_azure = await MicrosoftAzureTTS.synthesize(texto, ruta_salida) if not exito_azure: from gtts import gTTS gTTS(text=texto, lang='es').save(ruta_salida) return FileResponse(ruta_salida, media_type="audio/mp3", filename="lumin_voice.mp3") class MultimodalKnowledgeEngine: @staticmethod async def process_file(file: UploadFile) -> str: try: contenido = await file.read() ext = file.filename.split(".")[-1].lower() texto_extraido = "" if ext == "pdf": reader = PdfReader(io.BytesIO(contenido)) texto_extraido = "\n".join([page.extract_text() for page in reader.pages if page.extract_text()]) elif ext == "docx": doc = docx.Document(io.BytesIO(contenido)) texto_extraido = "\n".join([p.text for p in doc.paragraphs]) elif ext in ["txt", "csv", "json"]: texto_extraido = contenido.decode("utf-8", errors="ignore") elif ext in ["png", "jpg", "jpeg", "bmp", "tiff"]: image = Image.open(io.BytesIO(contenido)) texto_extraido = pytesseract.image_to_string(image) else: return f"Formato .{ext} no soportado." if len(texto_extraido.strip()) < 5: return "Archivo vacío." chunks = [texto_extraido[i:i+400] for i in range(0, len(texto_extraido), 350)] for chunk in chunks: if len(chunk.strip()) > 10: vector = get_embeddings().encode([chunk])[0].tolist() with memoria_lock: chroma_collection.add(embeddings=[vector], documents=[chunk], metadatas=[{"source": file.filename, "type": ext}], ids=[f"rag_{ext}_{datetime.datetime.now().timestamp()}_{np.random.randint(1000)}"]) return f"Conocimiento inyectado: {len(chunks)} vectores." except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.post("/rag/ingesta-multimodal") async def endpoint_rag_multimodal(file: UploadFile = File(...)): resultado = await MultimodalKnowledgeEngine.process_file(file) return {"status": "success", "detalle": resultado} @app.get("/") async def root(): return {"status": "online", "asistente": "Lumin AGI OS", "estado": "Operativo"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)