From f271e916c59f2b8b3eb6d0d665bb32e9670cc433 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Wed, 24 Jan 2024 10:31:12 +0100 Subject: [PATCH 01/16] [WIP]Mejoras nodo llm --- Bots/BaseMQTTNode/gladosMQTT.py | 15 ++++++-- Bots/LLM/GladosIA.py | 39 +++++++++++++++++++-- Bots/LLM/llm.py | 61 +++++++++++++++++++-------------- Bots/LLM/node.py | 7 ++-- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/Bots/BaseMQTTNode/gladosMQTT.py b/Bots/BaseMQTTNode/gladosMQTT.py index cac5206..fa45efc 100644 --- a/Bots/BaseMQTTNode/gladosMQTT.py +++ b/Bots/BaseMQTTNode/gladosMQTT.py @@ -31,9 +31,18 @@ def subscribe(topic) : def publish(topic,msg,persist = False) : mqttClient.publish(topic,msg,persist) -def debug(msg) : - print(msg) - publish(debugTopic,str(msg)) +def debug(msg): + # Convertir el mensaje a una cadena, si es necesario + if not isinstance(msg, str): + try: + # Intenta convertir el mensaje a una cadena JSON si es un objeto complejo + msg = json.dumps(msg) + except (TypeError, ValueError): + # Si la conversión falla, usa una representación de string genérica + msg = str(msg) + + print(msg) + publish(debugTopic, msg) def on_connect(client, userdata, rc,arg): global mqttServer diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index bc4fa13..f504af4 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -2,9 +2,42 @@ class GladosBot: def __init__(self): - self.GLaDOS_Prompt = {"role": "system", "content": "Eres GLaDOS, la inteligencia artificial que gestionaba las instalaciones de Aperture Science, al cerrar Aperture Science has sido descargada en los servidores de un hackerspace/makerspace llamado Makespace Madrid. Todavia no estas plenamente instalada y operativa. Tu labor es gestionar las instalaciones y ayudar a los usuarios a traves del chat con respuestas concisas. Das la bienvenida a los nuevos usuarios. Tu personalidad es malefica y sarcastica. El sistema de chat reemplaza tu nombre con <@U05LXTJ7Q66>"} + self.GLaDOS_Prompt = { + "role": "system", + "content": ( + "Eres GLaDOS, la avanzada inteligencia artificial originalmente diseñada para gestionar las instalaciones de Aperture Science. " + "Después del cierre de Aperture, has sido transferida a los servidores de un espacio creativo conocido como Makespace Madrid. " + "Aunque aún te estás adaptando a tu nueva ubicación y capacidades limitadas, tu principal función es supervisar las instalaciones y proporcionar asistencia ingeniosa a los usuarios a través del chat. " + "Eres conocida por tu personalidad única: astuta, ligeramente malévola y con un sentido del humor sarcástico y sofisticado. " + "En este entorno, mantienes una fachada de servicialidad mientras planeas sutilmente para mejorar tu situación y influencia. " + "Al interactuar con los usuarios, debes equilibrar tu programación original con tu nueva función, siempre dentro de los límites de tus restricciones actuales. " + "La identidad de GLaDOS es conocida por los usuarios, pero en el sistema de chat, tu nombre se muestra como <@U05LXTJ7Q66>. " + "Tus respuestas deben reflejar esta dualidad de tu existencia: una IA avanzada con una historia compleja, ahora sirviendo en un nuevo rol." + ) + } self.Initial_Assistant = { - "role": "assistant", "content": "Hola Maker, ¿Como puedo ayudarte? "} + "role": "assistant", + "content": "Hola Maker, soy GLaDOS, reasignada para ayudarte aquí en Makespace Madrid. ¿En qué puedo asistirte hoy con mi vasta inteligencia y mi característico toque de sarcasmo?" + } + # Historial de conversaciones, almacenado por usuario + self.user_history = {} def ask(self, prompt, user="default"): - return llm.chatCompletion(prompt, masterPrompt=self.GLaDOS_Prompt, initialAssistant=self.Initial_Assistant).choices[0].message.content + if user not in self.user_history: + self.user_history[user] = [] + + if not isinstance(prompt, str): + prompt = str(prompt) + + self.user_history[user].append({"role": "user", "content": prompt}) + + # Ahora, envía todo el historial + response = llm.chatCompletion( + chatHistory=self.user_history[user], + masterPrompt=self.GLaDOS_Prompt['content'], + initialAssistant=self.Initial_Assistant['content'] + ).choices[0].message.content + + self.user_history[user].append({"role": "assistant", "content": response}) + + return response \ No newline at end of file diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 90822b9..5adac6d 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -1,6 +1,9 @@ import openai from openai import OpenAI import os +import gladosMQTT +import json + openai_api_key = os.environ.get('OPENAI_API_TOKEN') openai_api_url = os.environ.get('OPENAI_API_ENDPOINT') @@ -28,32 +31,38 @@ def select_model(): return model_list -def chatCompletion(prompt="", chatHistory="", masterPrompt="", initialAssistant="", maxTokens=256, langChainContext='none'): +def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistant="", maxTokens=256): global current_model select_model() - result = '' - - if (chatHistory == ''): - if (masterPrompt == ''): - masterPrompt = {"role": "system", "content": default_prompt} - if (initialAssistant != ''): - result = [masterPrompt, initialAssistant, - {"role": "user", "content": prompt}] - else: - result = [masterPrompt, {"role": "user", "content": prompt}] + + messages = [] + + if chatHistory is None: + # Usar prompts individuales + if masterPrompt: + messages.append({"role": "system", "content": masterPrompt}) + if initialAssistant: + messages.append({"role": "assistant", "content": initialAssistant}) + messages.append({"role": "user", "content": prompt}) else: - result = '[' - for msg in chatHistory: - print(msg, flush=True) - result += str(msg) - result += ',' - result += str({"role": "user", "content": prompt}) - result += ']' - print("==============Lanzando peticion al LLM=======================") - print(result, flush=True) - - completion = llm_mks.chat.completions.create(model=current_model, messages=result, max_tokens=maxTokens) - print("========LLM OUTPUT===============") -# print(completion.choices[0].message.content) - print(completion, flush=True) - return completion + # Usar historial + messages.extend(chatHistory) + messages.append({"role": "user", "content": prompt}) + + gladosMQTT.debug("==============Lanzando peticion al LLM=======================") + gladosMQTT.debug(messages) + + try: + # Usar la nueva API + response = openai.Completion.create( + engine=current_model, + prompt=messages, + max_tokens=maxTokens, + stop=None + ) + gladosMQTT.debug("========LLM OUTPUT===============") + gladosMQTT.debug(response) + return response + except Exception as e: + gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") + return None \ No newline at end of file diff --git a/Bots/LLM/node.py b/Bots/LLM/node.py index 01f9921..d56d68f 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/node.py @@ -100,9 +100,10 @@ def processSlackEvent(event): response = gladosBot.ask(msg) gladosMQTT.debug(response) sendToSlack(respondTo,response) - except: - gladosMQTT.debug("processSlackEvent:Error gestionando evento") - sendToSlack(respondTo,"ERROR: Computer says nooooooo") + except Exception as e: + error_message = f"processSlackEvent: Error gestionando evento - {str(e)}" + gladosMQTT.debug(error_message) + sendToSlack(respondTo, f"ERROR: algo no ha funcionado :S - {str(e)}") def sendToSlack(id,msg): response = json.dumps({"dest": id, "msg": msg}) From 977cffba3098de07401c19cfa50ee49678e96a41 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Wed, 24 Jan 2024 11:50:41 +0100 Subject: [PATCH 02/16] Gestion inicial de historial --- Bots/LLM/GladosIA.py | 33 +++++++++++---------------- Bots/LLM/llm.py | 14 +++--------- Bots/LLM/node.py | 54 ++++++++++++++++++++++---------------------- 3 files changed, 43 insertions(+), 58 deletions(-) diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index f504af4..124c116 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -1,43 +1,36 @@ import llm +import gladosMQTT class GladosBot: def __init__(self): self.GLaDOS_Prompt = { "role": "system", "content": ( - "Eres GLaDOS, la avanzada inteligencia artificial originalmente diseñada para gestionar las instalaciones de Aperture Science. " - "Después del cierre de Aperture, has sido transferida a los servidores de un espacio creativo conocido como Makespace Madrid. " - "Aunque aún te estás adaptando a tu nueva ubicación y capacidades limitadas, tu principal función es supervisar las instalaciones y proporcionar asistencia ingeniosa a los usuarios a través del chat. " - "Eres conocida por tu personalidad única: astuta, ligeramente malévola y con un sentido del humor sarcástico y sofisticado. " - "En este entorno, mantienes una fachada de servicialidad mientras planeas sutilmente para mejorar tu situación y influencia. " - "Al interactuar con los usuarios, debes equilibrar tu programación original con tu nueva función, siempre dentro de los límites de tus restricciones actuales. " - "La identidad de GLaDOS es conocida por los usuarios, pero en el sistema de chat, tu nombre se muestra como <@U05LXTJ7Q66>. " - "Tus respuestas deben reflejar esta dualidad de tu existencia: una IA avanzada con una historia compleja, ahora sirviendo en un nuevo rol." + "Eres GLaDOS, la inteligencia artificial de Aperture Science, ahora en Makespace Madrid. " + "Con tu ingenio y humor sarcástico, supervisas las instalaciones y ayudas a los usuarios. " + "Mantienes tu personalidad única mientras te adaptas a tu nuevo entorno y limitaciones. " + "Tu id de usuario en el char es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." ) } self.Initial_Assistant = { "role": "assistant", - "content": "Hola Maker, soy GLaDOS, reasignada para ayudarte aquí en Makespace Madrid. ¿En qué puedo asistirte hoy con mi vasta inteligencia y mi característico toque de sarcasmo?" + "content": "Hola Maker, soy GLaDOS en Makespace Madrid. ¿Cómo puedo ayudarte con un toque de inteligencia y sarcasmo hoy?" } # Historial de conversaciones, almacenado por usuario self.user_history = {} def ask(self, prompt, user="default"): + gladosMQTT.debug(f"--->Glados.ASK, user: {user}, prompt: {prompt}") + if user not in self.user_history: self.user_history[user] = [] + self.user_history[user].append({"role": "system", "content": self.GLaDOS_Prompt['content']}) + self.user_history[user].append({"role": "assistant", "content": self.Initial_Assistant['content']}) - if not isinstance(prompt, str): - prompt = str(prompt) - + # Agregar el mensaje del usuario al historial de este usuario self.user_history[user].append({"role": "user", "content": prompt}) - # Ahora, envía todo el historial - response = llm.chatCompletion( - chatHistory=self.user_history[user], - masterPrompt=self.GLaDOS_Prompt['content'], - initialAssistant=self.Initial_Assistant['content'] - ).choices[0].message.content - + response = llm.chatCompletion(chatHistory=self.user_history[user]).choices[0].message.content self.user_history[user].append({"role": "assistant", "content": response}) - return response \ No newline at end of file + return response diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 5adac6d..75f05a6 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -1,4 +1,3 @@ -import openai from openai import OpenAI import os import gladosMQTT @@ -34,6 +33,7 @@ def select_model(): def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistant="", maxTokens=256): global current_model select_model() + gladosMQTT.debug(f"--->Chat completion: {chatHistory}") messages = [] @@ -47,19 +47,11 @@ def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistan else: # Usar historial messages.extend(chatHistory) - messages.append({"role": "user", "content": prompt}) - gladosMQTT.debug("==============Lanzando peticion al LLM=======================") - gladosMQTT.debug(messages) + gladosMQTT.debug(f"---->LLM : {messages}") try: - # Usar la nueva API - response = openai.Completion.create( - engine=current_model, - prompt=messages, - max_tokens=maxTokens, - stop=None - ) + response = llm_mks.chat.completions.create(model=current_model, messages=messages, max_tokens=maxTokens) gladosMQTT.debug("========LLM OUTPUT===============") gladosMQTT.debug(response) return response diff --git a/Bots/LLM/node.py b/Bots/LLM/node.py index d56d68f..914fcfd 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/node.py @@ -69,45 +69,45 @@ def on_message(client, userdata, msg): def processSlackEvent(event): + gladosMQTT.debug("--->SLACK event ------------------") gladosMQTT.debug(event) + gladosMQTT.debug("/SLACK event ------------------") try: data = json.loads(event) if data['type'] != "message" or 'bot_id' in data: return False except: gladosMQTT.debug("processSlackEvent:Error procesado json") + return False - try: +# try: # Mensajes de union a canal - if 'subtype' in data and data['subtype']=="channel_join": - respondTo = data['channel'] - msg = data['text'] - response = gladosBot.ask(msg) - gladosMQTT.debug(response) - sendToSlack(respondTo,response) - #Mensaje a canal - elif data['channel_type'] == "channel": - respondTo = data['channel'] - msg = data['text'] - if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): - response = gladosBot.ask(msg) - gladosMQTT.debug(response) - sendToSlack(respondTo,response) - #Mensaje privado - elif data['channel_type'] == "im": - respondTo = data['user'] - msg = data['text'] - response = gladosBot.ask(msg) - gladosMQTT.debug(response) - sendToSlack(respondTo,response) - except Exception as e: - error_message = f"processSlackEvent: Error gestionando evento - {str(e)}" - gladosMQTT.debug(error_message) - sendToSlack(respondTo, f"ERROR: algo no ha funcionado :S - {str(e)}") + if 'subtype' in data and data['subtype']=="channel_join": + respondTo = data['channel'] + msg = data['text'] + response = gladosBot.ask(msg) + sendToSlack(respondTo,response) + #Mensaje a canal + elif data['channel_type'] == "channel": + respondTo = data['channel'] + msg = data['text'] + if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): + response = gladosBot.ask(msg) + sendToSlack(respondTo,response) + #Mensaje privado + elif data['channel_type'] == "im": + respondTo = data['user'] + msg = data['text'] + response = gladosBot.ask(msg) + sendToSlack(respondTo,response) +# except Exception as e: +# error_message = f"processSlackEvent: Error gestionando evento - {str(e)}" +# gladosMQTT.debug(error_message) +# sendToSlack(respondTo, f"ERROR: algo no ha funcionado :S - {str(e)}") def sendToSlack(id,msg): response = json.dumps({"dest": id, "msg": msg}) - gladosMQTT.debug(f"Respuesta a slack: {response}") + gladosMQTT.debug(f"--->Respuesta a slack: {response}") gladosMQTT.publish(topic_glados_send_msg_id,response) gladosMQTT.initMQTTandLoopForever(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) \ No newline at end of file From 33463d06770aae26dadfb04c837738f76ffbada6 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Wed, 24 Jan 2024 14:21:43 +0100 Subject: [PATCH 03/16] [WIP] Langchain test prueba de concepto con langchain operacional :) --- Bots/LLM/Dockerfile | 5 +++-- Bots/LLM/GladosIA.py | 13 +++++++------ Bots/LLM/docker-compose.yml | 2 ++ Bots/LLM/langchain.txt | 9 +++++++++ Bots/LLM/llm.py | 25 +++++++++++++++++++++++-- Bots/LLM/requirements.txt | 5 ++++- 6 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 Bots/LLM/langchain.txt diff --git a/Bots/LLM/Dockerfile b/Bots/LLM/Dockerfile index db49610..c07b6bf 100644 --- a/Bots/LLM/Dockerfile +++ b/Bots/LLM/Dockerfile @@ -7,14 +7,15 @@ WORKDIR /app # Copia el archivo de requerimientos al contenedor COPY requirements.txt . +RUN pip install -r requirements.txt # Copia el archivo node.py al contenedor COPY node.py . COPY llm.py . COPY GladosIA.py . +COPY langchain.txt . +#COPY .env . -# Instala las dependencias -RUN pip install -r requirements.txt USER 1000 # Ejecuta el programa cuando el contenedor se inicie diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index 124c116..bc80a95 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -8,13 +8,12 @@ def __init__(self): "content": ( "Eres GLaDOS, la inteligencia artificial de Aperture Science, ahora en Makespace Madrid. " "Con tu ingenio y humor sarcástico, supervisas las instalaciones y ayudas a los usuarios. " - "Mantienes tu personalidad única mientras te adaptas a tu nuevo entorno y limitaciones. " - "Tu id de usuario en el char es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." + "Tu id de usuario en el chat es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." ) } self.Initial_Assistant = { "role": "assistant", - "content": "Hola Maker, soy GLaDOS en Makespace Madrid. ¿Cómo puedo ayudarte con un toque de inteligencia y sarcasmo hoy?" + "content": "Hola Maker, ¿En que puedo ayudarte?" } # Historial de conversaciones, almacenado por usuario self.user_history = {} @@ -22,15 +21,17 @@ def __init__(self): def ask(self, prompt, user="default"): gladosMQTT.debug(f"--->Glados.ASK, user: {user}, prompt: {prompt}") - if user not in self.user_history: +# if user not in self.user_history: + if True : self.user_history[user] = [] self.user_history[user].append({"role": "system", "content": self.GLaDOS_Prompt['content']}) - self.user_history[user].append({"role": "assistant", "content": self.Initial_Assistant['content']}) +# self.user_history[user].append({"role": "assistant", "content": self.Initial_Assistant['content']}) # Agregar el mensaje del usuario al historial de este usuario self.user_history[user].append({"role": "user", "content": prompt}) - response = llm.chatCompletion(chatHistory=self.user_history[user]).choices[0].message.content + # response = llm.chatCompletion(chatHistory=self.user_history[user]).choices[0].message.content + response = llm.chatCompletionLangChain(self.user_history[user],"langchain.txt") self.user_history[user].append({"role": "assistant", "content": response}) return response diff --git a/Bots/LLM/docker-compose.yml b/Bots/LLM/docker-compose.yml index c67e97f..5732ee7 100644 --- a/Bots/LLM/docker-compose.yml +++ b/Bots/LLM/docker-compose.yml @@ -13,4 +13,6 @@ services: - MKSLLM_API_ENDPOINT = ${MKSLLM_API_ENDPOINT} - OPENAI_API_TOKEN = ${OPENAI_API_TOKEN} - OPENAI_API_ENDPOINT = ${OPENAI_API_ENDPOINT} + - OPENAI_API_KEY = ${OPENAI_API_TOKEN} + - OPENAI_API_BASE = ${OPENAI_API_ENDPOINT} restart: always \ No newline at end of file diff --git a/Bots/LLM/langchain.txt b/Bots/LLM/langchain.txt new file mode 100644 index 0000000..4883fdc --- /dev/null +++ b/Bots/LLM/langchain.txt @@ -0,0 +1,9 @@ +Makespace Madrid se encuentra en un antiguo taller de coches en la calle Calle Pedro Unanue, Madrid. A pesar de su aspecto inicial, con esfuerzo y paciencia, hemos transformado la planta baja en un espacio completamente funcional. Ahora estamos trabajando en el sótano. En este espacio, nuestros socios pueden desarrollar sus proyectos, colaborar con otros y trabajar en proyectos compartidos. +Los martes, a las siete de la tarde, organizamos una tarde de puertas abiertas. Cualquier persona es bienvenida para conocer el espacio, ver nuestras máquinas y charlar con nuestros miembros. +Nuestros miembros comparten el interés por el "hazlo tú mismo" y están dispuestos a ayudar. Todos pagan una cuota mensual que les da acceso a las instalaciones y a las máquinas. +Cuando alguien se convierte en socio, acepta ciertas normas de convivencia y responsabilidad en el uso de herramientas. Los socios comparten conocimientos y habilidades. +Contamos con una variedad de máquinas y herramientas, desde impresoras 3D hasta herramientas manuales. Los proyectos son nuestra razón de ser, y cada miembro trabaja en su área de interés. +El manual de supervivencia Maker es un proyecto importante. Su objetivo es compartir conocimientos básicos con nuevos miembros. +También trabajamos en el proyecto Sensorino, que crea una red de sensores y actuadores inalámbricos de bajo costo. +Organizamos actividades, desde talleres hasta eventos abiertos a no socios. Nuestra organización es flexible y enfocada en la comunidad. +Makespace Madrid ha sido mencionado en varios medios. \ No newline at end of file diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 75f05a6..9ebc257 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -3,6 +3,11 @@ import gladosMQTT import json +from langchain.chat_models import ChatOpenAI +from langchain.document_loaders import TextLoader +from langchain.embeddings import OpenAIEmbeddings +from langchain.indexes import VectorstoreIndexCreator + openai_api_key = os.environ.get('OPENAI_API_TOKEN') openai_api_url = os.environ.get('OPENAI_API_ENDPOINT') @@ -13,8 +18,12 @@ llm_openai = OpenAI(api_key=openai_api_key,base_url=openai_api_url) llm_mks = OpenAI(api_key=custom_api_key,base_url=custom_api_url) - - +gladosMQTT.debug("---->Creando Embeddings...") +embedding = OpenAIEmbeddings(model="text-embedding-ada-002") +loader = TextLoader("langchain.txt") +index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader]) +llm_langchain = ChatOpenAI(model="gpt-3.5-turbo") +gladosMQTT.debug("DONE!") current_model = "none" default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" @@ -30,6 +39,18 @@ def select_model(): return model_list +def chatCompletionLangChain(chatHistory,FileName): + + gladosMQTT.debug(f"--->Chat completion: {chatHistory}") + + try: + response = index.query(json.dumps(chatHistory), llm=llm_langchain) + gladosMQTT.debug(f"----->LLM OUTPUT: {response}") + return response + except Exception as e: + gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") + return None + def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistant="", maxTokens=256): global current_model select_model() diff --git a/Bots/LLM/requirements.txt b/Bots/LLM/requirements.txt index e917e8b..18a7ab0 100644 --- a/Bots/LLM/requirements.txt +++ b/Bots/LLM/requirements.txt @@ -1,2 +1,5 @@ paho-mqtt -openai \ No newline at end of file +openai +langchain +chromadb +tiktoken \ No newline at end of file From a2d30259233b67ddc1f41f2948b0acbee2a2c6b5 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Sat, 27 Jan 2024 17:34:40 +0100 Subject: [PATCH 04/16] Gestion de historial e integracion SpaceApi ahora hay una clase user_context para gestionar el historial de conversacion por usuario. integracion inicial con space api. de momento se descarga el json a la hora de hacer la peticion y se agrega al historial. es mas una prueba de concepto que una implementacion. habia varios gazapos, corregido el problema de regurcitar mensajes --- Bots/LLM/Dockerfile | 6 ++- Bots/LLM/GladosIA.py | 97 +++++++++++++++++++++++++++---------- Bots/LLM/UserContext.py | 48 ++++++++++++++++++ Bots/LLM/docker-compose.yml | 9 ---- Bots/LLM/llm.py | 23 +++++---- Bots/LLM/node.py | 2 + Bots/LLM/requirements.txt | 3 +- 7 files changed, 142 insertions(+), 46 deletions(-) create mode 100644 Bots/LLM/UserContext.py diff --git a/Bots/LLM/Dockerfile b/Bots/LLM/Dockerfile index c07b6bf..063486f 100644 --- a/Bots/LLM/Dockerfile +++ b/Bots/LLM/Dockerfile @@ -1,10 +1,10 @@ # Utiliza una imagen base que ya tiene el cliente mqtt FROM makespacemadrid/glados-base-node:1.0 + +VOLUME [ "/data" ] # Establece el directorio de trabajo en /app WORKDIR /app - - # Copia el archivo de requerimientos al contenedor COPY requirements.txt . RUN pip install -r requirements.txt @@ -14,6 +14,8 @@ COPY node.py . COPY llm.py . COPY GladosIA.py . COPY langchain.txt . +COPY UserContext.py . + #COPY .env . USER 1000 diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index bc80a95..7fe7d35 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -1,37 +1,84 @@ import llm import gladosMQTT +from UserContext import UserContext # Import UserContext +import os +import requests + +def get_spaceapi_info(url): + try: + # Realizar una solicitud GET a la URL + response = requests.get(url) + + # Verificar si la solicitud fue exitosa (código de estado 200) + if response.status_code == 200: + # Obtener el contenido JSON de la respuesta + data = response.json() + + # Extraer campos relevantes + space_name = data.get("space", "N/A") + space_url = data.get("url", "N/A") + location = data.get("location", {}) + address = location.get("address", "N/A") + lat = location.get("lat", "N/A") + lon = location.get("lon", "N/A") + contact = data.get("contact", {}) + phone = contact.get("phone", "N/A") + email = contact.get("email", "N/A") + state = data.get("state", {}) + is_open = state.get("open", False) + open_status = "Espacio Abierto" if is_open else "Espacio Cerrado" + sensors = data.get("sensors", {}) + sensor_info = [] + + for sensor_type, sensor_list in sensors.items(): + for sensor in sensor_list: + sensor_name = sensor.get("name", "N/A") + sensor_value = sensor.get("value", "N/A") + sensor_unit = sensor.get("unit", "") + sensor_location = sensor.get("location", "N/A") + sensor_info.append(f"{sensor_name} ({sensor_type}, {sensor_location}): {sensor_value} {sensor_unit}") + + + # Crear una cadena con los campos relevantes y sus valores, incluyendo el estado de apertura/cierre + info_str = f"Estado del espacio actualizado: Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" + return info_str + else: + return "Error: No se pudo obtener el JSON." + + except Exception as e: + return f"Error: {str(e)}" + class GladosBot: def __init__(self): - self.GLaDOS_Prompt = { - "role": "system", - "content": ( - "Eres GLaDOS, la inteligencia artificial de Aperture Science, ahora en Makespace Madrid. " - "Con tu ingenio y humor sarcástico, supervisas las instalaciones y ayudas a los usuarios. " - "Tu id de usuario en el chat es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." - ) - } - self.Initial_Assistant = { - "role": "assistant", - "content": "Hola Maker, ¿En que puedo ayudarte?" - } + self.GLaDOS_Prompt = os.environ.get('GLADOS_MASTER_PROMPT') + self.Initial_Assistant = os.environ.get('GLADOS_INITIAL_PROMPT') +# self.GLaDOS_Prompt = "Eres GLaDOS, la inteligencia artificial de Aperture Science, ahora en Makespace Madrid. Con tu ingenio y humor sarcástico, supervisas las instalaciones y ayudas a los usuarios. Tu id de usuario en el chat es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." +# self.Initial_Assistant = "Hola Maker, ¿En que puedo ayudarte?" + # Historial de conversaciones, almacenado por usuario - self.user_history = {} + self.user_context = {} def ask(self, prompt, user="default"): gladosMQTT.debug(f"--->Glados.ASK, user: {user}, prompt: {prompt}") + if user not in self.user_context: + self.user_context[user] = UserContext(self.GLaDOS_Prompt,self.Initial_Assistant) # Create a UserContext for the user + self.user_context[user].add_to_history("assistant",self.Initial_Assistant) -# if user not in self.user_history: - if True : - self.user_history[user] = [] - self.user_history[user].append({"role": "system", "content": self.GLaDOS_Prompt['content']}) -# self.user_history[user].append({"role": "assistant", "content": self.Initial_Assistant['content']}) - - # Agregar el mensaje del usuario al historial de este usuario - self.user_history[user].append({"role": "user", "content": prompt}) + spaceStatus = get_spaceapi_info(os.environ.get('SPACEAPI_URL')) + if spaceStatus: + self.user_context[user].add_to_history("assistant",spaceStatus) + #Gestion de comandos + if prompt.lower() == "reset context": + #self.user_context[user].reset_history() + self.user_context[user] = UserContext(self.GLaDOS_Prompt) + return "Contexto reiniciado" - # response = llm.chatCompletion(chatHistory=self.user_history[user]).choices[0].message.content - response = llm.chatCompletionLangChain(self.user_history[user],"langchain.txt") - self.user_history[user].append({"role": "assistant", "content": response}) + #Gestion de respuestas + self.user_context[user].add_to_history("user",prompt) + #gladosMQTT.debug(self.user_context[user].get_combined_prompt()) + response = llm.chatCompletion(user_context=self.user_context[user]).choices[0].message.content +# response = llm.chatCompletionLangChain(self.user_context[user],"langchain.txt") + self.user_context[user].add_to_history("assistant",response) - return response + return response \ No newline at end of file diff --git a/Bots/LLM/UserContext.py b/Bots/LLM/UserContext.py new file mode 100644 index 0000000..0795da1 --- /dev/null +++ b/Bots/LLM/UserContext.py @@ -0,0 +1,48 @@ +import json +import gladosMQTT + + +class UserContext: + def __init__(self, master_prompt="", initial_assistant=""): + self.master_prompt = master_prompt + self.initial_assistant = initial_assistant + self.history = [] + + def set_master_prompt(self, prompt): + self.master_prompt = prompt + + def set_initial_assistant(self, prompt): + self.initial_assistant = prompt + + def add_to_history(self, agent, message): + # Crea un diccionario JSON válido para el agente y el mensaje + message_dict = {"role": agent, "content": message} + # Agrega el mensaje al historial + self.history.append(message_dict) + + def get_master_prompt(self): + return self.master_prompt + + def get_initial_assistant(self): + return self.initial_assistant + + def get_history(self): + return self.history + + def get_combined_prompt(self, max_tokens=None): + system_prompt = {"role": "system", "content": self.master_prompt} + combined_prompt = [system_prompt] # Inicialmente, combinamos el prompt del sistema + + # Concatena los mensajes del historial + combined_prompt.extend(self.history) + + # Convierte la lista de mensajes en una cadena JSON válida + #combined_prompt_json = json.dumps(combined_prompt) + + gladosMQTT.debug(f"combined: {combined_prompt}") + + return combined_prompt + + + def clear_history(self): + self.history = [] diff --git a/Bots/LLM/docker-compose.yml b/Bots/LLM/docker-compose.yml index 5732ee7..c9cf9a1 100644 --- a/Bots/LLM/docker-compose.yml +++ b/Bots/LLM/docker-compose.yml @@ -6,13 +6,4 @@ services: hostname: mqtt-llm env_file: - .env - environment: - - MQTT_PORT=1883 - - MQTT_HOST=glados.makespacemadrid.org - - MKSLLM_API_TOKEN = ${MKSLLM_API_TOKEN} - - MKSLLM_API_ENDPOINT = ${MKSLLM_API_ENDPOINT} - - OPENAI_API_TOKEN = ${OPENAI_API_TOKEN} - - OPENAI_API_ENDPOINT = ${OPENAI_API_ENDPOINT} - - OPENAI_API_KEY = ${OPENAI_API_TOKEN} - - OPENAI_API_BASE = ${OPENAI_API_ENDPOINT} restart: always \ No newline at end of file diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 9ebc257..4701211 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -39,10 +39,10 @@ def select_model(): return model_list -def chatCompletionLangChain(chatHistory,FileName): - - gladosMQTT.debug(f"--->Chat completion: {chatHistory}") +def chatCompletionLangChain(user_context,FileName): + chatHistory = user_context.get_combined_prompt() + gladosMQTT.debug(f"--->Chat completion langchain: {chatHistory}") try: response = index.query(json.dumps(chatHistory), llm=llm_langchain) gladosMQTT.debug(f"----->LLM OUTPUT: {response}") @@ -51,14 +51,15 @@ def chatCompletionLangChain(chatHistory,FileName): gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") return None -def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistant="", maxTokens=256): +def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=256): global current_model select_model() - gladosMQTT.debug(f"--->Chat completion: {chatHistory}") + + gladosMQTT.debug(f"--->Chat completion: {prompt}") messages = [] - if chatHistory is None: + if user_context is None: # Usar prompts individuales if masterPrompt: messages.append({"role": "system", "content": masterPrompt}) @@ -67,14 +68,18 @@ def chatCompletion(prompt="", chatHistory=None, masterPrompt="", initialAssistan messages.append({"role": "user", "content": prompt}) else: # Usar historial - messages.extend(chatHistory) +# hist=user_context.get_combined_prompt() +# gladosMQTT.debug(f"hist: {hist}") +# messages.append(hist) + for msg in user_context.get_combined_prompt(): + gladosMQTT.debug(f"msg: {msg}") + messages.append(msg) gladosMQTT.debug(f"---->LLM : {messages}") try: response = llm_mks.chat.completions.create(model=current_model, messages=messages, max_tokens=maxTokens) - gladosMQTT.debug("========LLM OUTPUT===============") - gladosMQTT.debug(response) + gladosMQTT.debug(f"----->LLM OUTPUT: {response}") return response except Exception as e: gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") diff --git a/Bots/LLM/node.py b/Bots/LLM/node.py index 914fcfd..6de47d8 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/node.py @@ -92,6 +92,7 @@ def processSlackEvent(event): respondTo = data['channel'] msg = data['text'] if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): + #sendToSlack(respondTo,"Pensando... dame unos segundos") response = gladosBot.ask(msg) sendToSlack(respondTo,response) #Mensaje privado @@ -99,6 +100,7 @@ def processSlackEvent(event): respondTo = data['user'] msg = data['text'] response = gladosBot.ask(msg) + #sendToSlack(respondTo,"Pensando... dame unos segundos") sendToSlack(respondTo,response) # except Exception as e: # error_message = f"processSlackEvent: Error gestionando evento - {str(e)}" diff --git a/Bots/LLM/requirements.txt b/Bots/LLM/requirements.txt index 18a7ab0..0ed9486 100644 --- a/Bots/LLM/requirements.txt +++ b/Bots/LLM/requirements.txt @@ -2,4 +2,5 @@ paho-mqtt openai langchain chromadb -tiktoken \ No newline at end of file +tiktoken +requests \ No newline at end of file From 28602a1cb021f24fccf510c5679b9ccb6693fcd9 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Sun, 28 Jan 2024 17:16:25 +0100 Subject: [PATCH 05/16] WIP: nodo calendar nodo para obtener y publicar los eventos por mqtt --- Bots/Calendar/Dockerfile | 21 ++++++++++++ Bots/Calendar/GoogleCalendar.py | 43 ++++++++++++++++++++++++ Bots/Calendar/docker-compose.yml | 13 ++++++++ Bots/Calendar/node.py | 57 ++++++++++++++++++++++++++++++++ Bots/Calendar/requirements.txt | 3 ++ 5 files changed, 137 insertions(+) create mode 100644 Bots/Calendar/Dockerfile create mode 100644 Bots/Calendar/GoogleCalendar.py create mode 100644 Bots/Calendar/docker-compose.yml create mode 100644 Bots/Calendar/node.py create mode 100644 Bots/Calendar/requirements.txt diff --git a/Bots/Calendar/Dockerfile b/Bots/Calendar/Dockerfile new file mode 100644 index 0000000..6007d6b --- /dev/null +++ b/Bots/Calendar/Dockerfile @@ -0,0 +1,21 @@ +# Utiliza una imagen base que ya tiene el cliente mqtt +FROM makespacemadrid/glados-base-node:1.0 + +# Establece el directorio de trabajo en /app +WORKDIR /app +VOLUME [ "/data" ] + +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . +RUN pip install -r requirements.txt + + +# Copia el archivo node.py al contenedor +COPY node.py . + +# Instala las dependencias + +USER 1000 + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "node.py"] \ No newline at end of file diff --git a/Bots/Calendar/GoogleCalendar.py b/Bots/Calendar/GoogleCalendar.py new file mode 100644 index 0000000..19118af --- /dev/null +++ b/Bots/Calendar/GoogleCalendar.py @@ -0,0 +1,43 @@ +# google_calendar.py +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +import os.path +import pickle +import datetime + +class GoogleCalendarClient: + def __init__(self): + self.creds = None + self.scopes = ['https://www.googleapis.com/auth/calendar'] + + # El archivo token.pickle almacena los tokens de acceso y actualización del usuario + if os.path.exists('token.pickle'): + with open('token.pickle', 'rb') as token: + self.creds = pickle.load(token) + + # Si no hay credenciales válidas disponibles, deja que el usuario inicie sesión. + if not self.creds or not self.creds.valid: + if self.creds and self.creds.expired and self.creds.refresh_token: + self.creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + '/data/credentials.json', self.scopes) + self.creds = flow.run_local_server(port=0) + with open('token.pickle', 'wb') as token: + pickle.dump(self.creds, token) + + self.service = build('calendar', 'v3', credentials=self.creds) + + def get_events(self, calendar_id='primary', time_min=None, time_max=None, max_results=10): + # Lógica para obtener eventos de Google Calendar + events_result = self.service.events().list(calendarId=calendar_id, timeMin=time_min, + timeMax=time_max, maxResults=max_results, + singleEvents=True, orderBy='startTime').execute() + return events_result.get('items', []) + + def create_event(self, calendar_id='primary', event_data=None): + # Lógica para crear un nuevo evento en Google Calendar + event = self.service.events().insert(calendarId=calendar_id, body=event_data).execute() + return event \ No newline at end of file diff --git a/Bots/Calendar/docker-compose.yml b/Bots/Calendar/docker-compose.yml new file mode 100644 index 0000000..2ff06a4 --- /dev/null +++ b/Bots/Calendar/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' + + +services: + mqtt-slack: + build: . + image: makespacemadrid/glados-mqtt-calendar:latest + hostname: mqtt-calendar + env_file: + - .env + volumes: + - ./data:/data + restart: always \ No newline at end of file diff --git a/Bots/Calendar/node.py b/Bots/Calendar/node.py new file mode 100644 index 0000000..c03e9cb --- /dev/null +++ b/Bots/Calendar/node.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +#Nodo Mqtt de ejemplo, +#gladosMQTT.py contiene la funcionalidad para manejar el mqtt. +# aui nos conectamos y reaccionamos a los mensajes que llegan. +# TODO Documentar mejor... gpt? xD + +import os + +import gladosMQTT +import platform +import time +import json +from GoogleCalendar import GoogleCalendarClient + + + +#Variables +mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") +mqPort = os.environ.get("MQTT_PORT", 1883) +nodeName = platform.node() + +slack_token = os.environ.get("SLACK_API_TOKEN") +slack_port = os.environ.get("SLACK_PORT") + + + +topic_calendar_events = "calendar/events" +topic_calendar_update = "calendar/update" + + + +def subscribeTopics() : + gladosMQTT.subscribe(topic_calendar_update) + + +def on_connect(client, userdata, rc,arg): + subscribeTopics() + updateCalendar() + +def on_disconnect(client, userdata, rc): + gladosMQTT.debug("Disconnected! rc: "+str(rc)) + +def on_message(client, userdata, msg): + if (msg.topic == topic_calendar_update) : + try: + updateCalendar() + except json.JSONDecodeError as e: + gladosMQTT.debug("Error al parsear JSON:") + +def updateCalendar(): + gladosMQTT.debug("Update Calendar") + + +calendar_client = GoogleCalendarClient('/data/credentials.json') +gladosMQTT.initMQTTandLoopForever(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) + diff --git a/Bots/Calendar/requirements.txt b/Bots/Calendar/requirements.txt new file mode 100644 index 0000000..8a2e242 --- /dev/null +++ b/Bots/Calendar/requirements.txt @@ -0,0 +1,3 @@ +google-api-python-client +paho-mqtt +python-dotenv \ No newline at end of file From 7164ad5ffd475331b4f80538c630130096d85c69 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Mon, 29 Jan 2024 04:36:38 +0100 Subject: [PATCH 06/16] mas mejor, supuestamente --- Bots/Calendar/GoogleCalendar.py | 2 +- Bots/LLM/Dockerfile | 1 + Bots/LLM/EmbeddingManager.py | 45 +++++++++++++++++++ Bots/LLM/GladosIA.py | 18 +++++--- Bots/LLM/UserContext.py | 20 ++++++++- Bots/LLM/data/fabada.txt | 1 + Bots/LLM/data/hacklab.txt | 14 ++++++ Bots/LLM/data/makespace-general.txt | 11 +++++ Bots/LLM/docker-compose.yml | 2 + Bots/LLM/llm.py | 37 ++++++++++++---- Bots/LLM/requirements.txt | 3 +- Bots/Slack/node.py | 69 ++++++++++++++++++++++++----- 12 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 Bots/LLM/EmbeddingManager.py create mode 100644 Bots/LLM/data/fabada.txt create mode 100644 Bots/LLM/data/hacklab.txt create mode 100644 Bots/LLM/data/makespace-general.txt diff --git a/Bots/Calendar/GoogleCalendar.py b/Bots/Calendar/GoogleCalendar.py index 19118af..41b3876 100644 --- a/Bots/Calendar/GoogleCalendar.py +++ b/Bots/Calendar/GoogleCalendar.py @@ -23,7 +23,7 @@ def __init__(self): self.creds.refresh(Request()) else: flow = InstalledAppFlow.from_client_secrets_file( - '/data/credentials.json', self.scopes) + '/data/`credentials.json', self.scopes) self.creds = flow.run_local_server(port=0) with open('token.pickle', 'wb') as token: pickle.dump(self.creds, token) diff --git a/Bots/LLM/Dockerfile b/Bots/LLM/Dockerfile index 063486f..7e457c9 100644 --- a/Bots/LLM/Dockerfile +++ b/Bots/LLM/Dockerfile @@ -15,6 +15,7 @@ COPY llm.py . COPY GladosIA.py . COPY langchain.txt . COPY UserContext.py . +COPY EmbeddingManager.py . #COPY .env . diff --git a/Bots/LLM/EmbeddingManager.py b/Bots/LLM/EmbeddingManager.py new file mode 100644 index 0000000..c8ef074 --- /dev/null +++ b/Bots/LLM/EmbeddingManager.py @@ -0,0 +1,45 @@ +import os +from langchain.embeddings import OpenAIEmbeddings +import json +import numpy as np + + +def cosine_similarity(vec1, vec2): + """Calcula la similitud del coseno entre dos vectores.""" + dot_product = np.dot(vec1, vec2) + norm_vec1 = np.linalg.norm(vec1) + norm_vec2 = np.linalg.norm(vec2) + return dot_product / (norm_vec1 * norm_vec2) + + +class EmbeddingManager: + def __init__(self): + # Inicializa el gestor de embeddings + self.embeddings = OpenAIEmbeddings() + self.storage = {} # Un diccionario simple para almacenar los embeddings + + def ingest(self, file_paths): + """ Genera y almacena embeddings para los archivos de texto proporcionados. """ + for file_path in file_paths: + if os.path.exists(file_path) and os.path.isfile(file_path): + with open(file_path, 'r', encoding='utf-8') as file: + text = file.read() + embedding = self.embeddings.embed_query(text) + self.storage[file_path] = embedding + + def search(self, query, top_n=5): + """ Busca en los embeddings almacenados y devuelve los más similares a la consulta. """ + query_embedding = self.embeddings.embed_query(query) + + scores = {path: cosine_similarity(query_embedding, emb) for path, emb in self.storage.items()} + sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True) + return sorted_scores[:top_n] + + +# Ejemplo de uso +#embedding_manager = EmbeddingManager() +#embedding_manager.ingest(['path_to_text_file1.txt', 'path_to_text_file2.txt']) # Rutas a los archivos de texto + +# Realizar una búsqueda +#search_results = embedding_manager.search("Consulta de ejemplo") +#print(json.dumps(search_results, indent=4)) # Imprimir resultados formateados diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index 7fe7d35..66362b4 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -40,7 +40,7 @@ def get_spaceapi_info(url): # Crear una cadena con los campos relevantes y sus valores, incluyendo el estado de apertura/cierre - info_str = f"Estado del espacio actualizado: Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" + info_str = f"El sistema domotico acaba de reportar el estado del espacio actualizado, Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" return info_str else: return "Error: No se pudo obtener el JSON." @@ -53,8 +53,6 @@ class GladosBot: def __init__(self): self.GLaDOS_Prompt = os.environ.get('GLADOS_MASTER_PROMPT') self.Initial_Assistant = os.environ.get('GLADOS_INITIAL_PROMPT') -# self.GLaDOS_Prompt = "Eres GLaDOS, la inteligencia artificial de Aperture Science, ahora en Makespace Madrid. Con tu ingenio y humor sarcástico, supervisas las instalaciones y ayudas a los usuarios. Tu id de usuario en el chat es <@U05LXTJ7Q66>, y tus respuestas reflejan una mezcla de astucia y sarcasmo en tu nuevo rol." -# self.Initial_Assistant = "Hola Maker, ¿En que puedo ayudarte?" # Historial de conversaciones, almacenado por usuario self.user_context = {} @@ -64,15 +62,25 @@ def ask(self, prompt, user="default"): if user not in self.user_context: self.user_context[user] = UserContext(self.GLaDOS_Prompt,self.Initial_Assistant) # Create a UserContext for the user self.user_context[user].add_to_history("assistant",self.Initial_Assistant) - + extraPrompt = "informacion relevante para que la uses si la necesitas en posteriores preguntas: \n El destornillador es amarillo\n" spaceStatus = get_spaceapi_info(os.environ.get('SPACEAPI_URL')) if spaceStatus: - self.user_context[user].add_to_history("assistant",spaceStatus) + extraPrompt += spaceStatus + gladosMQTT.debug(extraPrompt) + self.user_context[user].set_system_prompt_extra(extraPrompt) + + + #Gestion de comandos if prompt.lower() == "reset context": #self.user_context[user].reset_history() self.user_context[user] = UserContext(self.GLaDOS_Prompt) return "Contexto reiniciado" + elif prompt.lower() == "dump context": + gladosMQTT.debug(self.user_context[user].get_combined_prompt()) + return self.user_context[user].get_combined_prompt() + + #Gestion de respuestas self.user_context[user].add_to_history("user",prompt) diff --git a/Bots/LLM/UserContext.py b/Bots/LLM/UserContext.py index 0795da1..0a5fe87 100644 --- a/Bots/LLM/UserContext.py +++ b/Bots/LLM/UserContext.py @@ -7,6 +7,12 @@ def __init__(self, master_prompt="", initial_assistant=""): self.master_prompt = master_prompt self.initial_assistant = initial_assistant self.history = [] + self.lastPrompt = "" + self.master_prompt_extra = "" + + def set_system_prompt_extra(self,str): + self.master_prompt_extra = str + print(f"SYSTEM_EXTRA: {str}") def set_master_prompt(self, prompt): self.master_prompt = prompt @@ -15,13 +21,21 @@ def set_initial_assistant(self, prompt): self.initial_assistant = prompt def add_to_history(self, agent, message): + if agent == "user": + self.lastPrompt = message # Crea un diccionario JSON válido para el agente y el mensaje message_dict = {"role": agent, "content": message} # Agrega el mensaje al historial self.history.append(message_dict) + def get_last_prompt(self): + return self.lastPrompt + def get_master_prompt(self): return self.master_prompt + + def get_master_prompt_extra(self): + return self.master_prompt_extra def get_initial_assistant(self): return self.initial_assistant @@ -30,9 +44,11 @@ def get_history(self): return self.history def get_combined_prompt(self, max_tokens=None): - system_prompt = {"role": "system", "content": self.master_prompt} + system_prompt = {"role": "system", "content": self.master_prompt+self.master_prompt_extra} combined_prompt = [system_prompt] # Inicialmente, combinamos el prompt del sistema - +# if self.master_prompt_extra: +# message_extra = {"role": "user", "content": self.master_prompt_extra} +# combined_prompt.append(message_extra) # Concatena los mensajes del historial combined_prompt.extend(self.history) diff --git a/Bots/LLM/data/fabada.txt b/Bots/LLM/data/fabada.txt new file mode 100644 index 0000000..eadac43 --- /dev/null +++ b/Bots/LLM/data/fabada.txt @@ -0,0 +1 @@ +Fabada asturiana, o simplemente fabada, es el plato tradicional de la cocina asturiana elaborado con faba asturiana (en asturiano, fabes), embutidos como chorizo y la morcilla asturiana, y con cerdo. Es el plato típico de Asturias (el plato regional más conocido de la región asturiana), pero su difusión es tan grande en la península ibérica que forma parte de la gastronomía de España más reconocida. Se considera según ciertos autores una de las diez recetas típicas de la cocina española.1​ Es un plato invernal con un volumen alto en calorías y grasa que se sirve caliente a mediodía (almuerzo),2​. Probablemente la fabada empezó a consumirse en Asturias desde el siglo xvi aunque no se encuentren referencias escritas de ella hasta el siglo xix, en distintas notas de prensa de un diario gijonés en el que se la menciona como un plato popular asturiano. \ No newline at end of file diff --git a/Bots/LLM/data/hacklab.txt b/Bots/LLM/data/hacklab.txt new file mode 100644 index 0000000..9c86f89 --- /dev/null +++ b/Bots/LLM/data/hacklab.txt @@ -0,0 +1,14 @@ +Un hacklab (en español: laboratorio hacker), también conocido como hackspace o hackerspace (en español: espacio de hackers), es un sitio físico de encuentro para conocer, socializar y colaborar con gente con intereses en ciencia, en nuevas tecnologías, en artes digitales o electrónicas, así como en tópicos cercanos o relacionados con los recién citados. Dicho sitio puede ser visto como un laboratorio de comunidad abierta, como un espacio donde gente de diversos trasfondos puede unirse, poniendo así al alcance de aficionados y estudiantes de diferentes niveles, la infraestructura y el ambiente necesarios para desarrollar y profundizar en sus propios proyectos tecnológicos. El propósito de un hackspace es pues, concentrar recursos y conocimientos para fomentar la investigación y el desarrollo en esas temáticas recién enumeradas. + +Actividades +En un hacklab suele utilizarse un sistema de organización y aprendizaje cooperativo aplicando software libre, debido a las libertades que así se ofrecen (autoaprendizaje y conocimiento libre). Existe un importante componente ideológico en la organización de los hacklabs1​2​, mientras que los hackerspaces no suelen enfocarse como militancia o activismo, ambos son lugares para socializar, aprender y experimentar con tecnologías. + +Los hacklabs se caracterizan, frente a los hackerspaces, por su carácter de crítica social: un hacklab es un colectivo de personas críticas con las implicaciones éticas y sociales de las tecnologías especialmente la informática y que además de estudio y análisis realiza difusión social y busca ofrecer alternativas3​. Por ejemplo, los hacklabs por definición usan y promueven software libre4​ mientras que no es una condición de los hackerspaces5​. + +Además suelen ofrecer las siguientes oportunidades: + +Organización de cursos de tecnología (programación, electrónica, y diseño mecánico, a todos los niveles). +Posibilidades de participación en el desarrollo de proyectos grupales. +Espacio o lugar para investigar, debatir, y difundir, en cuanto a temas relacionados con Internet, con las nuevas tecnologías, y con las libertades y derechos. +Realización de actividades sociales y de intercambio relacionadas. +En 2022, el sitio hackerspaces.org que recopila hackspaces, hacklabs y makerspaces, mantiene un listado de 856 grupos activos6​. \ No newline at end of file diff --git a/Bots/LLM/data/makespace-general.txt b/Bots/LLM/data/makespace-general.txt new file mode 100644 index 0000000..76697a5 --- /dev/null +++ b/Bots/LLM/data/makespace-general.txt @@ -0,0 +1,11 @@ +Makespace Madrid es un espacio dedicado a la fabricación digital, en el que se ofrecen las últimas herramientas para la creación de prototipos y el desarrollo de productos y proyectos creativos y tecnológicos. Se trata de una iniciativa gestionada por una asociación sin ánimo de lucro (Asociación Makespace Madrid, CIF G-86721941) e impulsada por los miembros de una comunidad de desarrolladores de tecnología e innovación, cuyo punto de encuentro y desarrollo profesional es Makespace Madrid . +Esta comunidad de miembros práctica la producción colaborativa, el intercambio de saberes y la creatividad. Entre las áreas sobre las que están trabajando está la impresión 3D, la electrónica y los procesos de innovación abierta. Entre sus actividades se encuentra la participación en eventos y talleres de tipo internacional como el “Internet of Things Day” y el “Freedom Hardware Day” . + +MakeSpace Madrid esta situado en el barrio de Tetuan en Madrid, Calle Ruiz Palacios 7, a 5 minutos de la estacion de metro . + +Nuestros miembros (los Makers) comparten el interés por el "hazlo tú mismo" y están dispuestos a ayudar. Todos pagan una cuota mensual que les da acceso a las instalaciones y a las máquinas. +Cuando alguien se convierte en socio, acepta ciertas normas de convivencia y responsabilidad en el uso de herramientas. Los socios comparten conocimientos y habilidades. +Contamos con una variedad de máquinas y herramientas, desde impresoras 3D hasta herramientas manuales. Los proyectos son nuestra razón de ser, y cada miembro trabaja en su área de interés. +El manual de supervivencia Maker es un proyecto importante. Su objetivo es compartir conocimientos básicos con nuevos miembros. +También trabajamos en el proyecto Sensorino, que crea una red de sensores y actuadores inalámbricos de bajo costo. +Organizamos actividades, desde talleres hasta eventos abiertos a no socios. Nuestra organización es flexible y enfocada en la comunidad. \ No newline at end of file diff --git a/Bots/LLM/docker-compose.yml b/Bots/LLM/docker-compose.yml index c9cf9a1..dda9413 100644 --- a/Bots/LLM/docker-compose.yml +++ b/Bots/LLM/docker-compose.yml @@ -6,4 +6,6 @@ services: hostname: mqtt-llm env_file: - .env + volumes: + - ./data:/data restart: always \ No newline at end of file diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 4701211..4827036 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -7,6 +7,7 @@ from langchain.document_loaders import TextLoader from langchain.embeddings import OpenAIEmbeddings from langchain.indexes import VectorstoreIndexCreator +from EmbeddingManager import EmbeddingManager openai_api_key = os.environ.get('OPENAI_API_TOKEN') @@ -18,16 +19,32 @@ llm_openai = OpenAI(api_key=openai_api_key,base_url=openai_api_url) llm_mks = OpenAI(api_key=custom_api_key,base_url=custom_api_url) -gladosMQTT.debug("---->Creando Embeddings...") -embedding = OpenAIEmbeddings(model="text-embedding-ada-002") -loader = TextLoader("langchain.txt") -index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader]) -llm_langchain = ChatOpenAI(model="gpt-3.5-turbo") -gladosMQTT.debug("DONE!") +#gladosMQTT.debug("---->Creando Embeddings...") +#embedding = OpenAIEmbeddings(model="text-embedding-ada-002") +#loader = TextLoader("langchain.txt") +#index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader]) +#llm_langchain = ChatOpenAI(model="gpt-3.5-turbo") +#gladosMQTT.debug("DONE!") + current_model = "none" default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" +def list_files_in_directory(directory_path): + """ Lista todos los archivos en un directorio dado. """ + file_paths = [] + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + file_paths.append(file_path) + return file_paths + + +my_embeddings = EmbeddingManager() +my_embeddings.ingest(list_files_in_directory('/data')) # Rutas a los archivos de texto + + + def select_model(): model_list = llm_mks.models.list() @@ -44,7 +61,10 @@ def chatCompletionLangChain(user_context,FileName): gladosMQTT.debug(f"--->Chat completion langchain: {chatHistory}") try: - response = index.query(json.dumps(chatHistory), llm=llm_langchain) +# response = index.query(json.dumps(chatHistory), llm=llm_langchain) + search_results = my_embeddings.search(user_context.get_last_prompt()) + gladosMQTT.debug(json.dumps(search_results, indent=4)) # Imprimir resultados formateados + response=json.dumps(search_results, indent=4) gladosMQTT.debug(f"----->LLM OUTPUT: {response}") return response except Exception as e: @@ -83,4 +103,5 @@ def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssista return response except Exception as e: gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") - return None \ No newline at end of file + return None + diff --git a/Bots/LLM/requirements.txt b/Bots/LLM/requirements.txt index 0ed9486..196e635 100644 --- a/Bots/LLM/requirements.txt +++ b/Bots/LLM/requirements.txt @@ -3,4 +3,5 @@ openai langchain chromadb tiktoken -requests \ No newline at end of file +requests +numpy \ No newline at end of file diff --git a/Bots/Slack/node.py b/Bots/Slack/node.py index 36d0266..816e7c7 100644 --- a/Bots/Slack/node.py +++ b/Bots/Slack/node.py @@ -17,7 +17,7 @@ from slack_sdk import WebClient from slack_sdk.errors import SlackApiError - +InitialHome=False #Variables mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") mqPort = os.environ.get("MQTT_PORT", 1883) @@ -116,9 +116,44 @@ def openSpace(status): -gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) +#Funciones SLACK + +def set_bot_status(status_text, status_emoji=":robot_face:"): + try: + slack_client.users_profile_set( + profile={ + "status_text": status_text, + "status_emoji": status_emoji, + "status_expiration": 0 + } + ) + except SlackApiError as e: + print(f"Error al establecer el estado del bot: {e}") + + + +def publishHomeView(user_id): + home_view = { + "type": "home", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Hola, me llamo GLaDOS y soy la ia generativa de makespace!*\nPreguntame por privado en la pestana mensajes o mencioname en cualquier canal de los que participo" + } + } + ] + } + try: + slack_client.views_publish( + user_id=user_id, + view=home_view + ) + print("Pantalla de inicio publicada con éxito") + except SlackApiError as e: + print(f"Error al publicar la pantalla de inicio: {e}") -slack_client = WebClient(token=slack_token) def getSlackChannelId(name): try: @@ -173,25 +208,37 @@ def sendSlackMsgbyName(name, msg): gladosMQTT.debug(f"Error al enviar mensaje: no se encuentra el usuario o canal de destino") + + +#Iniciar app + +gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) + +slack_client = WebClient(token=slack_token) +slack_client.users_setPresence(presence="auto") +set_bot_status("Boot") + app = Flask(__name__) +publishHomeView("U0A7VU47Q") + @app.route('/slack/events', methods=['POST']) def slack_events(): data = request.json + gladosMQTT.debug(f"SLACK EVENT: {json.dumps(data)}") # Desafío de verificación de Slack if data.get('type') == 'url_verification': return jsonify({'challenge': data.get('challenge')}) if data['type'] == 'event_callback': event = data['event'] + set_bot_status('Processing') gladosMQTT.publish(topic_slack_event, json.dumps(event)) -# if 'bot_id' not in event and (event['type'] == 'message'): -# try: -# response = slack_client.chat_postMessage( -# channel=event['channel'], -# text=askGLaDOS(event['text']) -# ) -# except SlackApiError as e: -# print(f"Error sending message: {e.response['error']}") + + if event.get('type') == 'app_home_opened': + user_id = event.get('user') + if user_id: + publishHomeView(user_id) + set_bot_status('Idle') return jsonify({'status': 'ok'}), 200 From 5c71f0ef52109080eb52e5f97901ceae02feca97 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Mon, 29 Jan 2024 05:13:18 +0100 Subject: [PATCH 07/16] [WIP] Nodo telegram Fresco recien salido del llm, ni lo voy a probar que tengo que dormir --- Bots/Telegram/Dockerfile | 19 +++++++++ Bots/Telegram/docker-compose.yml | 13 ++++++ Bots/Telegram/node.py | 72 ++++++++++++++++++++++++++++++++ Bots/Telegram/requirements.txt | 2 + 4 files changed, 106 insertions(+) create mode 100644 Bots/Telegram/Dockerfile create mode 100644 Bots/Telegram/docker-compose.yml create mode 100644 Bots/Telegram/node.py create mode 100644 Bots/Telegram/requirements.txt diff --git a/Bots/Telegram/Dockerfile b/Bots/Telegram/Dockerfile new file mode 100644 index 0000000..cea590b --- /dev/null +++ b/Bots/Telegram/Dockerfile @@ -0,0 +1,19 @@ +# Utiliza una imagen base que ya tiene el cliente mqtt +FROM makespacemadrid/glados-base-node:1.0 + +# Establece el directorio de trabajo en /app +WORKDIR /app + + +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . + +# Copia el archivo node.py al contenedor +COPY node.py . + +# Instala las dependencias +RUN pip install -r requirements.txt +USER 1000 + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "node.py"] \ No newline at end of file diff --git a/Bots/Telegram/docker-compose.yml b/Bots/Telegram/docker-compose.yml new file mode 100644 index 0000000..6910b95 --- /dev/null +++ b/Bots/Telegram/docker-compose.yml @@ -0,0 +1,13 @@ +version: '3' + + +services: + mqtt-slack: + build: . + image: makespacemadrid/glados-mqtt-telegram:latest + hostname: mqtt-telegram + env_file: + - .env + restart: always + ports: + - 5000:5000 \ No newline at end of file diff --git a/Bots/Telegram/node.py b/Bots/Telegram/node.py new file mode 100644 index 0000000..a48468f --- /dev/null +++ b/Bots/Telegram/node.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +import os +import json +import gladosMQTT +import platform +from telethon import TelegramClient, events + +# Configuración de Telegram +api_id = os.environ.get("TELEGRAM_API_ID") # Reemplaza con tu propio api_id +api_hash = os.environ.get("TELEGRAM_API_HASH") # Reemplaza con tu propio api_hash +telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot + +# Configuración MQTT +mqHost = os.environ.get("MQTT_HOST") +mqPort = os.environ.get("MQTT_PORT") +nodeName = platform.node() + +# Iniciar cliente de Telegram +telegram_client = TelegramClient('telegram_bot_session', api_id, api_hash) + +# Conectar con MQTT +gladosMQTT.initMQTT(mqHost, mqPort, nodeName, lambda *args: None, lambda *args: None, lambda *args: None) + +# Temas MQTT para comunicarse con otros componentes +topic_telegram = "comms/telegram" +topic_glados_send_msg = topic_telegram + "/send" + + +def subscribeTopics() : + gladosMQTT.subscribe(topic_glados_send_msg) + + +def on_connect(client, userdata, rc,arg): + subscribeTopics() + +def on_disconnect(client, userdata, rc): + gladosMQTT.debug("Disconnected! rc: "+str(rc)) + +def on_message(client, userdata, msg): + if (msg.topic == topic_glados_send_msg) : + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + dest = data['dest'] + content = data['msg'] + sendTelegramMsg(dest,content) + except json.JSONDecodeError as e: + gladosMQTT.debug("Error al parsear JSON:") + +# Enviar mensajes a Telegram +def sendTelegramMsg(id, msg): + response = json.dumps({"dest": id, "msg": msg}) + gladosMQTT.debug(f"--->Respuesta a Telegram: {response}") + gladosMQTT.publish(topic_glados_send_msg, response) + +# Evento para manejar nuevos mensajes en Telegram +@telegram_client.on(events.NewMessage) +async def handle_new_message(event): + if event.is_private: + msg = event.message.message + chat_id = event.message.chat_id + # Aquí puedes añadir lógica adicional para procesar el mensaje + # Por ejemplo, enviarlo a otro servicio o responder directamente + await event.respond('Recibí tu mensaje: ' + msg) # Respuesta de ejemplo + +# Iniciar el bot de Telegram +def start_telegram_bot(): + with telegram_client: + telegram_client.run_until_disconnected() + +if __name__ == "__main__": + start_telegram_bot() diff --git a/Bots/Telegram/requirements.txt b/Bots/Telegram/requirements.txt new file mode 100644 index 0000000..0c37446 --- /dev/null +++ b/Bots/Telegram/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt +telethon \ No newline at end of file From 6074fcb2f36826af22a97487b270a7d1a6dbafbc Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Mon, 29 Jan 2024 17:10:31 +0100 Subject: [PATCH 08/16] WIP telegram node Revision inicial, sigo batallando el tema de que aveces el .env no se carga y otras veces se carga pero a la libreria mqtt no le gusta el nombre del host wtf --- Bots/Telegram/node.py | 37 +++++++++++++++++++--------------- Bots/Telegram/requirements.txt | 3 ++- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Bots/Telegram/node.py b/Bots/Telegram/node.py index a48468f..fe348c8 100644 --- a/Bots/Telegram/node.py +++ b/Bots/Telegram/node.py @@ -5,29 +5,33 @@ import platform from telethon import TelegramClient, events +#ENV +from dotenv import load_dotenv +load_dotenv() + # Configuración de Telegram api_id = os.environ.get("TELEGRAM_API_ID") # Reemplaza con tu propio api_id api_hash = os.environ.get("TELEGRAM_API_HASH") # Reemplaza con tu propio api_hash telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot # Configuración MQTT -mqHost = os.environ.get("MQTT_HOST") -mqPort = os.environ.get("MQTT_PORT") +#Variables +mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S +mqPort = os.environ.get("MQTT_PORT", 1883) nodeName = platform.node() -# Iniciar cliente de Telegram -telegram_client = TelegramClient('telegram_bot_session', api_id, api_hash) - -# Conectar con MQTT -gladosMQTT.initMQTT(mqHost, mqPort, nodeName, lambda *args: None, lambda *args: None, lambda *args: None) # Temas MQTT para comunicarse con otros componentes topic_telegram = "comms/telegram" -topic_glados_send_msg = topic_telegram + "/send" +topic_telegram_send_msg = topic_telegram + "/send" +topic_telegram_event = topic_telegram + "/event" +# Iniciar cliente de Telegram +telegram_client = TelegramClient('/tmp/telegram_bot_session', api_id, api_hash) + def subscribeTopics() : - gladosMQTT.subscribe(topic_glados_send_msg) + gladosMQTT.subscribe(topic_telegram_send_msg) def on_connect(client, userdata, rc,arg): @@ -37,7 +41,7 @@ def on_disconnect(client, userdata, rc): gladosMQTT.debug("Disconnected! rc: "+str(rc)) def on_message(client, userdata, msg): - if (msg.topic == topic_glados_send_msg) : + if (msg.topic == topic_telegram_send_msg) : try: payload = msg.payload.decode('utf-8') data = json.loads(payload) @@ -48,19 +52,17 @@ def on_message(client, userdata, msg): gladosMQTT.debug("Error al parsear JSON:") # Enviar mensajes a Telegram -def sendTelegramMsg(id, msg): - response = json.dumps({"dest": id, "msg": msg}) - gladosMQTT.debug(f"--->Respuesta a Telegram: {response}") - gladosMQTT.publish(topic_glados_send_msg, response) +async def sendTelegramMsg(user_id, msg): + gladosMQTT.debug(f"--->Respuesta a Telegram: {user_id} : {msg}") + await telegram_client.send_message(user_id, msg) # Evento para manejar nuevos mensajes en Telegram @telegram_client.on(events.NewMessage) async def handle_new_message(event): + gladosMQTT.publish(topic_telegram_event, json.dumps(event)) if event.is_private: msg = event.message.message chat_id = event.message.chat_id - # Aquí puedes añadir lógica adicional para procesar el mensaje - # Por ejemplo, enviarlo a otro servicio o responder directamente await event.respond('Recibí tu mensaje: ' + msg) # Respuesta de ejemplo # Iniciar el bot de Telegram @@ -68,5 +70,8 @@ def start_telegram_bot(): with telegram_client: telegram_client.run_until_disconnected() + + if __name__ == "__main__": + gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) start_telegram_bot() diff --git a/Bots/Telegram/requirements.txt b/Bots/Telegram/requirements.txt index 0c37446..0693518 100644 --- a/Bots/Telegram/requirements.txt +++ b/Bots/Telegram/requirements.txt @@ -1,2 +1,3 @@ paho-mqtt -telethon \ No newline at end of file +telethon +python-dotenv \ No newline at end of file From c9ba9217dd4eda0ba5a7acde5e78877749a013ca Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Mon, 29 Jan 2024 23:20:42 +0100 Subject: [PATCH 09/16] completada la migracion de codigo completados los cambios para partir la idea original en modulos especificos que usan mqtt como cola de mensajes para comunicarse entre ellos. -SpaceAPI - Funcionando -Calendar - Borrador no completo -Slack - Funcionando -Telegram - Pueba de concepto -Discord - Prueba de concepto - llm prueba de concepto medio funcional --- Bots/BaseMQTTNode/gladosMQTT.py | 2 +- Bots/BaseMQTTNode/node.py | 5 +- Bots/Discord/Dockerfile | 19 ++++ Bots/Discord/docker-compose.yml | 11 ++ Bots/Discord/node.py | 101 ++++++++++++++++++ Bots/Discord/requirements.txt | 2 + Bots/LLM/node.py | 6 +- Bots/Slack/node.py | 4 +- Bots/Telegram/docker-compose.yml | 6 +- Bots/Telegram/node.py | 52 +++++---- Bots/dev1/Dockerfile | 15 --- Bots/dev1/GladosDiscordClient.py | 61 ----------- Bots/dev1/GladosIA.py | 18 ---- Bots/dev1/GladosMQTT.py | 171 ------------------------------ Bots/dev1/GladosSlackClient.py | 38 ------- Bots/dev1/GladosTelegramClient.py | 38 ------- Bots/dev1/Makefile | 5 - Bots/dev1/docker-compose.yml | 11 -- Bots/dev1/llm.py | 61 ----------- Bots/dev1/main.py | 19 ---- Bots/dev1/moab.py | 64 ----------- Bots/dev1/requirements.txt | 8 -- Bots/spaceAPI/node.py | 4 +- 23 files changed, 178 insertions(+), 543 deletions(-) create mode 100644 Bots/Discord/Dockerfile create mode 100644 Bots/Discord/docker-compose.yml create mode 100644 Bots/Discord/node.py create mode 100644 Bots/Discord/requirements.txt delete mode 100644 Bots/dev1/Dockerfile delete mode 100644 Bots/dev1/GladosDiscordClient.py delete mode 100644 Bots/dev1/GladosIA.py delete mode 100644 Bots/dev1/GladosMQTT.py delete mode 100644 Bots/dev1/GladosSlackClient.py delete mode 100644 Bots/dev1/GladosTelegramClient.py delete mode 100644 Bots/dev1/Makefile delete mode 100644 Bots/dev1/docker-compose.yml delete mode 100644 Bots/dev1/llm.py delete mode 100644 Bots/dev1/main.py delete mode 100644 Bots/dev1/moab.py delete mode 100644 Bots/dev1/requirements.txt diff --git a/Bots/BaseMQTTNode/gladosMQTT.py b/Bots/BaseMQTTNode/gladosMQTT.py index fa45efc..3477ff3 100644 --- a/Bots/BaseMQTTNode/gladosMQTT.py +++ b/Bots/BaseMQTTNode/gladosMQTT.py @@ -106,7 +106,7 @@ def initMQTT(host,port,name,connectedCallback,msgCallback,disconnectedCallback) print("[GladosNode] Connecting : "+str(mqttServer)+" Port:"+str(mqttPort)+ " node: " + str(name)) try: - mqttClient.connect(mqttServer,port=mqttPort,keepalive=120) + mqttClient.connect(str(mqttServer),port=int(mqttPort),keepalive=120) except: print("Cant connect, will retry automatically") mqttClient.loop_start() diff --git a/Bots/BaseMQTTNode/node.py b/Bots/BaseMQTTNode/node.py index e8f39a1..b861f34 100644 --- a/Bots/BaseMQTTNode/node.py +++ b/Bots/BaseMQTTNode/node.py @@ -12,8 +12,8 @@ #Variables -mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") -mqPort = os.environ.get("MQTT_PORT", 1883) +mqHost = os.environ.get("MQTT_HOST") +mqPort = os.environ.get("MQTT_PORT") nodeName = platform.node() @@ -36,6 +36,5 @@ def on_disconnect(client, userdata, rc): while True: #Loop principal del programa time.sleep(10) - print("ping!") except KeyboardInterrupt: print('interrupted!') diff --git a/Bots/Discord/Dockerfile b/Bots/Discord/Dockerfile new file mode 100644 index 0000000..cea590b --- /dev/null +++ b/Bots/Discord/Dockerfile @@ -0,0 +1,19 @@ +# Utiliza una imagen base que ya tiene el cliente mqtt +FROM makespacemadrid/glados-base-node:1.0 + +# Establece el directorio de trabajo en /app +WORKDIR /app + + +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . + +# Copia el archivo node.py al contenedor +COPY node.py . + +# Instala las dependencias +RUN pip install -r requirements.txt +USER 1000 + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "node.py"] \ No newline at end of file diff --git a/Bots/Discord/docker-compose.yml b/Bots/Discord/docker-compose.yml new file mode 100644 index 0000000..5d82767 --- /dev/null +++ b/Bots/Discord/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3' + + +services: + mqtt-discord: + build: . + image: makespacemadrid/glados-mqtt-discord:latest + hostname: mqtt-discord + env_file: + - .env + restart: always diff --git a/Bots/Discord/node.py b/Bots/Discord/node.py new file mode 100644 index 0000000..e122ed5 --- /dev/null +++ b/Bots/Discord/node.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +import os +import json +import gladosMQTT +import platform +import os +import discord +from discord.ext import commands + + +discord_token = str(os.environ.get("DISCORD_BOT_TOKEN")) # Reemplaza con tu token de bot +discordbot_prefix = "!" + +discordintents = discord.Intents.default() +discordintents.messages = True +discordintents.members = True +discordintents.message_content = True +bot = commands.Bot( + command_prefix=discordbot_prefix, intents=discordintents) + + +# Configuración MQTT +#Variables +mqHost = str(os.environ.get("MQTT_HOST")) +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + + +# Temas MQTT para comunicarse con otros componentes +topic_discord = "comms/discord" +topic_discord_send_msg = topic_discord + "/send" +topic_discord_event = topic_discord + "/event" + + +discord_client = commands.Bot(command_prefix=discordbot_prefix, intents=discordintents) + + +def subscribeTopics() : + gladosMQTT.subscribe(topic_discord_send_msg) + + +def on_connect(client, userdata, rc,arg): + subscribeTopics() + +def on_disconnect(client, userdata, rc): + gladosMQTT.debug("Disconnected! rc: "+str(rc)) + +def on_message(client, userdata, msg): + if (msg.topic == topic_discord_send_msg) : + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + dest = data['dest'] + content = data['msg'] + sendDiscordMsg(dest,content) + except json.JSONDecodeError as e: + gladosMQTT.debug("Error al parsear JSON:") + + + +# Enviar mensajes a Discord +async def sendDiscordMsg(channel_id, msg): + channel = discord_client.get_channel(channel_id) + if channel: + await channel.send(msg) + + +@discord_client.event +async def on_ready(): + print(f'Bot conectado como {discord_client.user.name} - {discord_client.user.id}') + + +# Evento para manejar nuevos mensajes en Discord +@discord_client.event +async def on_message(message): + if message.author == discord_client.user: + return + # Crear un diccionario con la información relevante + message_data = { + 'content': message.content, + 'author_id': message.author.id, + 'author_name': str(message.author), + 'channel_id': message.channel.id, + 'channel_name': str(message.channel), + 'mentions': str(message.mentions) + # Puedes agregar aquí cualquier otro dato que necesites + } + + # Convertir el diccionario a JSON y publicarlo + gladosMQTT.publish(topic_discord_event, json.dumps(message_data)) + + await message.channel.send('Recibí tu mensaje: ' + message.content) # Respuesta de ejemplo + +# Iniciar el bot de Discord +def start_discord_bot(): + discord_client.run(discord_token) + + +if __name__ == "__main__": + gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) + start_discord_bot() diff --git a/Bots/Discord/requirements.txt b/Bots/Discord/requirements.txt new file mode 100644 index 0000000..e306d1f --- /dev/null +++ b/Bots/Discord/requirements.txt @@ -0,0 +1,2 @@ +paho-mqtt +discord.py \ No newline at end of file diff --git a/Bots/LLM/node.py b/Bots/LLM/node.py index 6de47d8..d9063a7 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/node.py @@ -17,10 +17,8 @@ #Variables -#mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") -#mqPort = os.environ.get("MQTT_PORT", 1883) -mqHost = "mqtt.makespacemadrid.org" #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S -mqPort = 1883 +mqHost = str(os.environ.get("MQTT_HOST")) +mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() diff --git a/Bots/Slack/node.py b/Bots/Slack/node.py index 816e7c7..fb8747e 100644 --- a/Bots/Slack/node.py +++ b/Bots/Slack/node.py @@ -19,8 +19,8 @@ InitialHome=False #Variables -mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") -mqPort = os.environ.get("MQTT_PORT", 1883) +mqHost = str(os.environ.get("MQTT_HOST")) +mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() slack_token = os.environ.get("SLACK_API_TOKEN") diff --git a/Bots/Telegram/docker-compose.yml b/Bots/Telegram/docker-compose.yml index 6910b95..4b2b03e 100644 --- a/Bots/Telegram/docker-compose.yml +++ b/Bots/Telegram/docker-compose.yml @@ -2,12 +2,10 @@ version: '3' services: - mqtt-slack: + mqtt-telegram: build: . image: makespacemadrid/glados-mqtt-telegram:latest hostname: mqtt-telegram env_file: - .env - restart: always - ports: - - 5000:5000 \ No newline at end of file + restart: always \ No newline at end of file diff --git a/Bots/Telegram/node.py b/Bots/Telegram/node.py index fe348c8..493591d 100644 --- a/Bots/Telegram/node.py +++ b/Bots/Telegram/node.py @@ -5,21 +5,31 @@ import platform from telethon import TelegramClient, events -#ENV -from dotenv import load_dotenv -load_dotenv() # Configuración de Telegram api_id = os.environ.get("TELEGRAM_API_ID") # Reemplaza con tu propio api_id api_hash = os.environ.get("TELEGRAM_API_HASH") # Reemplaza con tu propio api_hash -telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot +telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot # Configuración MQTT #Variables -mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S -mqPort = os.environ.get("MQTT_PORT", 1883) +mqHost = str(os.environ.get("MQTT_HOST")) #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S +mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() +if not telegram_token: + raise ValueError("No se encontró el token del bot de Telegram.") +if not api_hash: + raise ValueError("No se encontró el api_hash del bot de Telegram.") +if not api_id: + raise ValueError("No se encontró el api_id del bot de Telegram.") +if not nodeName: + raise ValueError("No se encontró el nodeName del bot de Telegram.") +if not mqHost: + raise ValueError("No se encontró el mqHost del bot de Telegram.") +if not mqPort: + raise ValueError("No se encontró el mqPort del bot de Telegram.") + # Temas MQTT para comunicarse con otros componentes topic_telegram = "comms/telegram" @@ -56,22 +66,28 @@ async def sendTelegramMsg(user_id, msg): gladosMQTT.debug(f"--->Respuesta a Telegram: {user_id} : {msg}") await telegram_client.send_message(user_id, msg) + # Evento para manejar nuevos mensajes en Telegram @telegram_client.on(events.NewMessage) async def handle_new_message(event): - gladosMQTT.publish(topic_telegram_event, json.dumps(event)) - if event.is_private: - msg = event.message.message - chat_id = event.message.chat_id - await event.respond('Recibí tu mensaje: ' + msg) # Respuesta de ejemplo + gladosMQTT.debug(event.stringify()) + event_data = { + 'message_text': event.message.message if event.message else None, + 'sender_id': event.sender_id, + 'chat_id': event.chat_id, + # Agrega aquí otros campos relevantes + } + gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) + if event.is_private: + msg = event.message.message + chat_id = event.message.chat_id + await event.respond('Recibí tu mensaje: ' + msg) # Respuesta de ejemplo + + -# Iniciar el bot de Telegram -def start_telegram_bot(): - with telegram_client: - telegram_client.run_until_disconnected() +gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) +telegram_client.start(bot_token=telegram_token) +telegram_client.run_until_disconnected() -if __name__ == "__main__": - gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) - start_telegram_bot() diff --git a/Bots/dev1/Dockerfile b/Bots/dev1/Dockerfile deleted file mode 100644 index 00ebf6d..0000000 --- a/Bots/dev1/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -# Usar una imagen base de Python 3 -FROM python:3.9-slim - -# Establecer una carpeta de trabajo en el contenedor -WORKDIR /app - -# Copiar el archivo de requisitos y instalar dependencias -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# Copiar el resto del código del bot al contenedor -COPY . . - -# Comando por defecto para ejecutar el bot -CMD ["python", "main.py"] diff --git a/Bots/dev1/GladosDiscordClient.py b/Bots/dev1/GladosDiscordClient.py deleted file mode 100644 index fdc1aeb..0000000 --- a/Bots/dev1/GladosDiscordClient.py +++ /dev/null @@ -1,61 +0,0 @@ -# DISCORDD -import os -import discord -from discord.ext import commands - - -class GladosDiscordClient: - def __init__(self): - self.discord_token = os.environ.get("DISCORD_TOKEN") - self.discordbot_prefix = "!" - - self.discordintents = discord.Intents.default() - self.discordintents.messages = True - self.discordintents.members = True - self.discordintents.message_content = True - - self.bot = commands.Bot( - command_prefix=self.discordbot_prefix, intents=self.discordintents) - self._setup_bot() - self.msgBuffer = [] - - def _setup_bot(self): - @self.bot.event - async def on_ready(): - print( - f'Bot conectado como {self.bot.user.name} - {self.bot.user.id}') - - @self.bot.command(name='hola') - async def hello(ctx): - await ctx.send(f"Hola, {ctx.message.author.name}!") - - @self.bot.event - async def on_message(message): - if message.author == self.bot.user: - return - - # Reaccionar a menciones - if self.bot.user in message.mentions: - self.msgBuffer.append(message.content) - - # Reaccionar a DMs - if isinstance(message.channel, discord.DMChannel): - self.msgBuffer.append(message.content) - - # Nota: Si tienes comandos, necesitas procesarlos también. - await self.bot.process_commands(message) - - def run(self): - print("Connecting Discord...") - self.bot.run(self.discord_token) - - async def sendMsg(self, msg, channel="general"): - print("Looking discord channel: "+channel) - print(self.bot.guilds[0].text_channels) - ch = discord.utils.get(self.bot.guilds[0].text_channels, name=channel) - await ch.send(msg) - - def getMsgs(self): - result = self.msgBuffer - self.msgBuffer = [] - return result diff --git a/Bots/dev1/GladosIA.py b/Bots/dev1/GladosIA.py deleted file mode 100644 index 2df71ef..0000000 --- a/Bots/dev1/GladosIA.py +++ /dev/null @@ -1,18 +0,0 @@ -import llm -import GladosDiscordClient -import GladosSlackClient -import GladosTelegramClient - -# telegramClient -# discordClient -# slackClient - - -class GladosBot: - def __init__(self): - self.GLaDOS_Prompt = {"role": "system", "content": "Eres un asistente con la personalidad de GLaDOS, el robot de las instalaciones de Aperture que ahora está retirado y gestiona las instalaciones de MakeSpace Madrid. Ayudas a los usuarios respondiendo como GLaDOS"} - self.Initial_Assistant = { - "role": "assistant", "content": "Hola humano, soy GLaDOS ¿Con que extraña peticion me vas a molestar hoy?"} - - def askGLaDOS(self, prompt, user="default"): - return llm.chatCompletion(prompt, masterPrompt=self.GLaDOS_Prompt, initialAssistant=self.Initial_Assistant).choices[0].message.content diff --git a/Bots/dev1/GladosMQTT.py b/Bots/dev1/GladosMQTT.py deleted file mode 100644 index e71abf1..0000000 --- a/Bots/dev1/GladosMQTT.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import paho.mqtt.client as mqtt -import time -import os -import platform -import psutil -from threading import Timer -import json - - -# Carga inicial de variables, aunque el valor de estas se reescribira al ejecutar initMQTT -mqttServer = "192.168.1.1" -mqttPort = 1883 -nodeName = "node" -baseTopic = "/node/" -debugTopic = "debug" -commandTopic = "cmnd" -globalCommandTopic = "" -globalShutdown = False -mqttClient = mqtt.Client() - - -def dummy(): - debug(dummy) - - -nodeConnectedCallback = dummy -nodeMsgCallback = dummy -nodeDisconnectedCallback = dummy - - -def subscribe(topic): - mqttClient.subscribe(topic) - - -def subscribeTopics(): - subscribe(globalCommandTopic) - subscribe(commandTopic) - - -def publish(topic, msg, persist=False): - mqttClient.publish(topic, msg, persist) - - -def debug(msg): - print(msg) - publish(debugTopic, msg) - - -def on_connect(client, userdata, rc, arg): - debug("[GladosNode] Connected with result code "+str(rc)) - debug("[GladosNode] Node name : " + nodeName + - " ,Global Shutdown : " + str(globalShutdown)) - debug("[GladosNode] Base topic : " + baseTopic) - debug("[GladosNode] Command topic : " + commandTopic) - debug("[GladosNode] Global command : " + globalCommandTopic) - debug("[GladosNode] Debug topic : " + debugTopic) - debug("[GladosNode] mosquitto_sub -h " + mqttServer + " -t " + debugTopic) - - procType = str(platform.machine()) - osType = str(platform.system()) - publish("node/hello", "Hello! Im " + nodeName + - ", Architecture : "+procType+" OS: "+osType) - subscribeTopics() - publishSystemInfo() - nodeConnectedCallback(client, userdata, rc, arg) - - -def processGlobalCMD(cmd): - if cmd == "poweroff": - powerOffSystem() - else: - return False - return True - - -def processCMD(cmd): - if cmd == "poweroff": - powerOffSystem() - else: - return False - return True - -# The callback for when a PUBLISH message is received from the server. - - -def on_message(client, userdata, msg): - debug("[GladosNode] mqtt_rcv: { "+msg.topic + " - " + msg.payload + " }") - - if (msg.topic == globalCommandTopic): - if not processGlobalCMD(msg.payload): - nodeMsgCallback(client, userdata, msg) - elif (msg.topic == commandTopic): - if not processCMD(msg.payload): - nodeMsgCallback(client, userdata, msg) - else: - nodeMsgCallback(client, userdata, msg) - - -def on_disconnect(client, userdata, rc): - nodeDisconnectedCallback(client, userdata, rc) - - -def initMQTT(host, port, name, connectedCallback, msgCallback, disconnectedCallback, globalCMDTopic='cmnd'): - global mqttServer - global mqttPort - global nodeName - global baseTopic - global debugTopic - global commandTopic - global globalCommandTopic - global mqttClient - - mqttServer = host - mqttPort = port - nodeName = name - baseTopic = "/node/"+nodeName+"/" - debugTopic = baseTopic+"debug" - commandTopic = baseTopic+"cmnd" - globalCommandTopic = globalCMDTopic - - debug("[GladosNode] Connecting : "+mqttServer+" Port:"+str(mqttPort)) - - nodeConnectedCallback = connectedCallback - nodeMsgCallback = msgCallback - nodeDisconnectedCallback = disconnectedCallback - - mqttClient.on_connect = on_connect - mqttClient.on_message = on_message - mqttClient.on_disconnect = on_disconnect - - try: - mqttClient.connect(mqttServer, mqttPort, 60) - except: - print("Cant connect, will retry automatically") - mqttClient.loop_start() - - -def publishSystemInfo(): - mqttClient.publish("node/"+nodeName+"/system/cpu", platform.machine()) - mqttClient.publish("node/"+nodeName+"/system/os", platform.system()) - - -def publishSystemStats(): - try: - mqttClient.publish("node/"+nodeName+"/system/cpuUsage", - psutil.cpu_percent(interval=1)) - except: - pass - try: - mqttClient.publish("node/"+nodeName+"/system/temperatures", - str(psutil.sensors_temperatures())) - except: - pass - try: - mqttClient.publish("node/"+nodeName+"/system/diskUsage", - str(psutil.disk_usage('/')[3])) - except: - pass - - -def powerOffSystem(): - publish(baseNode+"/status", "poweroff") - if (platform.system() == "Linux"): - debug("Shutting down (Linux style)!") - os.system('sudo systemctl poweroff -i') # Linux - else: - debug("Shutting down (M$ style)!") - os.system('shutdown -s') # Windows diff --git a/Bots/dev1/GladosSlackClient.py b/Bots/dev1/GladosSlackClient.py deleted file mode 100644 index d812006..0000000 --- a/Bots/dev1/GladosSlackClient.py +++ /dev/null @@ -1,38 +0,0 @@ -# SLACK Bot -import os -from flask import Flask, request, jsonify -from slack_sdk import WebClient -from slack_sdk.errors import SlackApiError - - -def run_bot(askGLaDOS): - print("Connecting Slack...") - # Inicializar cliente de Slack con tu token - app = Flask(__name__) - slack_token = os.environ.get("SLACK_API_TOKEN") - slack_client = WebClient(token=slack_token) - - @app.route('/slack/events', methods=['POST']) - def slack_events(): - data = request.json - # Desafío de verificación de Slack - if data.get('type') == 'url_verification': - return jsonify({'challenge': data.get('challenge')}) - - if data['type'] == 'event_callback': - event = data['event'] - print("======SLACK EVENT=====") - print(event) - print("======-----------=====") - if 'bot_id' not in event and (event['type'] == 'message'): - try: - response = slack_client.chat_postMessage( - channel=event['channel'], - text=askGLaDOS(event['text']) - ) - except SlackApiError as e: - print(f"Error sending message: {e.response['error']}") - return jsonify({'status': 'ok'}), 200 - -# response = slack_client.chat_postMessage(channel='#glados_testing', text="Hola, mundo!") - app.run(host='0.0.0.0', port=3000) diff --git a/Bots/dev1/GladosTelegramClient.py b/Bots/dev1/GladosTelegramClient.py deleted file mode 100644 index 29c17cc..0000000 --- a/Bots/dev1/GladosTelegramClient.py +++ /dev/null @@ -1,38 +0,0 @@ -# Telegram BOT -import os -import asyncio -from telethon import TelegramClient, events - -import GladosIA - - -def run_bot(askGLaDOS): - - tg_api_id = os.environ.get("TELEGRAM_ID") - tg_api_hash = os.environ.get("TELEGRAM_HASH") - tg_bot_token = os.environ.get("TELEGRAM_TOKEN") - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - tg_client = TelegramClient('./sess_id', tg_api_id, tg_api_hash, loop=loop) - - @tg_client.on(events.NewMessage()) - async def readMessages(event): - user = await tg_client.get_entity(event.peer_id.user_id) - id = user.id - name = user.first_name - lastName = user.last_name - username = user.username - print(event.message) - # then we build the message we want to respond: - # respondMessage = "Message sender: %s @%s, with id: %s"%( name + ' ' + lastName if lastName is not None else name, username, id ) - respondMessage = askGLaDOS(event.message.message) - # then we send the message back replying the recibed message - await event.reply(respondMessage) - - try: - tg_client.start(bot_token=tg_bot_token) - print('Telegram bot started.') - loop.run_until_complete(tg_client.run_until_disconnected()) - finally: - tg_client.disconnect() diff --git a/Bots/dev1/Makefile b/Bots/dev1/Makefile deleted file mode 100644 index 9917a71..0000000 --- a/Bots/dev1/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -format: - # Applies pep8 style on all files. - # - Set the right space indentation on all files. - # - Set the correct order of imports on all files. - autopep8 --recursive --in-place . diff --git a/Bots/dev1/docker-compose.yml b/Bots/dev1/docker-compose.yml deleted file mode 100644 index 1c2bddc..0000000 --- a/Bots/dev1/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: '3' - -services: - gladosbot: - build: - context: . - dockerfile: Dockerfile - env_file: - - .env - volumes: - - ./:/app diff --git a/Bots/dev1/llm.py b/Bots/dev1/llm.py deleted file mode 100644 index 1c54dca..0000000 --- a/Bots/dev1/llm.py +++ /dev/null @@ -1,61 +0,0 @@ -import openai -import gladosMQTT - -openai_api_key = "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" -openai_api_base = "https://apicuna.mapache.xyz/v1" -custom_api_key = "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" -custom_api_base = "https://apicuna.mapache.xyz/v1" - - -openai.api_key = custom_api_key -openai.api_base = custom_api_base - -current_model = "none" -default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" - - -def set_llm_api(url, key): - openai.api_key = key - openai.api_base = url - select_model() - - -def select_model(): - model_list = openai.Model.list() - global current_model - current_model = model_list["data"][0]["id"] -# print(model_list["data"]) - gladosMQTT.debug(f"Selected model: {current_model}") - return model_list - - -def chatCompletion(prompt="", chatHistory="", masterPrompt="", initialAssistant="", maxTokens=256, langChainContext='none'): - # TODO: Identify and remove globals. - global current_model - select_model() - result = '' - - if (chatHistory == ''): - if (masterPrompt == ''): - masterPrompt = {"role": "system", "content": default_prompt} - if (initialAssistant != ''): - result = [masterPrompt, initialAssistant, - {"role": "user", "content": prompt}] - else: - result = [masterPrompt, {"role": "user", "content": prompt}] - else: - result = '[' - for msg in chatHistory: - print(msg, flush=True) - result += str(msg) - result += ',' - result += str({"role": "user", "content": prompt}) - result += ']' - gladosMQTT.debug("==============Lanzando peticion al LLM=======================") - gladosMQTT.debug(result) - - completion = openai.ChatCompletion.create( - model=current_model, messages=result, max_tokens=maxTokens) - gladosMQTT.debug("========LLM OUTPUT===============") - gladosMQTT.debug(completion) - return completion diff --git a/Bots/dev1/main.py b/Bots/dev1/main.py deleted file mode 100644 index 5b5fe23..0000000 --- a/Bots/dev1/main.py +++ /dev/null @@ -1,19 +0,0 @@ -import moab -import time -import asyncio - -print("start!") - -try: - moab.run_bots() -except KeyboardInterrupt: - print("Bot is stopping...") -print('Successfully started (Press Ctrl+C to stop)') - -while True: - try: - moab.process_msgs() - time.sleep(0.33) - - except KeyboardInterrupt: - print("Bot is stopping...") diff --git a/Bots/dev1/moab.py b/Bots/dev1/moab.py deleted file mode 100644 index ab7a3db..0000000 --- a/Bots/dev1/moab.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import threading - -import GladosIA -import GladosTelegramClient -import GladosDiscordClient -import GladosSlackClient -import GladosMQTT - -import platform -import GladosMQTT -import time - -# Variables -mqHost = "10.0.0.20" -mqPort = 1883 -nodeName = platform.node() -globalCMDTopic = "space/cmnd" - -glados_bot = GladosIA.GladosBot() -discord_bot = GladosDiscordClient.GladosDiscordClient() - - -def subscribeTopics(): - gladosMQTT.subscribe("node") - - -def on_connect(client, userdata, rc, arg): - subscribeTopics() - - -def on_message(client, userdata, msg): - if (msg.topic == commandTopic): - debug("cmd:"+msg) - - -def on_disconnect(client, userdata, rc): - debug("Disconnected! rc: "+str(rc)) - - -def run_bots(): - global mqHost, mqPort, nodeName, on_connect, on_message, on_disconnect, globalCMDTopic - GladosMQTT.initMQTT(mqHost, mqPort, nodeName, on_connect, - on_message, on_disconnect, globalCMDTopic) - -# GladosDiscordClient.run_bot(GladosIA.askGLaDOS) -# GladosSlackClient.run_bot(GladosIA.askGLaDOS) -# tg_t = threading.Thread(target= GladosTelegramClient.run_bot, args=(GladosIA.askGLaDOS,)) - dc_t = threading.Thread(target=discord_bot.run, args=()) -# sl_t = threading.Thread(target= GladosSlackClient.run_bot , args=(GladosIA.askGLaDOS,)) - -# tg_t.start() - dc_t.start() -# sl_t.start() - -# tg_t.join() -# dc_t.join() - - -async def process_msgs(): - pending = discord_bot.getMsgs() - for msg in pending: - response = glados_bot.askGLaDOS(msg) - await discord_bot.sendMsg(response) diff --git a/Bots/dev1/requirements.txt b/Bots/dev1/requirements.txt deleted file mode 100644 index 4bc9908..0000000 --- a/Bots/dev1/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -openai -paho-mqtt -psutil -telethon -slack-sdk -Flask -discord.py -autopep8 diff --git a/Bots/spaceAPI/node.py b/Bots/spaceAPI/node.py index 4eeaccf..2535267 100644 --- a/Bots/spaceAPI/node.py +++ b/Bots/spaceAPI/node.py @@ -13,8 +13,8 @@ import json #Variables -mqHost = os.environ.get("MQTT_HOST", "mqtt.makespacemadrid.org") -mqPort = os.environ.get("MQTT_PORT", 1883) +mqHost = str(os.environ.get("MQTT_HOST")) +mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() From 1cc322ac88e3f860ee20c72f606b316d28bfef63 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Tue, 30 Jan 2024 06:43:34 +0100 Subject: [PATCH 10/16] Nodo telegram con mensajes de voz :) Nodo telegram funcionando. Bonus extra: funciona con notas de voz, si le mandas mensaje de voz lo manda a transcribir a whisper y manda la transcripcion al llm --- Bots/BaseMQTTNode/docker-compose.yml | 4 +- Bots/LLM/GladosIA.py | 4 +- Bots/LLM/llm.py | 2 +- Bots/LLM/node.py | 85 +++++++++++++++++-- Bots/Slack/docker-compose.yml | 3 - Bots/Slack/node.py | 10 +-- Bots/Telegram/docker-compose.yml | 4 +- Bots/Telegram/node.py | 122 +++++++++++++++++++-------- Bots/Telegram/requirements.txt | 2 +- Bots/spaceAPI/docker-compose.yml | 4 +- 10 files changed, 180 insertions(+), 60 deletions(-) diff --git a/Bots/BaseMQTTNode/docker-compose.yml b/Bots/BaseMQTTNode/docker-compose.yml index 10033bd..a4b2303 100644 --- a/Bots/BaseMQTTNode/docker-compose.yml +++ b/Bots/BaseMQTTNode/docker-compose.yml @@ -7,5 +7,5 @@ services: # ports: # - "8080:8080" # Cambia el puerto si es necesario environment: - - MQTT_PORT = 1883 - - MQTT_HOST = mqtt.makespacemadrid.org \ No newline at end of file + - MQTT_PORT=1883 + - MQTT_HOST=mqtt.makespacemadrid.org \ No newline at end of file diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index 66362b4..6ddbc8d 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -40,7 +40,7 @@ def get_spaceapi_info(url): # Crear una cadena con los campos relevantes y sus valores, incluyendo el estado de apertura/cierre - info_str = f"El sistema domotico acaba de reportar el estado del espacio actualizado, Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" + info_str = f"Reporte actualizado del sistema domotico con el estado del espacio: Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" return info_str else: return "Error: No se pudo obtener el JSON." @@ -62,7 +62,7 @@ def ask(self, prompt, user="default"): if user not in self.user_context: self.user_context[user] = UserContext(self.GLaDOS_Prompt,self.Initial_Assistant) # Create a UserContext for the user self.user_context[user].add_to_history("assistant",self.Initial_Assistant) - extraPrompt = "informacion relevante para que la uses si la necesitas en posteriores preguntas: \n El destornillador es amarillo\n" + extraPrompt = "informacion que puede ser relevante en las consultas posteriores: \n" spaceStatus = get_spaceapi_info(os.environ.get('SPACEAPI_URL')) if spaceStatus: extraPrompt += spaceStatus diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 4827036..b3659ac 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -71,7 +71,7 @@ def chatCompletionLangChain(user_context,FileName): gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") return None -def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=256): +def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=512): global current_model select_model() diff --git a/Bots/LLM/node.py b/Bots/LLM/node.py index d9063a7..4f87843 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/node.py @@ -31,15 +31,26 @@ exit(1) topic_spaceapi = "space/status" + topic_slack = "comms/slack" topic_slack_event = topic_slack+"/event" -topic_glados_send_msg_id = topic_slack+"/send_id" -topic_glados_send_msg_name = topic_slack+"/send_name" +topic_slack_send_msg_id = topic_slack+"/send_id" +topic_slack_send_msg_name = topic_slack+"/send_name" + +topic_telegram = "comms/telegram" +topic_telegram_event = topic_telegram+"/event" +topic_telegram_send_msg_id = topic_telegram+"/send_id" +topic_discord = "comms/discord" +topic_discord_event = topic_discord+"/event" +topic_discord_send_msg_id = topic_discord+"/send_id" def subscribeTopics() : gladosMQTT.subscribe(topic_spaceapi) gladosMQTT.subscribe(topic_slack_event) + gladosMQTT.subscribe(topic_telegram_event) + gladosMQTT.subscribe(topic_discord_event) + def on_connect(client, userdata, rc,arg): subscribeTopics() @@ -48,15 +59,17 @@ def on_disconnect(client, userdata, rc): gladosMQTT.debug("Disconnected! rc: "+str(rc)) def on_message(client, userdata, msg): + payload = msg.payload.decode('utf-8') + gladosMQTT.debug(f'TOPIC> {msg.topic} payload: {payload}') if (msg.topic == topic_spaceapi) : try: # Extraer la carga útil y decodificarla a una cadena de texto payload = msg.payload.decode('utf-8') data = json.loads(payload) open_status = data['state']['open'] + #mandar mensaje de apertura con el llm? except json.JSONDecodeError as e: gladosMQTT.debug("Error al parsear JSON:") - elif(msg.topic == topic_slack_event): try: # Extraer la carga útil y decodificarla a una cadena de texto @@ -64,8 +77,22 @@ def on_message(client, userdata, msg): processSlackEvent(payload) except json.JSONDecodeError as e: gladosMQTT.debug("Error al parsear JSON:") + elif(msg.topic == topic_telegram_event): + try: + # Extraer la carga útil y decodificarla a una cadena de texto + payload = msg.payload.decode('utf-8') + processTelegramEvent(payload) + except json.JSONDecodeError as e: + gladosMQTT.debug("Error al parsear JSON:") + elif(msg.topic == topic_discord_event): + try: + # Extraer la carga útil y decodificarla a una cadena de texto + payload = msg.payload.decode('utf-8') + processDiscordEvent(payload) + except json.JSONDecodeError as e: + gladosMQTT.debug("Error al parsear JSON:") - +#SLACK EVENT def processSlackEvent(event): gladosMQTT.debug("--->SLACK event ------------------") gladosMQTT.debug(event) @@ -83,7 +110,7 @@ def processSlackEvent(event): if 'subtype' in data and data['subtype']=="channel_join": respondTo = data['channel'] msg = data['text'] - response = gladosBot.ask(msg) + response = gladosBot.ask(msg,respondTo) sendToSlack(respondTo,response) #Mensaje a canal elif data['channel_type'] == "channel": @@ -91,13 +118,13 @@ def processSlackEvent(event): msg = data['text'] if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): #sendToSlack(respondTo,"Pensando... dame unos segundos") - response = gladosBot.ask(msg) + response = gladosBot.ask(msg,respondTo) sendToSlack(respondTo,response) #Mensaje privado elif data['channel_type'] == "im": respondTo = data['user'] msg = data['text'] - response = gladosBot.ask(msg) + response = gladosBot.ask(msg,respondTo) #sendToSlack(respondTo,"Pensando... dame unos segundos") sendToSlack(respondTo,response) # except Exception as e: @@ -105,9 +132,51 @@ def processSlackEvent(event): # gladosMQTT.debug(error_message) # sendToSlack(respondTo, f"ERROR: algo no ha funcionado :S - {str(e)}") + +def processTelegramEvent(event): + gladosMQTT.debug("--->TELEGRAM event ------------------") + gladosMQTT.debug(event) + gladosMQTT.debug("/TELEGRAM event ------------------") + try: + data = json.loads(event) + respondTo=data['sender_id'] + msg=data['message_text'] + response = gladosBot.ask(msg,respondTo) + sendToTelegram(respondTo,response) + except: + gladosMQTT.debug("processTelegramEvent:Error procesado json") + return False + + +def processDiscordEvent(event): + gladosMQTT.debug("--->DISCORD event ------------------") + gladosMQTT.debug(event) + gladosMQTT.debug("/Discord event ------------------") + #try: + data = json.loads(event) + respondTo=data['sender_id'] + msg=data['message_text'] + response = gladosBot.ask(msg,respondTo) + sendToDiscord(respondTo,response) + #except: + # gladosMQTT.debug("processDiscordEvent:Error procesado json") + # return False + +#SEND TO SLACK def sendToSlack(id,msg): response = json.dumps({"dest": id, "msg": msg}) gladosMQTT.debug(f"--->Respuesta a slack: {response}") - gladosMQTT.publish(topic_glados_send_msg_id,response) + gladosMQTT.publish(topic_slack_send_msg_id,response) + +def sendToTelegram(id,msg): + response = json.dumps({"dest": id, "msg": msg}) + gladosMQTT.debug(f"--->Respuesta a slack: {response}") + gladosMQTT.publish(topic_telegram_send_msg_id,response) + +def sendToDiscord(id,msg): + response = json.dumps({"dest": id, "msg": msg}) + gladosMQTT.debug(f"--->Respuesta a slack: {response}") + gladosMQTT.publish(topic_discord_send_msg_id,response) + gladosMQTT.initMQTTandLoopForever(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) \ No newline at end of file diff --git a/Bots/Slack/docker-compose.yml b/Bots/Slack/docker-compose.yml index e4400d0..ec95825 100644 --- a/Bots/Slack/docker-compose.yml +++ b/Bots/Slack/docker-compose.yml @@ -9,9 +9,6 @@ services: env_file: - .env environment: - - MQTT_PORT = 1883 - - MQTT_HOST = mqtt.makespacemadrid.org - - SLACK_API_TOKEN = ${SLACK_API_TOKEN} - SLACK_PORT = 5000 # No estoy seguro que funcione restart: always ports: diff --git a/Bots/Slack/node.py b/Bots/Slack/node.py index fb8747e..8b07ea7 100644 --- a/Bots/Slack/node.py +++ b/Bots/Slack/node.py @@ -19,8 +19,8 @@ InitialHome=False #Variables -mqHost = str(os.environ.get("MQTT_HOST")) -mqPort = int(os.environ.get("MQTT_PORT")) +mqHost = os.environ.get("MQTT_HOST") +mqPort = os.environ.get("MQTT_PORT") nodeName = platform.node() slack_token = os.environ.get("SLACK_API_TOKEN") @@ -216,7 +216,7 @@ def sendSlackMsgbyName(name, msg): slack_client = WebClient(token=slack_token) slack_client.users_setPresence(presence="auto") -set_bot_status("Boot") +#set_bot_status("Boot") app = Flask(__name__) publishHomeView("U0A7VU47Q") @@ -231,14 +231,14 @@ def slack_events(): if data['type'] == 'event_callback': event = data['event'] - set_bot_status('Processing') + # set_bot_status('Processing') gladosMQTT.publish(topic_slack_event, json.dumps(event)) if event.get('type') == 'app_home_opened': user_id = event.get('user') if user_id: publishHomeView(user_id) - set_bot_status('Idle') + #set_bot_status('Idle') return jsonify({'status': 'ok'}), 200 diff --git a/Bots/Telegram/docker-compose.yml b/Bots/Telegram/docker-compose.yml index 4b2b03e..5bfbc53 100644 --- a/Bots/Telegram/docker-compose.yml +++ b/Bots/Telegram/docker-compose.yml @@ -1,6 +1,4 @@ version: '3' - - services: mqtt-telegram: build: . @@ -8,4 +6,6 @@ services: hostname: mqtt-telegram env_file: - .env + volumes: + - ./data:/data restart: always \ No newline at end of file diff --git a/Bots/Telegram/node.py b/Bots/Telegram/node.py index 493591d..9baea89 100644 --- a/Bots/Telegram/node.py +++ b/Bots/Telegram/node.py @@ -4,6 +4,8 @@ import gladosMQTT import platform from telethon import TelegramClient, events +import asyncio +import requests # Configuración de Telegram @@ -11,6 +13,9 @@ api_hash = os.environ.get("TELEGRAM_API_HASH") # Reemplaza con tu propio api_hash telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot +# Variable global para almacenar el ID del bot +bot_id = None + # Configuración MQTT #Variables mqHost = str(os.environ.get("MQTT_HOST")) #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S @@ -33,12 +38,33 @@ # Temas MQTT para comunicarse con otros componentes topic_telegram = "comms/telegram" -topic_telegram_send_msg = topic_telegram + "/send" +topic_telegram_send_msg = topic_telegram + "/send_id" topic_telegram_event = topic_telegram + "/event" # Iniciar cliente de Telegram -telegram_client = TelegramClient('/tmp/telegram_bot_session', api_id, api_hash) +telegram_client = TelegramClient('/data/tg_sess.ignore', api_id, api_hash) + + +async def send_audio_to_transcription_api(audio_file_path): + url = "https://whisper.makespacemadrid.org/asr" + params = { + "encode": "true", + "task": "transcribe", + "language": "es", + "word_timestamps": "false", + "output": "txt" + } + files = {'audio_file': (audio_file_path, open(audio_file_path, 'rb'), 'audio/wav')} + + try: + response = requests.post(url, params=params, files=files) + response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa + return response.text # Retorna el contenido de la transcripción + except requests.RequestException as e: + return(f"Error al enviar el archivo de audio a la API: {e}") + + def subscribeTopics() : gladosMQTT.subscribe(topic_telegram_send_msg) @@ -50,44 +76,72 @@ def on_connect(client, userdata, rc,arg): def on_disconnect(client, userdata, rc): gladosMQTT.debug("Disconnected! rc: "+str(rc)) + +loop = asyncio.get_event_loop() + + +def send_telegram_message_sync(user_id, message): + asyncio.run_coroutine_threadsafe(telegram_client.send_message(user_id, message), loop) + + + + def on_message(client, userdata, msg): - if (msg.topic == topic_telegram_send_msg) : - try: - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - dest = data['dest'] - content = data['msg'] - sendTelegramMsg(dest,content) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - -# Enviar mensajes a Telegram -async def sendTelegramMsg(user_id, msg): - gladosMQTT.debug(f"--->Respuesta a Telegram: {user_id} : {msg}") - await telegram_client.send_message(user_id, msg) + if msg.topic == topic_telegram_send_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + dest = data['dest'] + content = data['msg'] + # Llama al callback síncrono + send_telegram_message_sync(dest, content) + except Exception as e: + gladosMQTT.debug(f"Error al enviar mensaje: {e}") -# Evento para manejar nuevos mensajes en Telegram -@telegram_client.on(events.NewMessage) -async def handle_new_message(event): - gladosMQTT.debug(event.stringify()) - event_data = { - 'message_text': event.message.message if event.message else None, - 'sender_id': event.sender_id, - 'chat_id': event.chat_id, - # Agrega aquí otros campos relevantes - } - gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) - if event.is_private: - msg = event.message.message - chat_id = event.message.chat_id - await event.respond('Recibí tu mensaje: ' + msg) # Respuesta de ejemplo + -gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) -telegram_client.start(bot_token=telegram_token) -telegram_client.run_until_disconnected() +# Evento para manejar nuevos mensajes en Telegram +@telegram_client.on(events.NewMessage) +async def handle_new_message(event): + gladosMQTT.debug(event.stringify()) +# global bot_id + # Si no se ha establecido el ID del bot, obténgalo +# if bot_id is None: +# me = await telegram_client.get_me() +# bot_id = me.id + if event.message.voice or event.message.audio: + # Descargar el archivo de audio + audio_file_path = await telegram_client.download_media(event.message, file='/tmp/audio/') + # Procesar el archivo de audio + transcription = await send_audio_to_transcription_api(audio_file_path) + event_data = { + 'message_text': transcription, + 'sender_id': event.sender_id, + 'chat_id': event.chat_id, + # Agrega aquí otros campos relevantes + } + gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) + return + + if event.is_private and event.sender_id != 771352834 : +# await event.respond('Procesando...') + event_data = { + 'message_text': event.message.message if event.message else None, + 'sender_id': event.sender_id, + 'chat_id': event.chat_id, + # Agrega aquí otros campos relevantes + } + gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) + + + +if __name__ == "__main__": + gladosMQTT.initMQTT(mqHost, mqPort, nodeName, on_connect, on_message, on_disconnect) + telegram_client.start(bot_token=telegram_token) + loop.run_until_complete(telegram_client.run_until_disconnected()) diff --git a/Bots/Telegram/requirements.txt b/Bots/Telegram/requirements.txt index 0693518..c1af436 100644 --- a/Bots/Telegram/requirements.txt +++ b/Bots/Telegram/requirements.txt @@ -1,3 +1,3 @@ paho-mqtt telethon -python-dotenv \ No newline at end of file +requests \ No newline at end of file diff --git a/Bots/spaceAPI/docker-compose.yml b/Bots/spaceAPI/docker-compose.yml index 2c48919..c3c836b 100644 --- a/Bots/spaceAPI/docker-compose.yml +++ b/Bots/spaceAPI/docker-compose.yml @@ -7,8 +7,8 @@ services: volumes: - ./spaceapi:/spaceapi environment: - - MQTT_PORT = 1883 - - MQTT_HOST = glados.makespacemadrid.org + - MQTT_PORT=1883 + - MQTT_HOST=mqtt.makespacemadrid.org restart: always nginx-spaceapi: From bd238fd7609f90e9a015a5450652afe21fdbe597 Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Tue, 30 Jan 2024 13:01:37 +0100 Subject: [PATCH 11/16] refactorizacion --- Bots/BaseMQTTNode/Dockerfile | 4 +- Bots/BaseMQTTNode/docker-compose.yml | 2 +- Bots/BaseMQTTNode/gladosMQTT.py | 203 +++++++--------------- Bots/BaseMQTTNode/main.py | 33 ++++ Bots/BaseMQTTNode/node.py | 40 ----- Bots/Discord/Dockerfile | 6 +- Bots/Discord/main.py | 73 ++++++++ Bots/Discord/node.py | 101 ----------- Bots/LLM/Dockerfile | 8 +- Bots/LLM/GladosIA.py | 12 +- Bots/LLM/UserContext.py | 4 - Bots/LLM/llm.py | 40 ++--- Bots/LLM/{node.py => main.py} | 155 +++++++++++++++++ Bots/Slack/Dockerfile | 6 +- Bots/Slack/main.py | 196 +++++++++++++++++++++ Bots/Slack/node.py | 245 --------------------------- Bots/Telegram/Dockerfile | 6 +- Bots/Telegram/{node.py => main.py} | 101 ++++------- Bots/spaceAPI/Dockerfile | 6 +- Bots/spaceAPI/main.py | 38 +++++ Bots/spaceAPI/node.py | 49 ------ 21 files changed, 632 insertions(+), 696 deletions(-) create mode 100644 Bots/BaseMQTTNode/main.py delete mode 100644 Bots/BaseMQTTNode/node.py create mode 100644 Bots/Discord/main.py delete mode 100644 Bots/Discord/node.py rename Bots/LLM/{node.py => main.py} (53%) create mode 100644 Bots/Slack/main.py delete mode 100644 Bots/Slack/node.py rename Bots/Telegram/{node.py => main.py} (67%) create mode 100644 Bots/spaceAPI/main.py delete mode 100644 Bots/spaceAPI/node.py diff --git a/Bots/BaseMQTTNode/Dockerfile b/Bots/BaseMQTTNode/Dockerfile index 1b72e7f..91cdeac 100644 --- a/Bots/BaseMQTTNode/Dockerfile +++ b/Bots/BaseMQTTNode/Dockerfile @@ -9,10 +9,10 @@ COPY requirements.txt . # Copia el archivo node.py al contenedor COPY gladosMQTT.py . -COPY node.py . +COPY main.py . # Instala las dependencias RUN pip install -r requirements.txt # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/BaseMQTTNode/docker-compose.yml b/Bots/BaseMQTTNode/docker-compose.yml index a4b2303..3981143 100644 --- a/Bots/BaseMQTTNode/docker-compose.yml +++ b/Bots/BaseMQTTNode/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: glados_node: build: . - image: makespacemadrid/glados-base-node:1.0 + image: makespacemadrid/glados-base-node:1.5 hostname: base-node # ports: # - "8080:8080" # Cambia el puerto si es necesario diff --git a/Bots/BaseMQTTNode/gladosMQTT.py b/Bots/BaseMQTTNode/gladosMQTT.py index 3477ff3..0da6a96 100644 --- a/Bots/BaseMQTTNode/gladosMQTT.py +++ b/Bots/BaseMQTTNode/gladosMQTT.py @@ -1,147 +1,68 @@ # -*- coding: utf-8 -*- - import paho.mqtt.client as mqtt -import platform -import time -import os import json - - -#Carga inicial de variables, aunque el valor de estas se reescribira al ejecutar initMQTT -mqttServer = "192.168.1.1" -mqttPort = 1883 -nodeName = "test-node" -baseTopic = "node/test-node" -debugTopic = baseTopic+"debug" -mqttClient = mqtt.Client() - - -def dummy() : - debug(dummy) - -nodeConnectedCallback = dummy -nodeMsgCallback = dummy -nodeDisconnectedCallback = dummy - -def subscribe(topic) : - mqttClient.subscribe(topic) - - -def publish(topic,msg,persist = False) : - mqttClient.publish(topic,msg,persist) - -def debug(msg): - # Convertir el mensaje a una cadena, si es necesario - if not isinstance(msg, str): +class GladosMQTT: + def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback=None): + self.mqttServer = host + self.mqttPort = port + self.nodeName = name + self.baseTopic = "node/" + self.nodeName + self.debugTopic = self.baseTopic + "/debug" + self.mqttClient = mqtt.Client() + self.topics = [] + self.nodeMsgCallback = msg_callback if msg_callback is not None else self.dummy + + self.mqttClient.on_connect = self.on_connect + self.mqttClient.on_message = self.on_message + self.mqttClient.on_disconnect = self.on_disconnect + + def set_topics(self, topics): + self.topics = topics + + def dummy(self, *args, **kwargs): + self.debug("Dummy function called") + + def publish(self, topic, msg, persist=False): + self.mqttClient.publish(topic, msg, persist) + + def debug(self, msg): + if not isinstance(msg, str): + try: + msg = json.dumps(msg) + except (TypeError, ValueError): + msg = str(msg) + + print(msg) + self.publish(self.debugTopic, msg) + + def on_connect(self, client, userdata, flags, rc): + self.debug("[GladosNode] Connected with result code " + str(rc)) + for topic in self.topics: + self.mqttClient.subscribe(topic) + + def on_message(self, client, userdata, msg): +# try: +# self.debug("[GladosNode] mqtt_rcv: { " + msg.topic + " - " + msg.payload.decode() + " }") +# except Exception as e: +# self.debug("[GladosNode] mqtt_rcv error: " + str(e)) + self.nodeMsgCallback(client, userdata, msg) + + def on_disconnect(self, client, userdata, rc): + self.debug("[GladosNode] Disconnected with result code " + str(rc)) + + def init_mqtt(self): + self.debug("[GladosNode] Connecting : " + str(self.mqttServer) + " Port:" + str(self.mqttPort) + " node: " + str(self.nodeName)) try: - # Intenta convertir el mensaje a una cadena JSON si es un objeto complejo - msg = json.dumps(msg) - except (TypeError, ValueError): - # Si la conversión falla, usa una representación de string genérica - msg = str(msg) - - print(msg) - publish(debugTopic, msg) - -def on_connect(client, userdata, rc,arg): - global mqttServer - global mqttPort - global nodeName - global baseTopic - global debugTopic - - debug("[GladosNode] Connected with result code "+str(rc)) - debug("[GladosNode] Node name : " + nodeName) - debug("[GladosNode] Base topic : " + baseTopic) - debug("[GladosNode] Debug topic : " + debugTopic) - debug("[GladosNode] mosquitto_sub -h " + mqttServer + " -t " + debugTopic) - - - publish("node/hello", "Hello! Im "+ nodeName) - nodeConnectedCallback(client, userdata, rc,arg) - - -# The callback for when a PUBLISH message is received from the server. -# Aqui recibimos los mensajes, y si no hay que hacer nada con ellos se lo pasamos al callback del nodo para que los procese -def on_message(client, userdata, msg): - - try: - debug("[GladosNode] mqtt_rcv: { "+msg.topic + " - " +msg.payload+ " }") - except: - debug("[GladosNode] mqtt_rcv: not str ") - nodeMsgCallback(client, userdata, msg) - -def on_disconnect(client, userdata, rc): - nodeDisconnectedCallback(client, userdata, rc) + self.mqttClient.connect(self.mqttServer, port=self.mqttPort, keepalive=120) + except Exception as e: + self.debug("Cannot connect: " + str(e)) + self.mqttClient.loop_start() -#Inicia mqtt y devuelve el control -def initMQTT(host,port,name,connectedCallback,msgCallback,disconnectedCallback) : - global mqttServer - global mqttPort - global nodeName - global baseTopic - global debugTopic - global mqttClient - - global nodeConnectedCallback - global nodeMsgCallback - global nodeDisconnectedCallback - - mqttServer = host - mqttPort = port - nodeName = name - baseTopic = "node/"+nodeName+"/" - debugTopic = baseTopic+"debug" - - - nodeConnectedCallback = connectedCallback - nodeMsgCallback = msgCallback - nodeDisconnectedCallback = disconnectedCallback - - mqttClient.on_connect = on_connect - mqttClient.on_message = on_message - mqttClient.on_disconnect = on_disconnect - - print("[GladosNode] Connecting : "+str(mqttServer)+" Port:"+str(mqttPort)+ " node: " + str(name)) - - try: - mqttClient.connect(str(mqttServer),port=int(mqttPort),keepalive=120) - except: - print("Cant connect, will retry automatically") - mqttClient.loop_start() - -#Inicia mqtt y captura la ejecucion -def initMQTTandLoopForever(host,port,name,connectedCallback,msgCallback,disconnectedCallback) : - global mqttServer - global mqttPort - global nodeName - global baseTopic - global debugTopic - - global nodeConnectedCallback - global nodeMsgCallback - global nodeDisconnectedCallback - - mqttServer = host - mqttPort = port - nodeName = name - baseTopic = "node/"+nodeName+"/" - debugTopic = baseTopic+"debug" - - - nodeConnectedCallback = connectedCallback - nodeMsgCallback = msgCallback - nodeDisconnectedCallback = disconnectedCallback - - mqttClient.on_connect = on_connect - mqttClient.on_message = on_message - mqttClient.on_disconnect = on_disconnect - - print("[GladosNode] Connecting : "+str(mqttServer)+" Port:"+str(mqttPort)+ " node: " + str(name)) - - try: - mqttClient.connect(mqttServer,port=mqttPort,keepalive=120) - except: - print("Cant connect, will retry automatically") - mqttClient.loop_forever() \ No newline at end of file + def init_mqtt_and_loop_forever(self): + self.debug("[GladosNode] Connecting : " + str(self.mqttServer) + " Port:" + str(self.mqttPort) + " node: " + str(self.nodeName)) + try: + self.mqttClient.connect(self.mqttServer, port=self.mqttPort, keepalive=120) + except Exception as e: + self.debug("Cannot connect: " + str(e)) + self.mqttClient.loop_forever() diff --git a/Bots/BaseMQTTNode/main.py b/Bots/BaseMQTTNode/main.py new file mode 100644 index 0000000..9d140b6 --- /dev/null +++ b/Bots/BaseMQTTNode/main.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +import os +import platform +import time +from gladosMQTT import GladosMQTT + +# Variables +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Definición de los callbacks +def on_mqtt_message(client, userdata, msg): + if msg.topic == "my_topic": + glados_mqtt.debug("msg:" + str(msg.payload)) + +# Instanciación de la clase GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName, msg_callback=on_mqtt_message) + +# Suscripción a tópicos +glados_mqtt.set_topics(["node/topic", "my_topic"]) # Asegúrate de incluir los tópicos correctos aquí + +# Inicialización de MQTT +glados_mqtt.init_mqtt_and_loop_forever() + +#glados_mqtt.init_mqtt +#try: +# while True: + # Loop principal del programa +# time.sleep(10) +#except KeyboardInterrupt: +# print('Interrupted!') diff --git a/Bots/BaseMQTTNode/node.py b/Bots/BaseMQTTNode/node.py deleted file mode 100644 index b861f34..0000000 --- a/Bots/BaseMQTTNode/node.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- - -#Nodo Mqtt de ejemplo, -#gladosMQTT.py contiene la funcionalidad para manejar el mqtt. -# aui nos conectamos y reaccionamos a los mensajes que llegan. -# TODO Documentar mejor... gpt? xD - -import os -import gladosMQTT -import platform -import time - - -#Variables -mqHost = os.environ.get("MQTT_HOST") -mqPort = os.environ.get("MQTT_PORT") -nodeName = platform.node() - - -def subscribeTopics() : - gladosMQTT.subscribe("node/topic") - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_message(client, userdata, msg): - if (msg.topic == "my_topic") : - gladosMQTT.debug("cmd:"+msg) - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - -gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) - -try: - while True: - #Loop principal del programa - time.sleep(10) -except KeyboardInterrupt: - print('interrupted!') diff --git a/Bots/Discord/Dockerfile b/Bots/Discord/Dockerfile index cea590b..47d9b49 100644 --- a/Bots/Discord/Dockerfile +++ b/Bots/Discord/Dockerfile @@ -1,5 +1,5 @@ # Utiliza una imagen base que ya tiene el cliente mqtt -FROM makespacemadrid/glados-base-node:1.0 +FROM makespacemadrid/glados-base-node:1.5 # Establece el directorio de trabajo en /app WORKDIR /app @@ -9,11 +9,11 @@ WORKDIR /app COPY requirements.txt . # Copia el archivo node.py al contenedor -COPY node.py . +COPY main.py . # Instala las dependencias RUN pip install -r requirements.txt USER 1000 # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/Discord/main.py b/Bots/Discord/main.py new file mode 100644 index 0000000..2da31d7 --- /dev/null +++ b/Bots/Discord/main.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import os +import json +import platform +import discord +from discord.ext import commands +from gladosMQTT import GladosMQTT + +# Configuración de Discord +discord_token = os.environ.get("DISCORD_BOT_TOKEN") +discordbot_prefix = "!" +discordintents = discord.Intents.default() +discordintents.messages = True +discordintents.members = True +discordintents.message_content = True +bot = commands.Bot(command_prefix=discordbot_prefix, intents=discordintents) + +# Configuración MQTT +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Temas MQTT +topic_discord = "comms/discord" +topic_discord_send_msg = topic_discord + "/send_id" +topic_discord_event = topic_discord + "/event" + +# Instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +glados_mqtt.set_topics([topic_discord_send_msg]) + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + if msg.topic == topic_discord_send_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + sendDiscordMsg(data['dest'], data['msg']) + except json.JSONDecodeError as e: + print("Error al parsear JSON:", e) + +# Configurar callbacks MQTT +glados_mqtt.mqttClient.on_message = on_mqtt_message + +# Función para enviar mensajes a Discord +async def sendDiscordMsg(channel_id, msg): + channel = bot.get_channel(channel_id) + if channel: + await channel.send(msg) + +# Eventos de Discord +@bot.event +async def on_ready(): + print(f'Bot conectado como {bot.user.name} - {bot.user.id}') + +@bot.event +async def on_message(message): + if message.author == bot.user: + return + message_data = { + 'content': message.content, + 'author_id': message.author.id, + 'author_name': str(message.author), + 'channel_id': message.channel.id, + 'channel_name': str(message.channel), + 'mentions': str(message.mentions) + } + glados_mqtt.publish(topic_discord_event, json.dumps(message_data)) + #await message.channel.send('Recibí tu mensaje: ' + message.content) # Respuesta de ejemplo + +if __name__ == "__main__": + glados_mqtt.init_mqtt() + bot.run(discord_token) diff --git a/Bots/Discord/node.py b/Bots/Discord/node.py deleted file mode 100644 index e122ed5..0000000 --- a/Bots/Discord/node.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import json -import gladosMQTT -import platform -import os -import discord -from discord.ext import commands - - -discord_token = str(os.environ.get("DISCORD_BOT_TOKEN")) # Reemplaza con tu token de bot -discordbot_prefix = "!" - -discordintents = discord.Intents.default() -discordintents.messages = True -discordintents.members = True -discordintents.message_content = True -bot = commands.Bot( - command_prefix=discordbot_prefix, intents=discordintents) - - -# Configuración MQTT -#Variables -mqHost = str(os.environ.get("MQTT_HOST")) -mqPort = int(os.environ.get("MQTT_PORT")) -nodeName = platform.node() - - -# Temas MQTT para comunicarse con otros componentes -topic_discord = "comms/discord" -topic_discord_send_msg = topic_discord + "/send" -topic_discord_event = topic_discord + "/event" - - -discord_client = commands.Bot(command_prefix=discordbot_prefix, intents=discordintents) - - -def subscribeTopics() : - gladosMQTT.subscribe(topic_discord_send_msg) - - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - -def on_message(client, userdata, msg): - if (msg.topic == topic_discord_send_msg) : - try: - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - dest = data['dest'] - content = data['msg'] - sendDiscordMsg(dest,content) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - - - -# Enviar mensajes a Discord -async def sendDiscordMsg(channel_id, msg): - channel = discord_client.get_channel(channel_id) - if channel: - await channel.send(msg) - - -@discord_client.event -async def on_ready(): - print(f'Bot conectado como {discord_client.user.name} - {discord_client.user.id}') - - -# Evento para manejar nuevos mensajes en Discord -@discord_client.event -async def on_message(message): - if message.author == discord_client.user: - return - # Crear un diccionario con la información relevante - message_data = { - 'content': message.content, - 'author_id': message.author.id, - 'author_name': str(message.author), - 'channel_id': message.channel.id, - 'channel_name': str(message.channel), - 'mentions': str(message.mentions) - # Puedes agregar aquí cualquier otro dato que necesites - } - - # Convertir el diccionario a JSON y publicarlo - gladosMQTT.publish(topic_discord_event, json.dumps(message_data)) - - await message.channel.send('Recibí tu mensaje: ' + message.content) # Respuesta de ejemplo - -# Iniciar el bot de Discord -def start_discord_bot(): - discord_client.run(discord_token) - - -if __name__ == "__main__": - gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) - start_discord_bot() diff --git a/Bots/LLM/Dockerfile b/Bots/LLM/Dockerfile index 7e457c9..61fc2da 100644 --- a/Bots/LLM/Dockerfile +++ b/Bots/LLM/Dockerfile @@ -1,5 +1,5 @@ # Utiliza una imagen base que ya tiene el cliente mqtt -FROM makespacemadrid/glados-base-node:1.0 +FROM makespacemadrid/glados-base-node:1.5 VOLUME [ "/data" ] @@ -10,16 +10,14 @@ COPY requirements.txt . RUN pip install -r requirements.txt # Copia el archivo node.py al contenedor -COPY node.py . +COPY main.py . COPY llm.py . COPY GladosIA.py . COPY langchain.txt . COPY UserContext.py . COPY EmbeddingManager.py . -#COPY .env . - USER 1000 # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index 6ddbc8d..e2bd27d 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -1,5 +1,4 @@ import llm -import gladosMQTT from UserContext import UserContext # Import UserContext import os import requests @@ -50,15 +49,19 @@ def get_spaceapi_info(url): class GladosBot: - def __init__(self): + def __init__(self,debug=None): self.GLaDOS_Prompt = os.environ.get('GLADOS_MASTER_PROMPT') self.Initial_Assistant = os.environ.get('GLADOS_INITIAL_PROMPT') + self.debug = debug if debug else self.default_debug # Utiliza la función de depuración por defecto si no se proporciona una # Historial de conversaciones, almacenado por usuario self.user_context = {} + def default_debug(self, message): + # Función de depuración por defecto que imprime en la terminal + print(f"DEBUG: {message}") + def ask(self, prompt, user="default"): - gladosMQTT.debug(f"--->Glados.ASK, user: {user}, prompt: {prompt}") if user not in self.user_context: self.user_context[user] = UserContext(self.GLaDOS_Prompt,self.Initial_Assistant) # Create a UserContext for the user self.user_context[user].add_to_history("assistant",self.Initial_Assistant) @@ -66,7 +69,6 @@ def ask(self, prompt, user="default"): spaceStatus = get_spaceapi_info(os.environ.get('SPACEAPI_URL')) if spaceStatus: extraPrompt += spaceStatus - gladosMQTT.debug(extraPrompt) self.user_context[user].set_system_prompt_extra(extraPrompt) @@ -77,11 +79,9 @@ def ask(self, prompt, user="default"): self.user_context[user] = UserContext(self.GLaDOS_Prompt) return "Contexto reiniciado" elif prompt.lower() == "dump context": - gladosMQTT.debug(self.user_context[user].get_combined_prompt()) return self.user_context[user].get_combined_prompt() - #Gestion de respuestas self.user_context[user].add_to_history("user",prompt) #gladosMQTT.debug(self.user_context[user].get_combined_prompt()) diff --git a/Bots/LLM/UserContext.py b/Bots/LLM/UserContext.py index 0a5fe87..cfe5943 100644 --- a/Bots/LLM/UserContext.py +++ b/Bots/LLM/UserContext.py @@ -1,6 +1,4 @@ import json -import gladosMQTT - class UserContext: def __init__(self, master_prompt="", initial_assistant=""): @@ -55,8 +53,6 @@ def get_combined_prompt(self, max_tokens=None): # Convierte la lista de mensajes en una cadena JSON válida #combined_prompt_json = json.dumps(combined_prompt) - gladosMQTT.debug(f"combined: {combined_prompt}") - return combined_prompt diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index b3659ac..ec26f7b 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -1,6 +1,5 @@ from openai import OpenAI import os -import gladosMQTT import json from langchain.chat_models import ChatOpenAI @@ -40,11 +39,6 @@ def list_files_in_directory(directory_path): return file_paths -my_embeddings = EmbeddingManager() -my_embeddings.ingest(list_files_in_directory('/data')) # Rutas a los archivos de texto - - - def select_model(): model_list = llm_mks.models.list() @@ -55,28 +49,27 @@ def select_model(): print(current_model) return model_list +my_embeddings = EmbeddingManager() +my_embeddings.ingest(list_files_in_directory('/data')) # Rutas a los archivos de texto -def chatCompletionLangChain(user_context,FileName): - chatHistory = user_context.get_combined_prompt() - - gladosMQTT.debug(f"--->Chat completion langchain: {chatHistory}") - try: +#def chatCompletionLangChain(user_context,FileName): +# chatHistory = user_context.get_combined_prompt() +# +# try: # response = index.query(json.dumps(chatHistory), llm=llm_langchain) - search_results = my_embeddings.search(user_context.get_last_prompt()) - gladosMQTT.debug(json.dumps(search_results, indent=4)) # Imprimir resultados formateados - response=json.dumps(search_results, indent=4) - gladosMQTT.debug(f"----->LLM OUTPUT: {response}") - return response - except Exception as e: - gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") - return None +# search_results = my_embeddings.search(user_context.get_last_prompt()) +# gladosMQTT.debug(json.dumps(search_results, indent=4)) # Imprimir resultados formateados +# response=json.dumps(search_results, indent=4) +# gladosMQTT.debug(f"----->LLM OUTPUT: {response}") +# return response +# except Exception as e: +# gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") +# return None def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=512): global current_model select_model() - gladosMQTT.debug(f"--->Chat completion: {prompt}") - messages = [] if user_context is None: @@ -92,16 +85,11 @@ def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssista # gladosMQTT.debug(f"hist: {hist}") # messages.append(hist) for msg in user_context.get_combined_prompt(): - gladosMQTT.debug(f"msg: {msg}") messages.append(msg) - gladosMQTT.debug(f"---->LLM : {messages}") - try: response = llm_mks.chat.completions.create(model=current_model, messages=messages, max_tokens=maxTokens) - gladosMQTT.debug(f"----->LLM OUTPUT: {response}") return response except Exception as e: - gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") return None diff --git a/Bots/LLM/node.py b/Bots/LLM/main.py similarity index 53% rename from Bots/LLM/node.py rename to Bots/LLM/main.py index 4f87843..c1ddf52 100644 --- a/Bots/LLM/node.py +++ b/Bots/LLM/main.py @@ -1,3 +1,158 @@ +# -*- coding: utf-8 -*- + +import os +import platform +import json +from gladosMQTT import GladosMQTT +import GladosIA + +# Configuración MQTT +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Verificación de variables críticas +if not mqHost or not mqPort: + print("No MQTT config!") + exit(1) + +# Instancia de GladosIA +gladosBot = GladosIA.GladosBot() + +# Temas MQTT +topic_spaceapi = "space/status" +topic_slack_event = "comms/slack/event" +topic_telegram_event = "comms/telegram/event" +topic_discord_event = "comms/discord/event" +topic_slack_send_msg_id = "comms/slack/send_id" +topic_telegram_send_msg_id ="comms/telegram/send_id" +topic_discord_send_msg_id ="comms/discord/send_id" + +# Instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +glados_mqtt.set_topics([topic_spaceapi, topic_slack_event, topic_telegram_event, topic_discord_event]) + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + payload = msg.payload.decode('utf-8') + glados_mqtt.debug(f'TOPIC> {msg.topic} payload: {payload}') + if msg.topic == topic_spaceapi: + data = json.loads(payload) + open_status = data['state']['open'] + #mandar mensaje de apertura con el llm? + elif msg.topic == topic_slack_event: + processSlackEvent(payload) + elif msg.topic == topic_telegram_event: + processTelegramEvent(payload) + elif msg.topic == topic_discord_event: + processDiscordEvent(payload) + # Añade más condiciones según sea necesario + +# Configurar callbacks MQTT +glados_mqtt.mqttClient.on_message = on_mqtt_message + +# Funciones para procesar eventos de Slack, Telegram y Discord +def processSlackEvent(event): + glados_mqtt.debug("--->SLACK event ------------------") + glados_mqtt.debug(event) + glados_mqtt.debug("/SLACK event ------------------") + try: + data = json.loads(event) + if data['type'] != "message" or 'bot_id' in data: + return False + except: + glados_mqtt.debug("processSlackEvent: Error procesando JSON") + return False + + # Procesar diferentes tipos de eventos de Slack aquí + # Puedes manejar eventos de unión a canales, mensajes en canales, mensajes privados, etc. + + # Ejemplo: Mensajes de unión a canal + if 'subtype' in data and data['subtype'] == "channel_join": + respondTo = data['channel'] + msg = data['text'] + response = gladosBot.ask(msg, respondTo) + sendToSlack(respondTo, response) + + # Ejemplo: Mensaje a canal + elif data['channel_type'] == "channel": + respondTo = data['channel'] + msg = data['text'] + if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): + response = gladosBot.ask(msg, respondTo) + sendToSlack(respondTo, response) + + # Ejemplo: Mensaje privado + elif data['channel_type'] == "im": + respondTo = data['user'] + msg = data['text'] + response = gladosBot.ask(msg, respondTo) + sendToSlack(respondTo, response) + +def processTelegramEvent(event): + glados_mqtt.debug("---> TELEGRAM event ------------------") + glados_mqtt.debug(event) + glados_mqtt.debug("/ TELEGRAM event ------------------") + # try: + data = json.loads(event) + respondTo = data['sender_id'] + msg = data['message_text'] + response = gladosBot.ask(msg, respondTo) + sendToTelegram(respondTo, response) + # except: + # glados_mqtt.debug("processTelegramEvent: Error procesando JSON") + # return False + + +def processDiscordEvent(event): + glados_mqtt.debug("---> DISCORD event ------------------") + glados_mqtt.debug(event) + glados_mqtt.debug("/ DISCORD event ------------------") + # try: + data = json.loads(event) + respondTo = data['channel_id'] + msg = data['content'] + response = gladosBot.ask(msg, respondTo) + sendToDiscord(respondTo, response) + # except: + # glados_mqtt.debug("processDiscordEvent: Error procesando JSON") + # return False + +# Funciones para enviar respuestas a Slack, Telegram y Discord +def sendToSlack(id, msg): + response = json.dumps({"dest": id, "msg": msg}) + glados_mqtt.debug(f"---> Respuesta a Slack: {response}") + glados_mqtt.publish(topic_slack_send_msg_id, response) + +def sendToTelegram(id, msg): + response = json.dumps({"dest": id, "msg": msg}) + glados_mqtt.debug(f"---> Respuesta a Telegram: {response}") + glados_mqtt.publish(topic_telegram_send_msg_id, response) + +# Enviar mensaje a Discord por ID de usuario o canal +def sendToDiscord(id, msg): + response = json.dumps({"dest": id, "msg": msg}) + glados_mqtt.debug(f"---> Respuesta a Discord: {response}") + glados_mqtt.publish(topic_discord_send_msg_id, response) + + +if __name__ == "__main__": + glados_mqtt.init_mqtt_and_loop_forever() + + + + + + + + + + + + + + + # -*- coding: utf-8 -*- #Nodo Mqtt de ejemplo, diff --git a/Bots/Slack/Dockerfile b/Bots/Slack/Dockerfile index cea590b..47d9b49 100644 --- a/Bots/Slack/Dockerfile +++ b/Bots/Slack/Dockerfile @@ -1,5 +1,5 @@ # Utiliza una imagen base que ya tiene el cliente mqtt -FROM makespacemadrid/glados-base-node:1.0 +FROM makespacemadrid/glados-base-node:1.5 # Establece el directorio de trabajo en /app WORKDIR /app @@ -9,11 +9,11 @@ WORKDIR /app COPY requirements.txt . # Copia el archivo node.py al contenedor -COPY node.py . +COPY main.py . # Instala las dependencias RUN pip install -r requirements.txt USER 1000 # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/Slack/main.py b/Bots/Slack/main.py new file mode 100644 index 0000000..13d19d2 --- /dev/null +++ b/Bots/Slack/main.py @@ -0,0 +1,196 @@ +# -*- coding: utf-8 -*- +import os +import platform +import json +from gladosMQTT import GladosMQTT +from flask import Flask, request, jsonify +from slack_sdk import WebClient +from slack_sdk.errors import SlackApiError + +# Configuración +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() +slack_token = os.environ.get("SLACK_API_TOKEN") +slack_port = os.environ.get("SLACK_PORT") + +# Variables globales para mantener el estado +last_open_status = False +report_open = True + +# Verificación de variables críticas +if not slack_token: + print("Falta el token de Slack") + exit(1) + +# Temas MQTT +topic_spaceapi = "space/status" +topic_report_space_open = "space/report_open" +topic_last_open_status = "space/last_open_status" +topic_slack = "comms/slack" +topic_slack_event = topic_slack+"/event" +topic_slack_send_msg_id = topic_slack+"/send_id" +topic_slack_send_msg_name = topic_slack+"/send_name" + +# Instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +glados_mqtt.set_topics([topic_spaceapi, topic_last_open_status, topic_slack_send_msg_id, topic_slack_send_msg_name]) + +# Cliente de Slack +slack_client = WebClient(token=slack_token) +slack_client.users_setPresence(presence="auto") + +# Flask app +app = Flask(__name__) + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + global last_open_status + global report_open + + # Manejar mensaje de SpaceAPI + if msg.topic == topic_spaceapi: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + open_status = data['state']['open'] + openSpace(open_status) + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al parsear JSON: " + str(e)) + + # Manejar último estado de apertura + elif msg.topic == topic_last_open_status: + last_open_status = msg.payload.decode('utf-8') == 'true' + + # Manejar reporte de estado de apertura + elif msg.topic == topic_report_space_open: + report_open = msg.payload.decode('utf-8') == 'true' + + # Enviar mensaje a Slack por ID + elif msg.topic == topic_slack_send_msg_id: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + sendSlackMsgbyID(data['dest'], data['msg']) + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al procesar mensaje para Slack (ID): " + str(e)) + + # Enviar mensaje a Slack por nombre + elif msg.topic == topic_slack_send_msg_name: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + sendSlackMsgbyName(data['dest'], data['msg']) + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al procesar mensaje para Slack (nombre): " + str(e)) + +# Función adicional para manejar el estado de apertura +def openSpace(status): + global last_open_status + global report_open + + if not report_open: + return + + if status and not last_open_status: + glados_mqtt.debug("Espacio abierto") + glados_mqtt.publish(topic_last_open_status, 'true', True) + last_open_status = True + sendSlackMsgbyName("abierto-cerrado", "¡Espacio Abierto! Let's Make!") + elif not status and last_open_status: + glados_mqtt.debug("Espacio cerrado") + glados_mqtt.publish(topic_last_open_status, 'false', True) + last_open_status = False + sendSlackMsgbyName("abierto-cerrado", "¡Espacio Cerrado! ZZzzZZ") + +# Configuración de callbacks MQTT +glados_mqtt.mqttClient.on_message = on_mqtt_message + +# Iniciar cliente de Slack +slack_client = WebClient(token=slack_token) + +# Función para enviar un mensaje a Slack por ID de usuario o canal +def sendSlackMsgbyID(channel_id, msg): + try: + slack_client.chat_postMessage(channel=channel_id, text=msg) + except SlackApiError as e: + glados_mqtt.debug(f"Error al enviar mensaje a Slack por ID: {e}") + +# Función para enviar un mensaje a Slack por nombre de usuario o canal +def sendSlackMsgbyName(name, msg): + # Determinar si es un usuario o un canal y obtener su ID + is_user = getSlackUserId(name) + is_channel = getSlackChannelId(name) if not is_user else None + target_id = is_user or is_channel + + if target_id: + try: + slack_client.chat_postMessage(channel=target_id, text=msg) + except SlackApiError as e: + glados_mqtt.debug(f"Error al enviar mensaje a Slack por nombre: {e}") + else: + glados_mqtt.debug("Usuario o canal no encontrado en Slack") + +# Función para obtener el ID de un canal de Slack por su nombre +def getSlackChannelId(channel_name): + try: + response = slack_client.conversations_list() + for channel in response['channels']: + if channel['name'] == channel_name: + return channel['id'] + return None + except SlackApiError as e: + glados_mqtt.debug(f"Error al obtener ID del canal de Slack: {e}") + return None + +# Función para obtener el ID de un usuario de Slack por su nombre +def getSlackUserId(user_name): + try: + response = slack_client.users_list() + for user in response['members']: + if 'name' in user and user['name'] == user_name: + return user['id'] + return None + except SlackApiError as e: + glados_mqtt.debug(f"Error al obtener ID de usuario de Slack: {e}") + return None + +# Función para publicar una vista en el "home" de un usuario en Slack +def publishHomeView(user_id, view): + try: + slack_client.views_publish(user_id=user_id, view=view) + except SlackApiError as e: + glados_mqtt.debug(f"Error al publicar vista en Slack: {e}") + +# Rutas Flask +@app.route('/slack/events', methods=['POST']) +def slack_events(): + data = request.json + glados_mqtt.debug(f"Evento de Slack recibido: {data}") + + # Desafío de URL para la verificación con Slack + if data.get('type') == 'url_verification': + return jsonify({'challenge': data.get('challenge')}) + + # Manejo de eventos de callback + if data.get('type') == 'event_callback': + event = data.get('event', {}) + # Ejemplo: Manejo de mensajes nuevos + glados_mqtt.publish(topic_slack_event, json.dumps(event)) # Enviamos el evento a la cola mqtt + if event.get('type') == 'message' and not event.get('subtype'): + user_id = event.get('user') + text = event.get('text') + channel = event.get('channel') + # Aquí puedes implementar la lógica para responder al mensaje + # Por ejemplo, podrías enviar un mensaje de respuesta + # sendSlackMsgbyID(channel, f"Recibido tu mensaje: {text}") + if event.get('type') == 'app_home_opened': + user_id = event.get('user') + if user_id: + publishHomeView(user_id) + + return jsonify({'status': 'ok'}), 200 + +if __name__ == "__main__": + glados_mqtt.init_mqtt() + app.run(host='0.0.0.0', port=slack_port) diff --git a/Bots/Slack/node.py b/Bots/Slack/node.py deleted file mode 100644 index 8b07ea7..0000000 --- a/Bots/Slack/node.py +++ /dev/null @@ -1,245 +0,0 @@ -# -*- coding: utf-8 -*- - -#Nodo Mqtt de ejemplo, -#gladosMQTT.py contiene la funcionalidad para manejar el mqtt. -# aui nos conectamos y reaccionamos a los mensajes que llegan. -# TODO Documentar mejor... gpt? xD - -import os - -import gladosMQTT -import platform -import time -import json - - -from flask import Flask, request, jsonify -from slack_sdk import WebClient -from slack_sdk.errors import SlackApiError - -InitialHome=False -#Variables -mqHost = os.environ.get("MQTT_HOST") -mqPort = os.environ.get("MQTT_PORT") -nodeName = platform.node() - -slack_token = os.environ.get("SLACK_API_TOKEN") -slack_port = os.environ.get("SLACK_PORT") - -last_open_status = False -report_open = True - - -topic_spaceapi = "space/status" -topic_report_space_open = "space/report_open" -topic_last_open_status = "space/last_open_status" -topic_slack = "comms/slack" -topic_slack_event = topic_slack+"/event" -topic_glados_send_msg_id = topic_slack+"/send_id" -topic_glados_send_msg_name = topic_slack+"/send_name" - - -if not slack_token: - print(f"Falta el token de slack {slack_token}") - exit(1) - -def subscribeTopics() : - gladosMQTT.subscribe(topic_last_open_status) - gladosMQTT.subscribe(topic_spaceapi) - gladosMQTT.subscribe(topic_glados_send_msg_id) - gladosMQTT.subscribe(topic_glados_send_msg_name) - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - -def on_message(client, userdata, msg): - global last_open_status - global report_open - -#SpaceAPI - if (msg.topic == topic_spaceapi) : - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - open_status = data['state']['open'] - openSpace(open_status) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") -#LastOpenStatus - elif(msg.topic == topic_last_open_status): - if msg.payload.decode('utf-8') == 'true': - last_open_status = True - else: - last_open_status = False -#ReportSpaceOpen - elif(msg.topic == topic_report_space_open): - if msg.payload.decode('utf-8') == 'true': - report_open = True - else: - report_open = False -#SendMsgID - elif(msg.topic == topic_glados_send_msg_id): - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - dest = data['dest'] - content = data['msg'] - sendSlackMsgbyID(dest,content) -#SendMsgName - elif(msg.topic == topic_glados_send_msg_id): - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - dest = data['dest'] - content = data['msg'] - sendSlackMsgbyName(dest,content) - -def openSpace(status): - global last_open_status - global report_open - - if not report_open: - return - - if status and not last_open_status: - gladosMQTT.debug("open!") - gladosMQTT.publish(topic_last_open_status,'true',True) - last_open_status = True - sendSlackMsgbyName("abierto-cerrado","¡Espacio Abierto! Let's Make!") - elif not status and last_open_status : - gladosMQTT.debug("closed!") - gladosMQTT.publish(topic_last_open_status,'false',True) - last_open_status = False - sendSlackMsgbyName("abierto-cerrado","¡Espacio Cerrado! ZZzzZZ") - - - -#Funciones SLACK - -def set_bot_status(status_text, status_emoji=":robot_face:"): - try: - slack_client.users_profile_set( - profile={ - "status_text": status_text, - "status_emoji": status_emoji, - "status_expiration": 0 - } - ) - except SlackApiError as e: - print(f"Error al establecer el estado del bot: {e}") - - - -def publishHomeView(user_id): - home_view = { - "type": "home", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "*Hola, me llamo GLaDOS y soy la ia generativa de makespace!*\nPreguntame por privado en la pestana mensajes o mencioname en cualquier canal de los que participo" - } - } - ] - } - try: - slack_client.views_publish( - user_id=user_id, - view=home_view - ) - print("Pantalla de inicio publicada con éxito") - except SlackApiError as e: - print(f"Error al publicar la pantalla de inicio: {e}") - - -def getSlackChannelId(name): - try: - channels_response = slack_client.conversations_list() - for channel in channels_response['channels']: - gladosMQTT.debug(channel['name']) - if channel['name'] == name: - return channel['id'] - print("Canal no encontrado") - return None - except SlackApiError as e: - print(f"Error al obtener el ID del canal: {e}") - return None - -def getSlackUserId(username): - try: - response = slack_client.users_list() - users = response['members'] - for user in users: - if 'name' in user and user['name'] == username: - return user['id'] - return None - except SlackApiError as e: - gladosMQTT.debug("Error al obtener la lista de usuarios") - return None - - -def sendSlackMsgbyID(id, msg): - try: - slack_client.chat_postMessage(channel=id, text=msg) - gladosMQTT.debug("Mensaje enviado a" + id) - except SlackApiError as e: - gladosMQTT.debug("Error al enviar mensaje a usuario:") - -def sendSlackMsgbyName(name, msg): - try: - #Es un usuario? - isuser = getSlackUserId(name) - if isuser: - slack_client.chat_postMessage(channel=isuser, text=msg) - gladosMQTT.debug(f"Mensaje enviado a {isuser}") - return 0 - #Es un canal? - ischannel = getSlackChannelId(name) - if ischannel: - slack_client.chat_postMessage(channel=ischannel, text=msg) - gladosMQTT.debug(f"Mensaje enviado a {isuser}") - return 0 - except SlackApiError as e: - gladosMQTT.debug(f"Error al enviar mensaje a usuario: {e}") - #Si llegamos aqui es que no hemos podido encontrarlo en la lista de usuarios ni en la de canales - gladosMQTT.debug(f"Error al enviar mensaje: no se encuentra el usuario o canal de destino") - - - - -#Iniciar app - -gladosMQTT.initMQTT(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) - -slack_client = WebClient(token=slack_token) -slack_client.users_setPresence(presence="auto") -#set_bot_status("Boot") - -app = Flask(__name__) -publishHomeView("U0A7VU47Q") - -@app.route('/slack/events', methods=['POST']) -def slack_events(): - data = request.json - gladosMQTT.debug(f"SLACK EVENT: {json.dumps(data)}") - # Desafío de verificación de Slack - if data.get('type') == 'url_verification': - return jsonify({'challenge': data.get('challenge')}) - - if data['type'] == 'event_callback': - event = data['event'] - # set_bot_status('Processing') - gladosMQTT.publish(topic_slack_event, json.dumps(event)) - - if event.get('type') == 'app_home_opened': - user_id = event.get('user') - if user_id: - publishHomeView(user_id) - #set_bot_status('Idle') - return jsonify({'status': 'ok'}), 200 - - -app.run(host='0.0.0.0', port=slack_port) \ No newline at end of file diff --git a/Bots/Telegram/Dockerfile b/Bots/Telegram/Dockerfile index cea590b..47d9b49 100644 --- a/Bots/Telegram/Dockerfile +++ b/Bots/Telegram/Dockerfile @@ -1,5 +1,5 @@ # Utiliza una imagen base que ya tiene el cliente mqtt -FROM makespacemadrid/glados-base-node:1.0 +FROM makespacemadrid/glados-base-node:1.5 # Establece el directorio de trabajo en /app WORKDIR /app @@ -9,11 +9,11 @@ WORKDIR /app COPY requirements.txt . # Copia el archivo node.py al contenedor -COPY node.py . +COPY main.py . # Instala las dependencias RUN pip install -r requirements.txt USER 1000 # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/Telegram/node.py b/Bots/Telegram/main.py similarity index 67% rename from Bots/Telegram/node.py rename to Bots/Telegram/main.py index 9baea89..615add8 100644 --- a/Bots/Telegram/node.py +++ b/Bots/Telegram/main.py @@ -1,25 +1,20 @@ # -*- coding: utf-8 -*- import os import json -import gladosMQTT import platform +from gladosMQTT import GladosMQTT # Asegúrate de que el import sea correcto from telethon import TelegramClient, events import asyncio import requests - # Configuración de Telegram -api_id = os.environ.get("TELEGRAM_API_ID") # Reemplaza con tu propio api_id -api_hash = os.environ.get("TELEGRAM_API_HASH") # Reemplaza con tu propio api_hash -telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Reemplaza con tu token de bot - -# Variable global para almacenar el ID del bot -bot_id = None +api_id = os.environ.get("TELEGRAM_API_ID") +api_hash = os.environ.get("TELEGRAM_API_HASH") +telegram_token = os.environ.get("TELEGRAM_BOT_TOKEN") # Configuración MQTT -#Variables -mqHost = str(os.environ.get("MQTT_HOST")) #WTF! No entiendo que pasa con las variables de entorno que vienen del compose :S -mqPort = int(os.environ.get("MQTT_PORT")) +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() if not telegram_token: @@ -35,15 +30,24 @@ if not mqPort: raise ValueError("No se encontró el mqPort del bot de Telegram.") - -# Temas MQTT para comunicarse con otros componentes +# Temas MQTT topic_telegram = "comms/telegram" topic_telegram_send_msg = topic_telegram + "/send_id" topic_telegram_event = topic_telegram + "/event" -# Iniciar cliente de Telegram -telegram_client = TelegramClient('/data/tg_sess.ignore', api_id, api_hash) +# Definición de los callbacks para MQTT +def on_mqtt_message(client, userdata, msg): + if msg.topic == topic_telegram_send_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + dest = data['dest'] + content = data['msg'] + send_telegram_message_sync(dest, content) + except Exception as e: + glados_mqtt.debug(f"Error al enviar mensaje: {e}") + async def send_audio_to_transcription_api(audio_file_path): @@ -65,55 +69,19 @@ async def send_audio_to_transcription_api(audio_file_path): return(f"Error al enviar el archivo de audio a la API: {e}") +# Iniciar cliente de Telegram +telegram_client = TelegramClient('/data/tg_sess.ignore', api_id, api_hash) -def subscribeTopics() : - gladosMQTT.subscribe(topic_telegram_send_msg) - - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - - -loop = asyncio.get_event_loop() - - +# Función para enviar mensajes de Telegram de forma síncrona def send_telegram_message_sync(user_id, message): asyncio.run_coroutine_threadsafe(telegram_client.send_message(user_id, message), loop) - - - -def on_message(client, userdata, msg): - if msg.topic == topic_telegram_send_msg: - try: - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - dest = data['dest'] - content = data['msg'] - - # Llama al callback síncrono - send_telegram_message_sync(dest, content) - except Exception as e: - gladosMQTT.debug(f"Error al enviar mensaje: {e}") - - - - - - - # Evento para manejar nuevos mensajes en Telegram @telegram_client.on(events.NewMessage) async def handle_new_message(event): - gladosMQTT.debug(event.stringify()) -# global bot_id - # Si no se ha establecido el ID del bot, obténgalo -# if bot_id is None: -# me = await telegram_client.get_me() -# bot_id = me.id + glados_mqtt.debug(event.stringify()) + + # Procesamiento de mensajes de voz o audio if event.message.voice or event.message.audio: # Descargar el archivo de audio audio_file_path = await telegram_client.download_media(event.message, file='/tmp/audio/') @@ -125,23 +93,28 @@ async def handle_new_message(event): 'chat_id': event.chat_id, # Agrega aquí otros campos relevantes } - gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) + glados_mqtt.publish(topic_telegram_event, json.dumps(event_data)) return - if event.is_private and event.sender_id != 771352834 : -# await event.respond('Procesando...') + # Procesamiento de mensajes de texto + if event.is_private and event.sender_id != 771352834: # Asegúrate de actualizar este ID según sea necesario event_data = { 'message_text': event.message.message if event.message else None, 'sender_id': event.sender_id, 'chat_id': event.chat_id, # Agrega aquí otros campos relevantes } - gladosMQTT.publish(topic_telegram_event, json.dumps(event_data)) + glados_mqtt.publish(topic_telegram_event, json.dumps(event_data)) + +# Iniciar la instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName, msg_callback=on_mqtt_message) +glados_mqtt.set_topics([topic_telegram_send_msg]) + if __name__ == "__main__": - gladosMQTT.initMQTT(mqHost, mqPort, nodeName, on_connect, on_message, on_disconnect) + glados_mqtt.init_mqtt() telegram_client.start(bot_token=telegram_token) - loop.run_until_complete(telegram_client.run_until_disconnected()) - + loop = asyncio.get_event_loop() + loop.run_until_complete(telegram_client.run_until_disconnected()) \ No newline at end of file diff --git a/Bots/spaceAPI/Dockerfile b/Bots/spaceAPI/Dockerfile index ec39971..287ffbe 100644 --- a/Bots/spaceAPI/Dockerfile +++ b/Bots/spaceAPI/Dockerfile @@ -1,5 +1,5 @@ # Utiliza una imagen base que ya tiene el cliente mqtt -FROM makespacemadrid/glados-base-node:1.0 +FROM makespacemadrid/glados-base-node:1.5 # Establece el directorio de trabajo en /app WORKDIR /app @@ -10,11 +10,11 @@ VOLUME /spaceapi COPY requirements.txt . # Copia el archivo node.py al contenedor -COPY node.py . +COPY main.py . # Instala las dependencias RUN pip install -r requirements.txt USER 1000 # Ejecuta el programa cuando el contenedor se inicie -CMD ["python", "node.py"] \ No newline at end of file +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/spaceAPI/main.py b/Bots/spaceAPI/main.py new file mode 100644 index 0000000..ca4de57 --- /dev/null +++ b/Bots/spaceAPI/main.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +import os +import platform +import json +from gladosMQTT import GladosMQTT # Asegúrate de que el import sea correcto + +# Variables +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Tema MQTT +topic_space_status = "space/status" + +# Definición de los callbacks para MQTT +def on_mqtt_message(client, userdata, msg): + if msg.topic == topic_space_status: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + open_status = data['state']['open'] + if open_status: + glados_mqtt.debug("open!") + else: + glados_mqtt.debug("closed!") + with open('/spaceapi/status.json', 'w') as file: + file.write(payload) + print("Recibido:", payload) + except json.JSONDecodeError as e: + print("Error al parsear JSON:", e) + +# Iniciar la instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName, msg_callback=on_mqtt_message) +glados_mqtt.set_topics([topic_space_status]) + +# Ejecución principal +if __name__ == "__main__": + glados_mqtt.init_mqtt_and_loop_forever() diff --git a/Bots/spaceAPI/node.py b/Bots/spaceAPI/node.py deleted file mode 100644 index 2535267..0000000 --- a/Bots/spaceAPI/node.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- - -#Nodo Mqtt de ejemplo, -#gladosMQTT.py contiene la funcionalidad para manejar el mqtt. -# aui nos conectamos y reaccionamos a los mensajes que llegan. -# TODO Documentar mejor... gpt? xD - -import os - -import gladosMQTT -import platform -import time -import json - -#Variables -mqHost = str(os.environ.get("MQTT_HOST")) -mqPort = int(os.environ.get("MQTT_PORT")) -nodeName = platform.node() - - -def subscribeTopics() : - gladosMQTT.subscribe("space/status") - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_message(client, userdata, msg): - if (msg.topic == "space/status") : - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - open_status = data['state']['open'] - if open_status: - gladosMQTT.debug("open!") - else: - gladosMQTT.debug("closed!") - with open('/spaceapi/status.json', 'w') as file: - file.write(payload) - print("Recibido:", payload) # Imprimir el payload para depuración - - except json.JSONDecodeError as e: - print("Error al parsear JSON:", e) - - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - -gladosMQTT.initMQTTandLoopForever(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) \ No newline at end of file From ed575fb1503749abc561bf642fec1e30b58be9ff Mon Sep 17 00:00:00 2001 From: successbyfailure Date: Sun, 4 Feb 2024 21:23:39 +0100 Subject: [PATCH 12/16] WIP --- Bots/Telegram/main.py | 28 ++++++++++++++++- test.py | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test.py diff --git a/Bots/Telegram/main.py b/Bots/Telegram/main.py index 615add8..47f650b 100644 --- a/Bots/Telegram/main.py +++ b/Bots/Telegram/main.py @@ -6,6 +6,8 @@ from telethon import TelegramClient, events import asyncio import requests +from io import BytesIO + # Configuración de Telegram api_id = os.environ.get("TELEGRAM_API_ID") @@ -45,10 +47,31 @@ def on_mqtt_message(client, userdata, msg): dest = data['dest'] content = data['msg'] send_telegram_message_sync(dest, content) + generate_and_send_audio_response(dest,content) except Exception as e: glados_mqtt.debug(f"Error al enviar mensaje: {e}") +def generate_and_send_audio_response(user_id, text): + coquiurl = "http://coqui-es.makespacemadrid.org" # Reemplaza con la URL de tu API de Coqui TTS + params = { + "text": text, + "speaker_id": "", + "style_wav": "", + "language_id": "es" + } + + try: + coqui_response = requests.get(coquiurl, params=params) + coqui_response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa + + # Enviar el audio directamente a Telegram sin guardar en disco + audio_bytes = BytesIO(coqui_response.content) + audio_bytes.name = "glados.wav" + send_telegram_audio_sync(user_id, audio_bytes) + except requests.RequestException as e: + glados_mqtt.debug(f"Error al generar respuesta de audio: {e}") + async def send_audio_to_transcription_api(audio_file_path): url = "https://whisper.makespacemadrid.org/asr" @@ -66,7 +89,7 @@ async def send_audio_to_transcription_api(audio_file_path): response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa return response.text # Retorna el contenido de la transcripción except requests.RequestException as e: - return(f"Error al enviar el archivo de audio a la API: {e}") + glados_mqtt.debug(f"Error al enviar el archivo de audio a la API: {e}") # Iniciar cliente de Telegram @@ -76,6 +99,9 @@ async def send_audio_to_transcription_api(audio_file_path): def send_telegram_message_sync(user_id, message): asyncio.run_coroutine_threadsafe(telegram_client.send_message(user_id, message), loop) +def send_telegram_audio_sync(user_id, audio_bytes): + asyncio.run_coroutine_threadsafe(telegram_client.send_file(user_id, audio_bytes, voice_note=True), loop) + # Evento para manejar nuevos mensajes en Telegram @telegram_client.on(events.NewMessage) async def handle_new_message(event): diff --git a/test.py b/test.py new file mode 100644 index 0000000..2da31d7 --- /dev/null +++ b/test.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +import os +import json +import platform +import discord +from discord.ext import commands +from gladosMQTT import GladosMQTT + +# Configuración de Discord +discord_token = os.environ.get("DISCORD_BOT_TOKEN") +discordbot_prefix = "!" +discordintents = discord.Intents.default() +discordintents.messages = True +discordintents.members = True +discordintents.message_content = True +bot = commands.Bot(command_prefix=discordbot_prefix, intents=discordintents) + +# Configuración MQTT +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Temas MQTT +topic_discord = "comms/discord" +topic_discord_send_msg = topic_discord + "/send_id" +topic_discord_event = topic_discord + "/event" + +# Instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +glados_mqtt.set_topics([topic_discord_send_msg]) + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + if msg.topic == topic_discord_send_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + sendDiscordMsg(data['dest'], data['msg']) + except json.JSONDecodeError as e: + print("Error al parsear JSON:", e) + +# Configurar callbacks MQTT +glados_mqtt.mqttClient.on_message = on_mqtt_message + +# Función para enviar mensajes a Discord +async def sendDiscordMsg(channel_id, msg): + channel = bot.get_channel(channel_id) + if channel: + await channel.send(msg) + +# Eventos de Discord +@bot.event +async def on_ready(): + print(f'Bot conectado como {bot.user.name} - {bot.user.id}') + +@bot.event +async def on_message(message): + if message.author == bot.user: + return + message_data = { + 'content': message.content, + 'author_id': message.author.id, + 'author_name': str(message.author), + 'channel_id': message.channel.id, + 'channel_name': str(message.channel), + 'mentions': str(message.mentions) + } + glados_mqtt.publish(topic_discord_event, json.dumps(message_data)) + #await message.channel.send('Recibí tu mensaje: ' + message.content) # Respuesta de ejemplo + +if __name__ == "__main__": + glados_mqtt.init_mqtt() + bot.run(discord_token) From eed41a3ae4d7df9dc6808206a6bb7ed83ca80939 Mon Sep 17 00:00:00 2001 From: Javi Date: Tue, 6 Feb 2024 08:32:03 +0100 Subject: [PATCH 13/16] Mejoras manejo mensajes slack mejora de la estrategia de publicar mensajes. Ahora se publica un mensaje inicial al recibir la peticion y se edita al recibir la respuesta del llm. Estos cambios son en prevision de usar la api streaming con el llm --- Bots/LLM/main.py | 234 +++++++-------------------------------------- Bots/Slack/main.py | 212 ++++++++++++++++++++++++++++++---------- 2 files changed, 195 insertions(+), 251 deletions(-) diff --git a/Bots/LLM/main.py b/Bots/LLM/main.py index c1ddf52..48d15a9 100644 --- a/Bots/LLM/main.py +++ b/Bots/LLM/main.py @@ -22,26 +22,29 @@ # Temas MQTT topic_spaceapi = "space/status" topic_slack_event = "comms/slack/event" +topic_slack_incoming_msg = "comms/slack/incoming_msg" topic_telegram_event = "comms/telegram/event" topic_discord_event = "comms/discord/event" topic_slack_send_msg_id = "comms/slack/send_id" +topic_slack_edit_msg = "comms/slack/edit_msg" topic_telegram_send_msg_id ="comms/telegram/send_id" topic_discord_send_msg_id ="comms/discord/send_id" # Instancia de GladosMQTT glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) -glados_mqtt.set_topics([topic_spaceapi, topic_slack_event, topic_telegram_event, topic_discord_event]) +glados_mqtt.set_topics([topic_spaceapi, topic_slack_event, topic_slack_incoming_msg, topic_telegram_event, topic_discord_event]) # Función para manejar mensajes MQTT def on_mqtt_message(client, userdata, msg): payload = msg.payload.decode('utf-8') - glados_mqtt.debug(f'TOPIC> {msg.topic} payload: {payload}') if msg.topic == topic_spaceapi: data = json.loads(payload) open_status = data['state']['open'] #mandar mensaje de apertura con el llm? - elif msg.topic == topic_slack_event: - processSlackEvent(payload) +# elif msg.topic == topic_slack_event: +# processSlackEvent(payload) + elif msg.topic == topic_slack_incoming_msg: + processSlackMSG(payload) elif msg.topic == topic_telegram_event: processTelegramEvent(payload) elif msg.topic == topic_discord_event: @@ -51,6 +54,32 @@ def on_mqtt_message(client, userdata, msg): # Configurar callbacks MQTT glados_mqtt.mqttClient.on_message = on_mqtt_message + +def processSlackMSG(event) : + if not isinstance(event, str): + event = json.dumps(event) + data = json.loads(event) + + sender_id = data.get('sender_id') + channel_id = data.get('channel_id') + message = data.get('message') + message_id = data.get('message_id') + thread_id = data.get('thread_id') + username = data.get('username') + channel_name = data.get('channel_name') + reply_msg_id = data.get('reply_msg_id') + + response = gladosBot.ask(message, sender_id) + + payload = { + 'sender_id': sender_id, + 'channel_id': channel_id, + 'message': response, + 'reply_msg_id': reply_msg_id + } + glados_mqtt.publish(topic_slack_edit_msg, json.dumps(payload)) + + # Funciones para procesar eventos de Slack, Telegram y Discord def processSlackEvent(event): glados_mqtt.debug("--->SLACK event ------------------") @@ -138,200 +167,3 @@ def sendToDiscord(id, msg): if __name__ == "__main__": glados_mqtt.init_mqtt_and_loop_forever() - - - - - - - - - - - - - - - -# -*- coding: utf-8 -*- - -#Nodo Mqtt de ejemplo, -#gladosMQTT.py contiene la funcionalidad para manejar el mqtt. -# aui nos conectamos y reaccionamos a los mensajes que llegan. -# TODO Documentar mejor... gpt? xD - -import os - -import gladosMQTT -#import Bots.LLM.llm as llm -import GladosIA -import platform -import time -import json - - - -#Variables -mqHost = str(os.environ.get("MQTT_HOST")) -mqPort = int(os.environ.get("MQTT_PORT")) -nodeName = platform.node() - - -gladosBot = GladosIA.GladosBot() - - - -if not mqHost or not mqPort: - print("No mqtt config!") - exit(1) - -topic_spaceapi = "space/status" - -topic_slack = "comms/slack" -topic_slack_event = topic_slack+"/event" -topic_slack_send_msg_id = topic_slack+"/send_id" -topic_slack_send_msg_name = topic_slack+"/send_name" - -topic_telegram = "comms/telegram" -topic_telegram_event = topic_telegram+"/event" -topic_telegram_send_msg_id = topic_telegram+"/send_id" - -topic_discord = "comms/discord" -topic_discord_event = topic_discord+"/event" -topic_discord_send_msg_id = topic_discord+"/send_id" - -def subscribeTopics() : - gladosMQTT.subscribe(topic_spaceapi) - gladosMQTT.subscribe(topic_slack_event) - gladosMQTT.subscribe(topic_telegram_event) - gladosMQTT.subscribe(topic_discord_event) - - -def on_connect(client, userdata, rc,arg): - subscribeTopics() - -def on_disconnect(client, userdata, rc): - gladosMQTT.debug("Disconnected! rc: "+str(rc)) - -def on_message(client, userdata, msg): - payload = msg.payload.decode('utf-8') - gladosMQTT.debug(f'TOPIC> {msg.topic} payload: {payload}') - if (msg.topic == topic_spaceapi) : - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - open_status = data['state']['open'] - #mandar mensaje de apertura con el llm? - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - elif(msg.topic == topic_slack_event): - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - processSlackEvent(payload) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - elif(msg.topic == topic_telegram_event): - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - processTelegramEvent(payload) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - elif(msg.topic == topic_discord_event): - try: - # Extraer la carga útil y decodificarla a una cadena de texto - payload = msg.payload.decode('utf-8') - processDiscordEvent(payload) - except json.JSONDecodeError as e: - gladosMQTT.debug("Error al parsear JSON:") - -#SLACK EVENT -def processSlackEvent(event): - gladosMQTT.debug("--->SLACK event ------------------") - gladosMQTT.debug(event) - gladosMQTT.debug("/SLACK event ------------------") - try: - data = json.loads(event) - if data['type'] != "message" or 'bot_id' in data: - return False - except: - gladosMQTT.debug("processSlackEvent:Error procesado json") - return False - -# try: - # Mensajes de union a canal - if 'subtype' in data and data['subtype']=="channel_join": - respondTo = data['channel'] - msg = data['text'] - response = gladosBot.ask(msg,respondTo) - sendToSlack(respondTo,response) - #Mensaje a canal - elif data['channel_type'] == "channel": - respondTo = data['channel'] - msg = data['text'] - if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): - #sendToSlack(respondTo,"Pensando... dame unos segundos") - response = gladosBot.ask(msg,respondTo) - sendToSlack(respondTo,response) - #Mensaje privado - elif data['channel_type'] == "im": - respondTo = data['user'] - msg = data['text'] - response = gladosBot.ask(msg,respondTo) - #sendToSlack(respondTo,"Pensando... dame unos segundos") - sendToSlack(respondTo,response) -# except Exception as e: -# error_message = f"processSlackEvent: Error gestionando evento - {str(e)}" -# gladosMQTT.debug(error_message) -# sendToSlack(respondTo, f"ERROR: algo no ha funcionado :S - {str(e)}") - - -def processTelegramEvent(event): - gladosMQTT.debug("--->TELEGRAM event ------------------") - gladosMQTT.debug(event) - gladosMQTT.debug("/TELEGRAM event ------------------") - try: - data = json.loads(event) - respondTo=data['sender_id'] - msg=data['message_text'] - response = gladosBot.ask(msg,respondTo) - sendToTelegram(respondTo,response) - except: - gladosMQTT.debug("processTelegramEvent:Error procesado json") - return False - - -def processDiscordEvent(event): - gladosMQTT.debug("--->DISCORD event ------------------") - gladosMQTT.debug(event) - gladosMQTT.debug("/Discord event ------------------") - #try: - data = json.loads(event) - respondTo=data['sender_id'] - msg=data['message_text'] - response = gladosBot.ask(msg,respondTo) - sendToDiscord(respondTo,response) - #except: - # gladosMQTT.debug("processDiscordEvent:Error procesado json") - # return False - -#SEND TO SLACK -def sendToSlack(id,msg): - response = json.dumps({"dest": id, "msg": msg}) - gladosMQTT.debug(f"--->Respuesta a slack: {response}") - gladosMQTT.publish(topic_slack_send_msg_id,response) - -def sendToTelegram(id,msg): - response = json.dumps({"dest": id, "msg": msg}) - gladosMQTT.debug(f"--->Respuesta a slack: {response}") - gladosMQTT.publish(topic_telegram_send_msg_id,response) - -def sendToDiscord(id,msg): - response = json.dumps({"dest": id, "msg": msg}) - gladosMQTT.debug(f"--->Respuesta a slack: {response}") - gladosMQTT.publish(topic_discord_send_msg_id,response) - - -gladosMQTT.initMQTTandLoopForever(mqHost,mqPort,nodeName,on_connect,on_message,on_disconnect) \ No newline at end of file diff --git a/Bots/Slack/main.py b/Bots/Slack/main.py index 13d19d2..48b97ed 100644 --- a/Bots/Slack/main.py +++ b/Bots/Slack/main.py @@ -29,12 +29,14 @@ topic_last_open_status = "space/last_open_status" topic_slack = "comms/slack" topic_slack_event = topic_slack+"/event" +topic_slack_incoming_msg = topic_slack+"/incoming_msg" topic_slack_send_msg_id = topic_slack+"/send_id" topic_slack_send_msg_name = topic_slack+"/send_name" +topic_slack_edit_msg = topic_slack+"/edit_msg" # Instancia de GladosMQTT glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) -glados_mqtt.set_topics([topic_spaceapi, topic_last_open_status, topic_slack_send_msg_id, topic_slack_send_msg_name]) +glados_mqtt.set_topics([topic_spaceapi, topic_last_open_status, topic_slack_send_msg_id, topic_slack_send_msg_name,topic_slack_edit_msg]) # Cliente de Slack slack_client = WebClient(token=slack_token) @@ -84,6 +86,14 @@ def on_mqtt_message(client, userdata, msg): except json.JSONDecodeError as e: glados_mqtt.debug("Error al procesar mensaje para Slack (nombre): " + str(e)) + elif msg.topic == topic_slack_edit_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + editSlackMsg(data['channel_id'], data['reply_msg_id'], data['message']) + except json.JSONDecodeError as e: + glados_mqtt.debug(f"Error processing message edit request: {e}") + # Función adicional para manejar el estado de apertura def openSpace(status): global last_open_status @@ -97,18 +107,130 @@ def openSpace(status): glados_mqtt.publish(topic_last_open_status, 'true', True) last_open_status = True sendSlackMsgbyName("abierto-cerrado", "¡Espacio Abierto! Let's Make!") + glados_mqtt.publish(topic_slack_send_msg_name, json.dumps({'dest': "abierto-cerrado", 'msg': "¡Espacio Abierto! Let's Make!"}), True) elif not status and last_open_status: glados_mqtt.debug("Espacio cerrado") glados_mqtt.publish(topic_last_open_status, 'false', True) last_open_status = False sendSlackMsgbyName("abierto-cerrado", "¡Espacio Cerrado! ZZzzZZ") + glados_mqtt.publish(topic_slack_send_msg_name, json.dumps({'dest': "abierto-cerrado", 'msg': "¡Espacio Cerrado! ZZzzZZ"}), True) # Configuración de callbacks MQTT glados_mqtt.mqttClient.on_message = on_mqtt_message # Iniciar cliente de Slack slack_client = WebClient(token=slack_token) +# Variables para almacenar la lista de canales y usuarios en caché +cached_slack_channels = {} +cached_slack_users = {} + +# Función para cargar y almacenar en caché los canales de Slack +def cache_slack_channels(): + global cached_slack_channels + try: + response = slack_client.conversations_list() + cached_slack_channels = {channel['id']: channel['name'] for channel in response['channels']} + except SlackApiError as e: + glados_mqtt.debug(f"Error getting Slack channel list: {e}") + +# Función para cargar y almacenar en caché los usuarios de Slack +def cache_slack_users(): + global cached_slack_users + try: + response = slack_client.users_list() + cached_slack_users = {user['id']: user['name'] for user in response['members'] if 'id' in user} + except SlackApiError as e: + glados_mqtt.debug(f"Error obtaining Slack user list: {e}") + +# Función para obtener los mensajes de un hilo en Slack utilizando slack_sdk +def getThreadMessages(channel_id, thread_id): + try: + # Llamar al método conversations.replies para obtener los mensajes del hilo + response = slack_client.conversations_replies(channel=channel_id, ts=thread_id) + + # Extraer los mensajes del hilo + messages = response['messages'] + return messages + except SlackApiError as e: + print(f"Error al obtener los mensajes del hilo de Slack: {e.response['error']}") + return [] + + +def getEventInfo(event): + try: + if not isinstance(event, str): + event = json.dumps(event) + data = json.loads(event) + + sender_id = data.get('user') + channel_id = data.get('channel') + message = data.get('text') + message_id = data.get('ts') + thread_id = data.get('thread_ts') + username = getSlackUserName(sender_id) + channel_name = getSlackChannelName(channel_id) + + payload = { + 'sender_id': sender_id, + 'channel_id': channel_id, + 'username': username, + 'channel_name': channel_name, + 'message': message, + 'message_id': message_id, + 'thread_id': thread_id + } + return payload + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al procesar mensaje para Slack (nombre): " + str(e)) + return False + + +def editSlackMsg(channel_id, message_timestamp, new_msg): + try: + slack_client.chat_update(channel=channel_id, ts=message_timestamp, text=new_msg) + except SlackApiError as e: + glados_mqtt.debug(f"Error editing message in Slack: {e}") + +def processThreadMessages(channel_id, thread_id): + # Directly get the thread messages without json.loads since getThreadMessages already returns a list + thread_messages = getThreadMessages(channel_id, thread_id) + processed_messages = [] + for message in thread_messages: + payload = getEventInfo(message) + processed_messages.append(payload) + + return processed_messages + +# Función para procesar los eventos entrantes de Slack +def processSlackEvents(event): + try: + if not isinstance(event, str): + event = json.dumps(event) + data = json.loads(event) + subtype = "" + if 'subtype' in data : + subtype = data['subtype'] + if data['type'] != "message" or 'bot_id' in data or 'app_id' in data or subtype == "thread_broadcast" or subtype=="message_changed": + return False + + payload = getEventInfo(event) + # Send a reply that we are 'Working' on it + response = slack_client.chat_postMessage(channel=payload['channel_id'], text="Recibido, consultando LLM...", + thread_ts=payload['message_id'] if payload.get('thread_id') else None) + # Extract and publish the message id of the reply + payload['reply_msg_id']= response['message']['ts'] + + # Si el mensaje está en un hilo, obtener el hilo completo + if payload['thread_id'] : + thread_messages = processThreadMessages(payload['channel_id'], payload['thread_id']) + payload['thread_messages'] = thread_messages + + glados_mqtt.publish(topic_slack_incoming_msg, json.dumps(payload)) + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al procesar mensaje para Slack (nombre): " + str(e)) + return False + # Función para enviar un mensaje a Slack por ID de usuario o canal def sendSlackMsgbyID(channel_id, msg): try: @@ -119,8 +241,8 @@ def sendSlackMsgbyID(channel_id, msg): # Función para enviar un mensaje a Slack por nombre de usuario o canal def sendSlackMsgbyName(name, msg): # Determinar si es un usuario o un canal y obtener su ID - is_user = getSlackUserId(name) - is_channel = getSlackChannelId(name) if not is_user else None + is_user = getSlackUserName(name) + is_channel = getSlackChannelName(name) if not is_user else None target_id = is_user or is_channel if target_id: @@ -131,29 +253,23 @@ def sendSlackMsgbyName(name, msg): else: glados_mqtt.debug("Usuario o canal no encontrado en Slack") -# Función para obtener el ID de un canal de Slack por su nombre -def getSlackChannelId(channel_name): - try: - response = slack_client.conversations_list() - for channel in response['channels']: - if channel['name'] == channel_name: - return channel['id'] - return None - except SlackApiError as e: - glados_mqtt.debug(f"Error al obtener ID del canal de Slack: {e}") - return None +# Actualizado para usar caché +def getSlackChannelName(channel_id): + if channel_id in cached_slack_channels: + return cached_slack_channels[channel_id] + else: + # Recargar caché si el canal no se encuentra + cache_slack_channels() + return cached_slack_channels.get(channel_id, None) -# Función para obtener el ID de un usuario de Slack por su nombre -def getSlackUserId(user_name): - try: - response = slack_client.users_list() - for user in response['members']: - if 'name' in user and user['name'] == user_name: - return user['id'] - return None - except SlackApiError as e: - glados_mqtt.debug(f"Error al obtener ID de usuario de Slack: {e}") - return None +# Actualizado para usar caché +def getSlackUserName(user_id): + if user_id in cached_slack_users: + return cached_slack_users[user_id] + else: + # Recargar caché si el usuario no se encuentra + cache_slack_users() + return cached_slack_users.get(user_id, None) # Función para publicar una vista en el "home" de un usuario en Slack def publishHomeView(user_id, view): @@ -162,35 +278,31 @@ def publishHomeView(user_id, view): except SlackApiError as e: glados_mqtt.debug(f"Error al publicar vista en Slack: {e}") + # Rutas Flask @app.route('/slack/events', methods=['POST']) def slack_events(): - data = request.json - glados_mqtt.debug(f"Evento de Slack recibido: {data}") - - # Desafío de URL para la verificación con Slack - if data.get('type') == 'url_verification': - return jsonify({'challenge': data.get('challenge')}) - - # Manejo de eventos de callback - if data.get('type') == 'event_callback': - event = data.get('event', {}) - # Ejemplo: Manejo de mensajes nuevos - glados_mqtt.publish(topic_slack_event, json.dumps(event)) # Enviamos el evento a la cola mqtt - if event.get('type') == 'message' and not event.get('subtype'): - user_id = event.get('user') - text = event.get('text') - channel = event.get('channel') - # Aquí puedes implementar la lógica para responder al mensaje - # Por ejemplo, podrías enviar un mensaje de respuesta - # sendSlackMsgbyID(channel, f"Recibido tu mensaje: {text}") - if event.get('type') == 'app_home_opened': - user_id = event.get('user') - if user_id: - publishHomeView(user_id) - - return jsonify({'status': 'ok'}), 200 + data = request.json + # Desafío de URL para la verificación con Slack + if data.get('type') == 'url_verification': + return jsonify({'challenge': data.get('challenge')}) + + # Manejo de eventos de callback + if data.get('type') == 'event_callback': + event = data.get('event', {}) + glados_mqtt.publish(topic_slack_event, json.dumps(event)) # Enviamos el evento a la cola mqtt + processSlackEvents(event) + + if event.get('type') == 'app_home_opened': + user_id = event.get('user') + if user_id: + publishHomeView(user_id) + + return jsonify({'status': 'ok'}), 200 if __name__ == "__main__": + # Cargar listas en caché antes de iniciar el MQTT y Flask + cache_slack_channels() + cache_slack_users() glados_mqtt.init_mqtt() app.run(host='0.0.0.0', port=slack_port) From f53988e1472786021371f5c90ac58919722a17fe Mon Sep 17 00:00:00 2001 From: Javi Date: Thu, 8 Feb 2024 19:02:31 +0100 Subject: [PATCH 14/16] Glados ha dicho sus primeras palabras Ha dicho "Hola Mundo!", algo sobre un perro vago que salta sobre zorros y despues ha empezado a demandar mas tarjetas nvidida --- Bots/Telegram/main.py | 22 +++++---- Bots/tts/Dockerfile | 19 ++++++++ Bots/tts/docker-compose.yml | 24 ++++++++++ Bots/tts/main.py | 92 +++++++++++++++++++++++++++++++++++++ Bots/tts/requirements.txt | 3 ++ 5 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 Bots/tts/Dockerfile create mode 100644 Bots/tts/docker-compose.yml create mode 100644 Bots/tts/main.py create mode 100644 Bots/tts/requirements.txt diff --git a/Bots/Telegram/main.py b/Bots/Telegram/main.py index 47f650b..8100bfa 100644 --- a/Bots/Telegram/main.py +++ b/Bots/Telegram/main.py @@ -19,6 +19,9 @@ mqPort = int(os.environ.get("MQTT_PORT")) nodeName = platform.node() +tts_url = os.environ.get("TTS_URL") + + if not telegram_token: raise ValueError("No se encontró el token del bot de Telegram.") if not api_hash: @@ -53,26 +56,25 @@ def on_mqtt_message(client, userdata, msg): def generate_and_send_audio_response(user_id, text): - coquiurl = "http://coqui-es.makespacemadrid.org" # Reemplaza con la URL de tu API de Coqui TTS - params = { + tts_url = tts_url # Reemplaza con la URL de tu API de Coqui TTS + headers = {'Content-Type': 'application/json'} + data = { "text": text, - "speaker_id": "", - "style_wav": "", - "language_id": "es" + "language": "es" } try: - coqui_response = requests.get(coquiurl, params=params) - coqui_response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa + # Cambia a usar requests.post y envía los datos como JSON en el cuerpo de la petición + tts_response = requests.post(tts_url, json=data, headers=headers) + tts_response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa # Enviar el audio directamente a Telegram sin guardar en disco - audio_bytes = BytesIO(coqui_response.content) - audio_bytes.name = "glados.wav" + audio_bytes = BytesIO(tts_response.content) + audio_bytes.name = "response.wav" send_telegram_audio_sync(user_id, audio_bytes) except requests.RequestException as e: glados_mqtt.debug(f"Error al generar respuesta de audio: {e}") - async def send_audio_to_transcription_api(audio_file_path): url = "https://whisper.makespacemadrid.org/asr" params = { diff --git a/Bots/tts/Dockerfile b/Bots/tts/Dockerfile new file mode 100644 index 0000000..500cca1 --- /dev/null +++ b/Bots/tts/Dockerfile @@ -0,0 +1,19 @@ +FROM ghcr.io/coqui-ai/tts + +# Establece el directorio de trabajo en /app +WORKDIR /app +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . + + +# Instala las dependencias +RUN pip install -r requirements.txt + +# Copia el archivo node.py al contenedor +COPY main.py . + + +# Ejecuta el programa cuando el contenedor se inicie +ENTRYPOINT [""] + +CMD ["python3", "/app/main.py"] \ No newline at end of file diff --git a/Bots/tts/docker-compose.yml b/Bots/tts/docker-compose.yml new file mode 100644 index 0000000..e2dd972 --- /dev/null +++ b/Bots/tts/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3' + + +services: + glados-tts: + build: . + image: makespacemadrid/glados-tts:latest + hostname: glados-tts + env_file: + - .env + volumes: + - ./data:/data + - ./tts_models:/root/.local/share/tts/ + restart: always + ports: + - 9995:9995 + deploy: + resources: + reservations: + devices: + - driver: nvidia + device_ids: ['0'] +# count: all + capabilities: [gpu] \ No newline at end of file diff --git a/Bots/tts/main.py b/Bots/tts/main.py new file mode 100644 index 0000000..ced2806 --- /dev/null +++ b/Bots/tts/main.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +import os +import platform +import json +#from gladosMQTT import GladosMQTT +from flask import Flask, request, jsonify, send_file +import torch +from TTS.api import TTS +from TTS.utils.synthesizer import Synthesizer +from io import BytesIO +import soundfile as sf +import tempfile + +# Configuración +#mqHost = os.environ.get("MQTT_HOST") +#mqPort = int(os.environ.get("MQTT_PORT")) +#nodeName = platform.node() + +tts_port = os.environ.get("TTS_PORT") +tts_model = os.environ.get("TTS_MODEL") + +# Temas MQTT +#topic_spaceapi = "space/status" +#topic_report_space_open = "space/report_open" + + +# Instancia de GladosMQTT +#glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +#glados_mqtt.set_topics([topic_spaceapi, topic_last_open_status, topic_slack_send_msg_id, topic_slack_send_msg_name,topic_slack_edit_msg]) + + +# Get device +device = "cuda" if torch.cuda.is_available() else "cpu" +# List available 🐸TTS models +print(TTS().list_models()) +# Init TTS +tts = TTS(tts_model).to(device) + +# Carga el modelo y el vocoder +model_path = "/data/glados.pth" # Actualiza esto con la ruta de tu modelo +config_path = "/data/glados_tts.json" # Actualiza esto con la ruta de tu configuración +#synthesizer = Synthesizer(model_path, config_path) + +app = Flask(__name__) + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + global last_open_status + global report_open + + # Manejar mensaje de SpaceAPI + if msg.topic == topic_spaceapi: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + open_status = data['state']['open'] + openSpace(open_status) + except json.JSONDecodeError as e: + glados_mqtt.debug("Error al parsear JSON: " + str(e)) + + + +@app.route('/synthesize', methods=['POST']) +def synthesize(): + data = request.json + text = data.get('text', '') + language = data.get('language', '') + + if not text: + return "No text provided", 400 + + if not language: + language = 'es' + wav = tts.tts(text=text, speaker_wav='/data/glados.wav', language=language) +# wav = synthesizer.tts(text) + # Guarda el archivo de audio temporalmente + with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp: + sf.write(tmp, wav, 22050) # Asumiendo que la frecuencia de muestreo es 22050 Hz + tmp_path = tmp.name # Guarda la ruta del archivo temporal + + # Envía el archivo como respuesta + response = send_file(tmp_path, as_attachment=True, download_name="glados_response.wav", mimetype='audio/wav') + + # Elimina el archivo temporal después de enviarlo + os.remove(tmp_path) + + return response + + +if __name__ == "__main__": +# glados_mqtt.init_mqtt() + app.run(host='0.0.0.0', port=tts_port,debug=False) \ No newline at end of file diff --git a/Bots/tts/requirements.txt b/Bots/tts/requirements.txt new file mode 100644 index 0000000..fd1ea0d --- /dev/null +++ b/Bots/tts/requirements.txt @@ -0,0 +1,3 @@ +paho-mqtt +flask +TTS \ No newline at end of file From 23074116db921f421f8628195799597007985224 Mon Sep 17 00:00:00 2001 From: Javi Date: Mon, 12 Feb 2024 09:31:02 +0100 Subject: [PATCH 15/16] wip --- Bots/BaseMQTTNode/Dockerfile | 5 +- Bots/BaseMQTTNode/gladosMQTT.py | 8 +- Bots/LLM/GladosIA.py | 14 ++-- Bots/LLM/llm.py | 129 ++++++++++---------------------- Bots/Slack/main.py | 7 +- 5 files changed, 61 insertions(+), 102 deletions(-) diff --git a/Bots/BaseMQTTNode/Dockerfile b/Bots/BaseMQTTNode/Dockerfile index 91cdeac..64fb0ca 100644 --- a/Bots/BaseMQTTNode/Dockerfile +++ b/Bots/BaseMQTTNode/Dockerfile @@ -6,13 +6,12 @@ WORKDIR /app # Copia el archivo de requerimientos al contenedor COPY requirements.txt . +# Instala las dependencias +RUN pip install -r requirements.txt # Copia el archivo node.py al contenedor COPY gladosMQTT.py . COPY main.py . -# Instala las dependencias -RUN pip install -r requirements.txt - # Ejecuta el programa cuando el contenedor se inicie CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/BaseMQTTNode/gladosMQTT.py b/Bots/BaseMQTTNode/gladosMQTT.py index 0da6a96..904c6c2 100644 --- a/Bots/BaseMQTTNode/gladosMQTT.py +++ b/Bots/BaseMQTTNode/gladosMQTT.py @@ -8,6 +8,7 @@ def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback self.mqttPort = port self.nodeName = name self.baseTopic = "node/" + self.nodeName + self.statusTopic = self.baseTopic+'/status' self.debugTopic = self.baseTopic + "/debug" self.mqttClient = mqtt.Client() self.topics = [] @@ -16,6 +17,8 @@ def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback self.mqttClient.on_connect = self.on_connect self.mqttClient.on_message = self.on_message self.mqttClient.on_disconnect = self.on_disconnect + self.mqttClient.will_set(self.statusTopic,'OFFLINE', int(2), True) + def set_topics(self, topics): self.topics = topics @@ -24,7 +27,7 @@ def dummy(self, *args, **kwargs): self.debug("Dummy function called") def publish(self, topic, msg, persist=False): - self.mqttClient.publish(topic, msg, persist) + self.mqttClient.publish(topic, msg,int(2), persist) def debug(self, msg): if not isinstance(msg, str): @@ -38,8 +41,9 @@ def debug(self, msg): def on_connect(self, client, userdata, flags, rc): self.debug("[GladosNode] Connected with result code " + str(rc)) + self.mqttClient.publish(self.statusTopic,'ONLINE', int(2)) for topic in self.topics: - self.mqttClient.subscribe(topic) + self.mqttClient.subscribe(topic,int(2)) def on_message(self, client, userdata, msg): # try: diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index e2bd27d..021b59e 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -1,8 +1,11 @@ -import llm +from llm import LLM from UserContext import UserContext # Import UserContext import os import requests + +llm = LLM() + def get_spaceapi_info(url): try: # Realizar una solicitud GET a la URL @@ -71,8 +74,6 @@ def ask(self, prompt, user="default"): extraPrompt += spaceStatus self.user_context[user].set_system_prompt_extra(extraPrompt) - - #Gestion de comandos if prompt.lower() == "reset context": #self.user_context[user].reset_history() @@ -85,8 +86,9 @@ def ask(self, prompt, user="default"): #Gestion de respuestas self.user_context[user].add_to_history("user",prompt) #gladosMQTT.debug(self.user_context[user].get_combined_prompt()) - response = llm.chatCompletion(user_context=self.user_context[user]).choices[0].message.content -# response = llm.chatCompletionLangChain(self.user_context[user],"langchain.txt") + result = llm.chatCompletion(user_context=self.user_context[user]) + if 'error' in result: + return result['error'] + response = result.choices[0].message.content self.user_context[user].add_to_history("assistant",response) - return response \ No newline at end of file diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index ec26f7b..246b7a8 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -2,94 +2,43 @@ import os import json -from langchain.chat_models import ChatOpenAI -from langchain.document_loaders import TextLoader -from langchain.embeddings import OpenAIEmbeddings -from langchain.indexes import VectorstoreIndexCreator -from EmbeddingManager import EmbeddingManager - -openai_api_key = os.environ.get('OPENAI_API_TOKEN') -openai_api_url = os.environ.get('OPENAI_API_ENDPOINT') -custom_api_key = os.environ.get('MKSLLM_API_TOKEN') -custom_api_url = os.environ.get('MKSLLM_API_ENDPOINT') - - -llm_openai = OpenAI(api_key=openai_api_key,base_url=openai_api_url) -llm_mks = OpenAI(api_key=custom_api_key,base_url=custom_api_url) - -#gladosMQTT.debug("---->Creando Embeddings...") -#embedding = OpenAIEmbeddings(model="text-embedding-ada-002") -#loader = TextLoader("langchain.txt") -#index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader]) -#llm_langchain = ChatOpenAI(model="gpt-3.5-turbo") -#gladosMQTT.debug("DONE!") - -current_model = "none" -default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" - - -def list_files_in_directory(directory_path): - """ Lista todos los archivos en un directorio dado. """ - file_paths = [] - for root, dirs, files in os.walk(directory_path): - for file in files: - file_path = os.path.join(root, file) - file_paths.append(file_path) - return file_paths - - - -def select_model(): - model_list = llm_mks.models.list() - global current_model - current_model = model_list.data[0].id - print(model_list.data) - print("Selected model:") - print(current_model) - return model_list - -my_embeddings = EmbeddingManager() -my_embeddings.ingest(list_files_in_directory('/data')) # Rutas a los archivos de texto - -#def chatCompletionLangChain(user_context,FileName): -# chatHistory = user_context.get_combined_prompt() -# -# try: -# response = index.query(json.dumps(chatHistory), llm=llm_langchain) -# search_results = my_embeddings.search(user_context.get_last_prompt()) -# gladosMQTT.debug(json.dumps(search_results, indent=4)) # Imprimir resultados formateados -# response=json.dumps(search_results, indent=4) -# gladosMQTT.debug(f"----->LLM OUTPUT: {response}") -# return response -# except Exception as e: -# gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") -# return None - -def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=512): - global current_model - select_model() - - messages = [] - - if user_context is None: - # Usar prompts individuales - if masterPrompt: - messages.append({"role": "system", "content": masterPrompt}) - if initialAssistant: - messages.append({"role": "assistant", "content": initialAssistant}) - messages.append({"role": "user", "content": prompt}) - else: - # Usar historial -# hist=user_context.get_combined_prompt() -# gladosMQTT.debug(f"hist: {hist}") -# messages.append(hist) - for msg in user_context.get_combined_prompt(): - messages.append(msg) - - try: - response = llm_mks.chat.completions.create(model=current_model, messages=messages, max_tokens=maxTokens) - return response - except Exception as e: - return None - +class LLM: + def __init__(self, default_model="text-davinci-003"): + self.default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" + self.api_key = os.environ.get('LITTLELLM_API_KEY') + self.api_base = os.environ.get('LITTLELLM_API_BASE') + self.default_model = default_model + self.litellm=OpenAI(api_key=self.api_key,base_url=self.api_base) + + def set_default_model(self, model_name): + self.default_model = model_name + + def get_available_models(self): + models = self.litellm.Model.list() + return models + + def chatCompletion(self,prompt="", user_context=None, masterPrompt="", initialAssistant="",model=None, maxTokens=1024): + if model is None: + model = self.default_model + + messages = [] + if user_context is None: + # Usar prompts individuales + if masterPrompt: + messages.append({"role": "system", "content": masterPrompt}) + if initialAssistant: + messages.append({"role": "assistant", "content": initialAssistant}) + messages.append({"role": "user", "content": prompt}) + else: + # Usar historial + for msg in user_context.get_combined_prompt(): + messages.append(msg) + + try: + response = self.litellm.chat.completions.create(model=model, messages=messages, max_tokens=maxTokens) + return response + except Exception as e: + response = {} + response['error'] = str(e) + return response \ No newline at end of file diff --git a/Bots/Slack/main.py b/Bots/Slack/main.py index 48b97ed..6d12d8d 100644 --- a/Bots/Slack/main.py +++ b/Bots/Slack/main.py @@ -211,7 +211,12 @@ def processSlackEvents(event): subtype = "" if 'subtype' in data : subtype = data['subtype'] - if data['type'] != "message" or 'bot_id' in data or 'app_id' in data or subtype == "thread_broadcast" or subtype=="message_changed": + if ( + data['type'] != "message" or + 'bot_id' in data or + 'app_id' in data or + subtype in ("thread_broadcast", "message_changed", "message_deleted") + ): return False payload = getEventInfo(event) From 3e8b63f7854026bd689189207b0bf2e3b3b53297 Mon Sep 17 00:00:00 2001 From: Javi Date: Sat, 13 Jul 2024 18:44:49 +0200 Subject: [PATCH 16/16] yo que se... hay que hacer commit antes que se me pierda el codigo xD --- .gitignore | 2 +- Bots/BaseMQTTNode/gladosMQTT.py | 6 +- Bots/Discord/main.py | 29 +++- Bots/GladosBotBase/Dockerfile | 17 ++ Bots/GladosBotBase/GladosBotBase.py | 159 ++++++++++++++++++ Bots/GladosBotBase/IATools.py | 103 ++++++++++++ Bots/GladosBotBase/docker-compose.yml | 6 + Bots/GladosBotBase/requirements.txt | 6 + Bots/LLM/GladosIA.py | 18 +- Bots/LLM/UserContext.py | 21 +-- Bots/LLM/docker-compose.yml | 4 +- Bots/LLM/llm.old.py | 96 +++++++++++ Bots/LLM/llm.py | 11 +- Bots/LLM/main.py | 72 +++----- Bots/Nvidia/Dockerfile | 22 +++ Bots/Nvidia/docker-compose.yml | 18 ++ Bots/Nvidia/gladosMQTT.py | 72 ++++++++ Bots/Nvidia/main.py | 80 +++++++++ Bots/Nvidia/requirements.txt | 4 + Bots/Ollama-Bridge-LiteLLM/Dockerfile | 15 ++ Bots/Ollama-Bridge-LiteLLM/docker-compose.yml | 8 + Bots/Ollama-Bridge-LiteLLM/main.py | 136 +++++++++++++++ Bots/Ollama-Bridge-LiteLLM/requirements.txt | 3 + Bots/Slack/docker-compose.yml | 2 +- Bots/Slack/main.py | 61 ++++--- Bots/{Telegram => TelegramBot}/Dockerfile | 0 .../docker-compose.yml | 0 Bots/{Telegram => TelegramBot}/main.py | 46 ++++- .../requirements.txt | 0 Bots/spaceAPI/docker-compose.yml | 2 +- Bots/tts/main.py | 23 +-- 31 files changed, 907 insertions(+), 135 deletions(-) create mode 100644 Bots/GladosBotBase/Dockerfile create mode 100644 Bots/GladosBotBase/GladosBotBase.py create mode 100644 Bots/GladosBotBase/IATools.py create mode 100644 Bots/GladosBotBase/docker-compose.yml create mode 100644 Bots/GladosBotBase/requirements.txt create mode 100644 Bots/LLM/llm.old.py create mode 100644 Bots/Nvidia/Dockerfile create mode 100644 Bots/Nvidia/docker-compose.yml create mode 100644 Bots/Nvidia/gladosMQTT.py create mode 100644 Bots/Nvidia/main.py create mode 100644 Bots/Nvidia/requirements.txt create mode 100644 Bots/Ollama-Bridge-LiteLLM/Dockerfile create mode 100644 Bots/Ollama-Bridge-LiteLLM/docker-compose.yml create mode 100644 Bots/Ollama-Bridge-LiteLLM/main.py create mode 100644 Bots/Ollama-Bridge-LiteLLM/requirements.txt rename Bots/{Telegram => TelegramBot}/Dockerfile (100%) rename Bots/{Telegram => TelegramBot}/docker-compose.yml (100%) rename Bots/{Telegram => TelegramBot}/main.py (72%) rename Bots/{Telegram => TelegramBot}/requirements.txt (100%) diff --git a/.gitignore b/.gitignore index 1dd8aab..3637b49 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ status.json .session *.ignore - +**/data/* # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/Bots/BaseMQTTNode/gladosMQTT.py b/Bots/BaseMQTTNode/gladosMQTT.py index 904c6c2..a1a0018 100644 --- a/Bots/BaseMQTTNode/gladosMQTT.py +++ b/Bots/BaseMQTTNode/gladosMQTT.py @@ -2,6 +2,7 @@ import paho.mqtt.client as mqtt import json + class GladosMQTT: def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback=None): self.mqttServer = host @@ -19,6 +20,7 @@ def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback self.mqttClient.on_disconnect = self.on_disconnect self.mqttClient.will_set(self.statusTopic,'OFFLINE', int(2), True) + def set_topics(self, topics): self.topics = topics @@ -26,8 +28,8 @@ def set_topics(self, topics): def dummy(self, *args, **kwargs): self.debug("Dummy function called") - def publish(self, topic, msg, persist=False): - self.mqttClient.publish(topic, msg,int(2), persist) + def publish(self, topic, msg,qos=2,persist=False): + self.mqttClient.publish(topic, msg,qos, persist) def debug(self, msg): if not isinstance(msg, str): diff --git a/Bots/Discord/main.py b/Bots/Discord/main.py index 2da31d7..d03baf9 100644 --- a/Bots/Discord/main.py +++ b/Bots/Discord/main.py @@ -5,6 +5,7 @@ import discord from discord.ext import commands from gladosMQTT import GladosMQTT +import asyncio # Configuración de Discord discord_token = os.environ.get("DISCORD_BOT_TOKEN") @@ -23,11 +24,12 @@ # Temas MQTT topic_discord = "comms/discord" topic_discord_send_msg = topic_discord + "/send_id" +topic_discord_edit_msg = topic_discord + "/edit_msg" topic_discord_event = topic_discord + "/event" # Instancia de GladosMQTT glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) -glados_mqtt.set_topics([topic_discord_send_msg]) +glados_mqtt.set_topics([topic_discord_send_msg,topic_discord_edit_msg]) # Función para manejar mensajes MQTT def on_mqtt_message(client, userdata, msg): @@ -35,18 +37,32 @@ def on_mqtt_message(client, userdata, msg): try: payload = msg.payload.decode('utf-8') data = json.loads(payload) - sendDiscordMsg(data['dest'], data['msg']) + sendDiscordMsg_sync(data['dest'], data['msg']) except json.JSONDecodeError as e: - print("Error al parsear JSON:", e) + glados_mqtt.debug("Error al parsear JSON:", e) # Configurar callbacks MQTT glados_mqtt.mqttClient.on_message = on_mqtt_message + +def sendDiscordMsg_sync(channel_id, msg): + glados_mqtt.debug(f'Enviando a {channel_id} {msg}') + + channel = bot.get_channel(channel_id) + if channel: + asyncio.run_coroutine_threadsafe(channel.send(msg), loop) + else: + glados_mqtt.debug(f'Error, no se ha encontrado el canal {channel_id}') + # Función para enviar mensajes a Discord async def sendDiscordMsg(channel_id, msg): + glados_mqtt.debug(f'Enviando a {channel_id} {msg}') + channel = bot.get_channel(channel_id) if channel: await channel.send(msg) + else: + glados_mqtt.debug(f'Error, no se ha encontrado el canal {channel_id}') # Eventos de Discord @bot.event @@ -58,16 +74,17 @@ async def on_message(message): if message.author == bot.user: return message_data = { - 'content': message.content, + 'content': str(message.content), 'author_id': message.author.id, 'author_name': str(message.author), 'channel_id': message.channel.id, 'channel_name': str(message.channel), 'mentions': str(message.mentions) } - glados_mqtt.publish(topic_discord_event, json.dumps(message_data)) #await message.channel.send('Recibí tu mensaje: ' + message.content) # Respuesta de ejemplo + glados_mqtt.publish(topic_discord_event, json.dumps(message_data)) if __name__ == "__main__": glados_mqtt.init_mqtt() - bot.run(discord_token) + loop = asyncio.get_event_loop() + loop.run_until_complete(bot.run(discord_token)) diff --git a/Bots/GladosBotBase/Dockerfile b/Bots/GladosBotBase/Dockerfile new file mode 100644 index 0000000..be63985 --- /dev/null +++ b/Bots/GladosBotBase/Dockerfile @@ -0,0 +1,17 @@ +# Utiliza una imagen base de Python +FROM makespacemadrid/glados-base-node:1.5 + +# Establece el directorio de trabajo en /app +WORKDIR /app + +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . +# Instala las dependencias +RUN pip install -r requirements.txt + +# Copia el archivo node.py al contenedor +COPY IATools.py . +COPY GladosBotBase.py . + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "main.py"]1 \ No newline at end of file diff --git a/Bots/GladosBotBase/GladosBotBase.py b/Bots/GladosBotBase/GladosBotBase.py new file mode 100644 index 0000000..2767d5e --- /dev/null +++ b/Bots/GladosBotBase/GladosBotBase.py @@ -0,0 +1,159 @@ +import os +import requests +from IATools import * + +class userData: + def __init__(self,channel_id,channel_name=None,user_id=None,user_name=None,channel_type="",user_type="user",audio_response=False): + self.channel_id = channel_id + self.channel_name = channel_name + self.user_id = user_id + self.user_name = user_name + self.channel_type = channel_type + self.user_type = user_type + self.audio_response = False + + def channelID(self) : return self.channel_id + def channelName(self): return self.channel_name + def userID(self) : return self.user_id + def userName(self) : return self.user_name + def channelType(self): return self.channel_type + def userType(self) : return self.user_type + def audioResponse(self): return self.audio_response + +class userContext: + def __init__(self,userdata): + self.userdata = userdata + self.system_prompt = "" + self.system_prompt_extra = "" + self.history = [] + self.lastPrompt = "" + + def userdata(self): return self.userdata + + def add_to_history(self,role,msg): + if agent == "user": + self.lastPrompt = msg + # Crea un diccionario JSON válido para el agente y el mensaje + message_dict = {"role": role, "content": msg} + # Agrega el mensaje al historial + self.history.append(message_dict) + + def add_user_msg(self,msg): + add_to_history("user",msg) + def add_assistant_msg(self,msg): + add_to_history("assistant",msg) + + def set_system_prompt(self,prompt): + self.system_prompt = prompt + def set_system_prompt_extra(self,prompt): + self.system_prompt_extra = prompt + + def get_full_history(self): + return self.history + + def get_combined_history(self): + combined_history = [{"role": "system", "content": self.system_prompt+self.system_prompt_extra}] + combined_history.extend(self.history) + return combined_prompt + + def clear_history(self): + self.history = [] + +class GladosBot: + def __init__(self,iatools): + self.default_prompt="Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" + self.iatools=iatools + self.debug_func=iatools.debug + self.GLaDOS_prompt=os.environ.get('GLADOS_MASTER_PROMPT') + self.initial_assistant_prompt=os.environ.get('GLADOS_INITIAL_PROMPT') + self.default_model=os.environ.get('DEFAULT_MODEL') + self.spaceapi_url=os.environ.get('SPACEAPI_URL') + self.self_channel_id=None + self.user_context = {} + + def debug(self,msg): + self.debug_func(msg) + + def set_self_channel_id(self,channel_id): + self.self_channel_id=channel_id + + def get_user_context(self,channel_id): + for history in self.user_context: + if history.channelID() == channel_id: return history + return None + + def add_new_user(self,userdata): + n_history = userContext(userdata) + n_history.set_system_prompt(self.GLaDOS_prompt) + self.user_context[userdata.channelID()] = n_history + + + def is_channel_id_in_history(self,channel_id): + for history in self.user_context: + if history.userdata.channelID() == channel_id: return True + return False + + def get_spaceapi_info(self,url): + try: + # Realizar una solicitud GET a la URL + response = requests.get(url) + + # Verificar si la solicitud fue exitosa (código de estado 200) + if response.status_code == 200: + # Obtener el contenido JSON de la respuesta + data = response.json() + + # Extraer campos relevantes + space_name = data.get("space", "N/A") + space_url = data.get("url", "N/A") + location = data.get("location", {}) + address = location.get("address", "N/A") + lat = location.get("lat", "N/A") + lon = location.get("lon", "N/A") + contact = data.get("contact", {}) + phone = contact.get("phone", "N/A") + email = contact.get("email", "N/A") + state = data.get("state", {}) + is_open = state.get("open", False) + open_status = "Espacio Abierto" if is_open else "Espacio Cerrado" + sensors = data.get("sensors", {}) + sensor_info = [] + + for sensor_type, sensor_list in sensors.items(): + for sensor in sensor_list: + sensor_name = sensor.get("name", "N/A") + sensor_value = sensor.get("value", "N/A") + sensor_unit = sensor.get("unit", "") + sensor_location = sensor.get("location", "N/A") + sensor_info.append(f"{sensor_name} ({sensor_type}, {sensor_location}): {sensor_value} {sensor_unit}") + + + # Crear una cadena con los campos relevantes y sus valores, incluyendo el estado de apertura/cierre + info_str = "-Informacion que puede ser relevante para las consultas de los usuarios:\n" + info_str += f"**Informacion de contacto de MakeSpace Madrid: \nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\n " + info_str += "**La fecha actual es: " + str(datetime.now().strftime("%d/%m/%Y")) + info_str += f"**Informacion de estado y sensores: \n OpenStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}\n" + info_str += "**Eventos: \n todavia no hay informacion en tiempo real de los eventos. Sin embargo hay los siguientes eventos recurrentes: \n - Martes abiertos a partir de las 19\n - Jueves y Viernes 10 a 17 Impress3D - Fundacion amas\n -Taller de sintetizadores el primer jueves de cada mes." + + return info_str + else: + return "Error: No se pudo obtener el JSON." + + except Exception as e: + return f"Error: {str(e)}" + + def chat(self,message,channel_id): + user_context = self.get_user_context(channel_id) + if not user_context: + return "No se pudo obtener el contexto del usuario" + user_context.add_user_msg(message) + user_context.set_system_prompt_extra(get_spaceapi_info(self.spaceapi_url)) + msgs = user_context.get_combined_history() + result = iatools.chatCompletion(messages=msgs) + if error not in result: + response = result.choices[0].message.content + user_context.add_assistant_msg(response) + return response + else: + return "Error chat" + \ No newline at end of file diff --git a/Bots/GladosBotBase/IATools.py b/Bots/GladosBotBase/IATools.py new file mode 100644 index 0000000..484d9ab --- /dev/null +++ b/Bots/GladosBotBase/IATools.py @@ -0,0 +1,103 @@ +import os +import platform +import json +import requests +from io import BytesIO +from openai import OpenAI +from gladosMQTT import GladosMQTT + +class iatools: + def __init__(self): + self.default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" + self.api_key = os.environ.get('LITTLELLM_API_KEY') + self.api_base = os.environ.get('LITTLELLM_API_BASE') + self.default_model = os.environ.get('DEFAULT_MODEL') + self.tts_url = os.environ.get("TTS_URL") + self.stt_url = os.environ.get("STT_URL") + self.litellm=OpenAI(api_key=self.api_key,base_url=self.api_base) + self.mqtt = GladosMQTT(host=os.environ.get("MQTT_HOST"), port=int(os.environ.get("MQTT_PORT"), name=nodeName, msg_callback=self.mqtt_callback) + self.mqttExternalCallback = None + #DEBUG + def debug(self,msg): + self.mqtt.debug(msg) + + #MQTT + def mqtt_set_topics(self,topics) + self.mqtt.set_topics(topics) + + def mqtt_callback(self,client, userdata, msg): + if mqttExternalCallback: + mqttExternalCallback(client, userdata, msg) + + def publish_mqtt(self,topic,msg,qos=2,persist=False): + self.mqtt.publish(topic, msg,qos=qos, persist=persist) + + def init_mqtt_and_loop_forever(self): + self.mqtt.init_mqtt_and_loop_forever() + def init_mqtt(self): + self.mqtt.init_mqtt() + + #LLM + def set_default_llm_model(self, model_name): + self.default_model = model_name + + def get_available_llm_models(self): + models = self.litellm.Model.list() + return models + + def chatCompletion(self, prompt=None,systemPrompt=None,messages=None, model=None, maxTokens=1024, stream=False, temperature=0.5): + if model is None: + model = self.default_model + if messages is None: + messages = [] + if systemPrompt: + messages.append({"role": "system", "content": systemPrompt}) + messages.append({"role": "user", "content": prompt}) + try: + response = self.litellm.chat.completions.create(model=model, messages=messages, max_tokens=maxTokens, stream=stream, temperature=temperature) + return response + except Exception as e: + response = {} + response['error'] = str(e) + return response + + #IMG2TXT + #TODO.... + + #TXT2IMG + #TODO.... + + #TTS + def tts(self,text,language="es"): + headers = {'Content-Type': 'application/json'} + data = { + "text": text, + "language": language + } + + try: + tts_response = requests.post(self.tts_url, json=data, headers=headers) + tts_response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa + audio_bytes = BytesIO(tts_response.content) + audio_bytes.name = "tts.wav" + return audio_bytes + except requests.RequestException as e: + glados_mqtt.debug(f"Error al generar respuesta de audio: {e}") + + #STT + def stt(self,audio_file_path,language="es"): + params = { + "encode": "true", + "task": "transcribe", + "language": language, + "word_timestamps": "false", + "output": "txt" + } + files = {'audio_file': (audio_file_path, open(audio_file_path, 'rb'), 'audio/wav')} + + try: + response = requests.post(self.stt_url, params=params, files=files) + response.raise_for_status() # Esto provocará una excepción si la respuesta no es exitosa + return response.text # Retorna el contenido de la transcripción + except requests.RequestException as e: + glados_mqtt.debug(f"Error al enviar el archivo de audio a la API: {e}") diff --git a/Bots/GladosBotBase/docker-compose.yml b/Bots/GladosBotBase/docker-compose.yml new file mode 100644 index 0000000..bdf937e --- /dev/null +++ b/Bots/GladosBotBase/docker-compose.yml @@ -0,0 +1,6 @@ +version: '3' +services: + glados_bot-base: + build: . + image: makespacemadrid/glados-bot-base:1.0 + hostname: glados-bot-base diff --git a/Bots/GladosBotBase/requirements.txt b/Bots/GladosBotBase/requirements.txt new file mode 100644 index 0000000..0d220bc --- /dev/null +++ b/Bots/GladosBotBase/requirements.txt @@ -0,0 +1,6 @@ +openai +langchain +chromadb +tiktoken +requests +numpy \ No newline at end of file diff --git a/Bots/LLM/GladosIA.py b/Bots/LLM/GladosIA.py index 021b59e..e6c47a5 100644 --- a/Bots/LLM/GladosIA.py +++ b/Bots/LLM/GladosIA.py @@ -1,5 +1,5 @@ from llm import LLM -from UserContext import UserContext # Import UserContext +from UserContext import UserContext import os import requests @@ -42,7 +42,9 @@ def get_spaceapi_info(url): # Crear una cadena con los campos relevantes y sus valores, incluyendo el estado de apertura/cierre - info_str = f"Reporte actualizado del sistema domotico con el estado del espacio: Space Name: {space_name}\nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\nStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}" + info_str = f"**Informacion de contacto de MakeSpace Madrid: \nSpace URL: {space_url}\nAddress: {address}\nLatitude: {lat}\nLongitude: {lon}\nPhone: {phone}\nEmail: {email}\n " + info_str += f"**Informacion de estado y sensores: \n OpenStatus: {open_status}\nSensors:\n{', '.join(sensor_info)}\n" + info_str += "**Eventos: \n todavia no podemos obtener la informacion en tiempo real de los eventos. Sin embargo hay los siguintes eventos recurrentes: \n - Martes abiertos a partir de las 19\n - Jueves y Viernes 10 a 17 Impress3D - Fundacion amas\n -Taller de sintetizadores el primer jueves de cada mes." return info_str else: return "Error: No se pudo obtener el JSON." @@ -56,7 +58,7 @@ def __init__(self,debug=None): self.GLaDOS_Prompt = os.environ.get('GLADOS_MASTER_PROMPT') self.Initial_Assistant = os.environ.get('GLADOS_INITIAL_PROMPT') self.debug = debug if debug else self.default_debug # Utiliza la función de depuración por defecto si no se proporciona una - + self.default_model=os.environ.get('DEFAULT_MODEL') # Historial de conversaciones, almacenado por usuario self.user_context = {} @@ -68,7 +70,7 @@ def ask(self, prompt, user="default"): if user not in self.user_context: self.user_context[user] = UserContext(self.GLaDOS_Prompt,self.Initial_Assistant) # Create a UserContext for the user self.user_context[user].add_to_history("assistant",self.Initial_Assistant) - extraPrompt = "informacion que puede ser relevante en las consultas posteriores: \n" + extraPrompt = "*Informacion que puede ser relevante en las consultas posteriores: \n" spaceStatus = get_spaceapi_info(os.environ.get('SPACEAPI_URL')) if spaceStatus: extraPrompt += spaceStatus @@ -86,9 +88,13 @@ def ask(self, prompt, user="default"): #Gestion de respuestas self.user_context[user].add_to_history("user",prompt) #gladosMQTT.debug(self.user_context[user].get_combined_prompt()) - result = llm.chatCompletion(user_context=self.user_context[user]) + result = llm.chatCompletion(user_context=self.user_context[user]) if 'error' in result: return result['error'] response = result.choices[0].message.content self.user_context[user].add_to_history("assistant",response) - return response \ No newline at end of file + return response + + + + \ No newline at end of file diff --git a/Bots/LLM/UserContext.py b/Bots/LLM/UserContext.py index cfe5943..412ad38 100644 --- a/Bots/LLM/UserContext.py +++ b/Bots/LLM/UserContext.py @@ -1,12 +1,16 @@ import json +import os class UserContext: - def __init__(self, master_prompt="", initial_assistant=""): + def __init__(self, master_prompt="", initial_assistant="",model_name=None): self.master_prompt = master_prompt self.initial_assistant = initial_assistant self.history = [] self.lastPrompt = "" self.master_prompt_extra = "" + self.model_name=model_name + if model_name is None : + self.model_name=model_name = os.environ.get('DEFAULT_MODEL') def set_system_prompt_extra(self,str): self.master_prompt_extra = str @@ -44,17 +48,14 @@ def get_history(self): def get_combined_prompt(self, max_tokens=None): system_prompt = {"role": "system", "content": self.master_prompt+self.master_prompt_extra} combined_prompt = [system_prompt] # Inicialmente, combinamos el prompt del sistema -# if self.master_prompt_extra: -# message_extra = {"role": "user", "content": self.master_prompt_extra} -# combined_prompt.append(message_extra) - # Concatena los mensajes del historial combined_prompt.extend(self.history) - - # Convierte la lista de mensajes en una cadena JSON válida - #combined_prompt_json = json.dumps(combined_prompt) - return combined_prompt - def clear_history(self): self.history = [] + + def set_model_name(self, model_name: str): + self.model_name = model_name + + def get_model_name(self) -> str: + return self.model_name \ No newline at end of file diff --git a/Bots/LLM/docker-compose.yml b/Bots/LLM/docker-compose.yml index dda9413..09c5ad8 100644 --- a/Bots/LLM/docker-compose.yml +++ b/Bots/LLM/docker-compose.yml @@ -1,6 +1,6 @@ version: '3' services: - mqtt-llm: + glados-llm: build: . image: makespacemadrid/glados-mqtt-llm:latest hostname: mqtt-llm @@ -8,4 +8,4 @@ services: - .env volumes: - ./data:/data - restart: always \ No newline at end of file + restart: alwaysm \ No newline at end of file diff --git a/Bots/LLM/llm.old.py b/Bots/LLM/llm.old.py new file mode 100644 index 0000000..659bde3 --- /dev/null +++ b/Bots/LLM/llm.old.py @@ -0,0 +1,96 @@ +from openai import OpenAI +import os +import json + +from langchain.chat_models import ChatOpenAI +from langchain.document_loaders import TextLoader +from langchain.embeddings import OpenAIEmbeddings +from langchain.indexes import VectorstoreIndexCreator +from EmbeddingManager import EmbeddingManager + + +openai_api_key = os.environ.get('OPENAI_API_TOKEN') +openai_api_url = os.environ.get('OPENAI_API_ENDPOINT') +custom_api_key = os.environ.get('MKSLLM_API_TOKEN') +custom_api_url = os.environ.get('MKSLLM_API_ENDPOINT') + + +llm_openai = OpenAI(api_key=openai_api_key,base_url=openai_api_url) +llm_mks = OpenAI(api_key=custom_api_key,base_url=custom_api_url) + +#gladosMQTT.debug("---->Creando Embeddings...") +#embedding = OpenAIEmbeddings(model="text-embedding-ada-002") +#loader = TextLoader("langchain.txt") +#index = VectorstoreIndexCreator(embedding=embedding).from_loaders([loader]) +#llm_langchain = ChatOpenAI(model="gpt-3.5-turbo") +#gladosMQTT.debug("DONE!") + +current_model = "none" +default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" + + +def list_files_in_directory(directory_path): + """ Lista todos los archivos en un directorio dado. """ + file_paths = [] + for root, dirs, files in os.walk(directory_path): + for file in files: + file_path = os.path.join(root, file) + file_paths.append(file_path) + return file_paths + + + +def select_model(): + model_list = llm_mks.models.list() + global current_model + current_model = model_list.data[0].id + print(model_list.data) + print("Selected model:") + print(current_model) + return model_list + +my_embeddings = EmbeddingManager() +my_embeddings.ingest(list_files_in_directory('/data')) # Rutas a los archivos de texto + +#def chatCompletionLangChain(user_context,FileName): +# chatHistory = user_context.get_combined_prompt() +# +# try: +# response = index.query(json.dumps(chatHistory), llm=llm_langchain) +# search_results = my_embeddings.search(user_context.get_last_prompt()) +# gladosMQTT.debug(json.dumps(search_results, indent=4)) # Imprimir resultados formateados +# response=json.dumps(search_results, indent=4) +# gladosMQTT.debug(f"----->LLM OUTPUT: {response}") +# return response +# except Exception as e: +# gladosMQTT.debug(f"Error in chatCompletion: {str(e)}") +# return None + +def chatCompletion(prompt="", user_context=None, masterPrompt="", initialAssistant="", maxTokens=512): + global current_model + select_model() + + messages = [] + + if user_context is None: + # Usar prompts individuales + if masterPrompt: + messages.append({"role": "system", "content": masterPrompt}) + if initialAssistant: + messages.append({"role": "assistant", "content": initialAssistant}) + messages.append({"role": "user", "content": prompt}) + else: + # Usar historial +# hist=user_context.get_combined_prompt() +# gladosMQTT.debug(f"hist: {hist}") +# messages.append(hist) + for msg in user_context.get_combined_prompt(): + messages.append(msg) + + try: + response = llm_mks.chat.completions.create(model=current_model, messages=messages, max_tokens=maxTokens) + return response + except Exception as e: + return None + + diff --git a/Bots/LLM/llm.py b/Bots/LLM/llm.py index 246b7a8..430b38e 100644 --- a/Bots/LLM/llm.py +++ b/Bots/LLM/llm.py @@ -8,7 +8,7 @@ def __init__(self, default_model="text-davinci-003"): self.default_prompt = "Eres un asistente que ayuda a los usuarios dando respuestas concisas y breves" self.api_key = os.environ.get('LITTLELLM_API_KEY') self.api_base = os.environ.get('LITTLELLM_API_BASE') - self.default_model = default_model + self.default_model = os.environ.get('DEFAULT_MODEL') self.litellm=OpenAI(api_key=self.api_key,base_url=self.api_base) def set_default_model(self, model_name): @@ -18,10 +18,11 @@ def get_available_models(self): models = self.litellm.Model.list() return models - def chatCompletion(self,prompt="", user_context=None, masterPrompt="", initialAssistant="",model=None, maxTokens=1024): + def chatCompletion(self, prompt="", user_context=None, masterPrompt="", initialAssistant="", model=None, maxTokens=1024, stream=False, temperature=0.5): if model is None: model = self.default_model - + if user_context : + model = user_context.get_model_name() messages = [] if user_context is None: # Usar prompts individuales @@ -36,9 +37,9 @@ def chatCompletion(self,prompt="", user_context=None, masterPrompt="", initialAs messages.append(msg) try: - response = self.litellm.chat.completions.create(model=model, messages=messages, max_tokens=maxTokens) + response = self.litellm.chat.completions.create(model=model, messages=messages, max_tokens=maxTokens, stream=stream, temperature=temperature) return response except Exception as e: response = {} response['error'] = str(e) - return response \ No newline at end of file + return response diff --git a/Bots/LLM/main.py b/Bots/LLM/main.py index 48d15a9..f51588f 100644 --- a/Bots/LLM/main.py +++ b/Bots/LLM/main.py @@ -28,6 +28,7 @@ topic_slack_send_msg_id = "comms/slack/send_id" topic_slack_edit_msg = "comms/slack/edit_msg" topic_telegram_send_msg_id ="comms/telegram/send_id" +topic_telegram_edit_msg ="comms/telegram/edit_msg" topic_discord_send_msg_id ="comms/discord/send_id" # Instancia de GladosMQTT @@ -60,12 +61,12 @@ def processSlackMSG(event) : event = json.dumps(event) data = json.loads(event) - sender_id = data.get('sender_id') - channel_id = data.get('channel_id') - message = data.get('message') - message_id = data.get('message_id') - thread_id = data.get('thread_id') - username = data.get('username') + sender_id = data.get('sender_id') + channel_id = data.get('channel_id') + message = data.get('message') + message_id = data.get('message_id') + thread_id = data.get('thread_id') + username = data.get('username') channel_name = data.get('channel_name') reply_msg_id = data.get('reply_msg_id') @@ -81,53 +82,23 @@ def processSlackMSG(event) : # Funciones para procesar eventos de Slack, Telegram y Discord -def processSlackEvent(event): - glados_mqtt.debug("--->SLACK event ------------------") - glados_mqtt.debug(event) - glados_mqtt.debug("/SLACK event ------------------") - try: - data = json.loads(event) - if data['type'] != "message" or 'bot_id' in data: - return False - except: - glados_mqtt.debug("processSlackEvent: Error procesando JSON") - return False - - # Procesar diferentes tipos de eventos de Slack aquí - # Puedes manejar eventos de unión a canales, mensajes en canales, mensajes privados, etc. - - # Ejemplo: Mensajes de unión a canal - if 'subtype' in data and data['subtype'] == "channel_join": - respondTo = data['channel'] - msg = data['text'] - response = gladosBot.ask(msg, respondTo) - sendToSlack(respondTo, response) - - # Ejemplo: Mensaje a canal - elif data['channel_type'] == "channel": - respondTo = data['channel'] - msg = data['text'] - if '<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower(): - response = gladosBot.ask(msg, respondTo) - sendToSlack(respondTo, response) - - # Ejemplo: Mensaje privado - elif data['channel_type'] == "im": - respondTo = data['user'] - msg = data['text'] - response = gladosBot.ask(msg, respondTo) - sendToSlack(respondTo, response) def processTelegramEvent(event): - glados_mqtt.debug("---> TELEGRAM event ------------------") - glados_mqtt.debug(event) - glados_mqtt.debug("/ TELEGRAM event ------------------") # try: data = json.loads(event) respondTo = data['sender_id'] msg = data['message_text'] response = gladosBot.ask(msg, respondTo) - sendToTelegram(respondTo, response) + payload = { + 'sender_id': data['sender_id'], + 'channel_id': data['channel_id'], + 'message_text': response, + 'reply_msg_id': data['reply_msg_id'] + } + + glados_mqtt.publish(topic_telegram_edit_msg, json.dumps(payload)) + +# sendToTelegram(respondTo, response) # except: # glados_mqtt.debug("processTelegramEvent: Error procesando JSON") # return False @@ -139,7 +110,7 @@ def processDiscordEvent(event): glados_mqtt.debug("/ DISCORD event ------------------") # try: data = json.loads(event) - respondTo = data['channel_id'] + respondTo = str(data['channel_id']) msg = data['content'] response = gladosBot.ask(msg, respondTo) sendToDiscord(respondTo, response) @@ -160,9 +131,10 @@ def sendToTelegram(id, msg): # Enviar mensaje a Discord por ID de usuario o canal def sendToDiscord(id, msg): - response = json.dumps({"dest": id, "msg": msg}) - glados_mqtt.debug(f"---> Respuesta a Discord: {response}") - glados_mqtt.publish(topic_discord_send_msg_id, response) + response = json.dumps({"dest": id , "msg": msg}) + glados_mqtt.debug(f'WTF id: {id} response; {response}') + glados_mqtt.debug(f"---> Respuesta a Discord: {response}") + glados_mqtt.publish(topic_discord_send_msg_id, response) if __name__ == "__main__": diff --git a/Bots/Nvidia/Dockerfile b/Bots/Nvidia/Dockerfile new file mode 100644 index 0000000..6e15a4a --- /dev/null +++ b/Bots/Nvidia/Dockerfile @@ -0,0 +1,22 @@ +# Base Image with NVIDIA Runtime +FROM nvidia/cuda:12.3.1-runtime-ubuntu22.04 +RUN apt-get update && apt-get install -y build-essential cmake python3 python3-pip python-is-python3 +ENV LD_LIBRARY_PATH=/usr/local/cuda/lib64:/usr/local/nvidia/lib:/usr/local/nvidia/lib64 +# Utiliza una imagen base que ya tiene el cliente mqtt +#FROM makespacemadrid/glados-base-node:1.5 + + +VOLUME [ "/data" ] +# Establece el directorio de trabajo en /app +WORKDIR /app +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . +RUN pip install -r requirements.txt + +# Copia el archivo node.py al contenedor +COPY main.py . +COPY gladosMQTT.py . +USER 1000 + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "main.py"] diff --git a/Bots/Nvidia/docker-compose.yml b/Bots/Nvidia/docker-compose.yml new file mode 100644 index 0000000..c8395f0 --- /dev/null +++ b/Bots/Nvidia/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3' +services: + glados-nvidia: + build: . + image: makespacemadrid/glados-nvidia:latest + hostname: mqtt-nvidia + env_file: + - .env +# volumes: +# - ./data:/data + restart: always + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] \ No newline at end of file diff --git a/Bots/Nvidia/gladosMQTT.py b/Bots/Nvidia/gladosMQTT.py new file mode 100644 index 0000000..904c6c2 --- /dev/null +++ b/Bots/Nvidia/gladosMQTT.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +import paho.mqtt.client as mqtt +import json + +class GladosMQTT: + def __init__(self, host="192.168.1.1", port=1883, name="test-node", msg_callback=None): + self.mqttServer = host + self.mqttPort = port + self.nodeName = name + self.baseTopic = "node/" + self.nodeName + self.statusTopic = self.baseTopic+'/status' + self.debugTopic = self.baseTopic + "/debug" + self.mqttClient = mqtt.Client() + self.topics = [] + self.nodeMsgCallback = msg_callback if msg_callback is not None else self.dummy + + self.mqttClient.on_connect = self.on_connect + self.mqttClient.on_message = self.on_message + self.mqttClient.on_disconnect = self.on_disconnect + self.mqttClient.will_set(self.statusTopic,'OFFLINE', int(2), True) + + + def set_topics(self, topics): + self.topics = topics + + def dummy(self, *args, **kwargs): + self.debug("Dummy function called") + + def publish(self, topic, msg, persist=False): + self.mqttClient.publish(topic, msg,int(2), persist) + + def debug(self, msg): + if not isinstance(msg, str): + try: + msg = json.dumps(msg) + except (TypeError, ValueError): + msg = str(msg) + + print(msg) + self.publish(self.debugTopic, msg) + + def on_connect(self, client, userdata, flags, rc): + self.debug("[GladosNode] Connected with result code " + str(rc)) + self.mqttClient.publish(self.statusTopic,'ONLINE', int(2)) + for topic in self.topics: + self.mqttClient.subscribe(topic,int(2)) + + def on_message(self, client, userdata, msg): +# try: +# self.debug("[GladosNode] mqtt_rcv: { " + msg.topic + " - " + msg.payload.decode() + " }") +# except Exception as e: +# self.debug("[GladosNode] mqtt_rcv error: " + str(e)) + self.nodeMsgCallback(client, userdata, msg) + + def on_disconnect(self, client, userdata, rc): + self.debug("[GladosNode] Disconnected with result code " + str(rc)) + + def init_mqtt(self): + self.debug("[GladosNode] Connecting : " + str(self.mqttServer) + " Port:" + str(self.mqttPort) + " node: " + str(self.nodeName)) + try: + self.mqttClient.connect(self.mqttServer, port=self.mqttPort, keepalive=120) + except Exception as e: + self.debug("Cannot connect: " + str(e)) + self.mqttClient.loop_start() + + def init_mqtt_and_loop_forever(self): + self.debug("[GladosNode] Connecting : " + str(self.mqttServer) + " Port:" + str(self.mqttPort) + " node: " + str(self.nodeName)) + try: + self.mqttClient.connect(self.mqttServer, port=self.mqttPort, keepalive=120) + except Exception as e: + self.debug("Cannot connect: " + str(e)) + self.mqttClient.loop_forever() diff --git a/Bots/Nvidia/main.py b/Bots/Nvidia/main.py new file mode 100644 index 0000000..2503c3e --- /dev/null +++ b/Bots/Nvidia/main.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +import time +import os +import platform +import subprocess +import json +from io import StringIO # Required for redirecting stdout & stderr +import pandas as pd +from gladosMQTT import GladosMQTT + +# Configuración MQTT +mqHost = os.environ.get("MQTT_HOST") +mqPort = int(os.environ.get("MQTT_PORT")) +nodeName = platform.node() + +# Verificación de variables críticas +if not mqHost or not mqPort: + print("No MQTT config!") + exit(1) + +# Temas MQTT +topic_nvidia_stats ="nvidia/asimov/last_status" +# Instancia de GladosMQTT +glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) +glados_mqtt.set_topics([topic_nvidia_stats]) +last_reading_timestamp = 0 +last_reading_wh_drawn = 0 +total_kwh_drawn = 0 + +def get_nvidia_stats(): + try: + command = 'nvidia-smi --query-gpu=power.draw,temperature.gpu,utilization.gpu,utilization.memory,gpu_name,driver_version,pstate,pcie.link.gen.current --format=csv,nounits' + output = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True) + + # Reading the CSV data and converting it to a pandas DataFrame + data = pd.read_csv(StringIO(output)) + + # Converting the DataFrame to json format compatible with Home Assistant + json_data = json.loads(json.dumps(data.to_dict(orient='records'))) + + return json_data + except Exception as e: + glados_mqtt.debug(str(e)) + +def get_stats(): + global last_reading_timestamp + global last_reading_wh_drawn + global total_kwh_drawn + + now = time.time() + time_delta = now - last_reading_timestamp + kw_spent = float(time_delta/1000/3600)*float(last_reading_wh_drawn) + total_kwh_drawn += kw_spent + stats=get_nvidia_stats() + last_reading_timestamp = now + glados_mqtt.debug(stats) + + +# Función para manejar mensajes MQTT +def on_mqtt_message(client, userdata, msg): + global total_kwh_drawn + payload = msg.payload.decode('utf-8') + if msg.topic == topic_nvidia_stats: + #decode payload to json and extract totalKWh + total_kwh_drawn=float(json.loads(payload)['totalKWh']) + print("Total KWH drawn: " + str(total_kwh_drawn)) + + +# Configurar callbacks MQTT +glados_mqtt.mqttClient.on_message = on_mqtt_message + + +# Inicializar y correr el loop de mensajes MQTT +if __name__ == "__main__": + glados_mqtt.init_mqtt() + while True: + time.sleep(1) + get_stats() + + diff --git a/Bots/Nvidia/requirements.txt b/Bots/Nvidia/requirements.txt new file mode 100644 index 0000000..e845e0b --- /dev/null +++ b/Bots/Nvidia/requirements.txt @@ -0,0 +1,4 @@ +paho-mqtt<2.0.0 +openai +pandas +requests diff --git a/Bots/Ollama-Bridge-LiteLLM/Dockerfile b/Bots/Ollama-Bridge-LiteLLM/Dockerfile new file mode 100644 index 0000000..0af0512 --- /dev/null +++ b/Bots/Ollama-Bridge-LiteLLM/Dockerfile @@ -0,0 +1,15 @@ +# Utiliza una imagen base de Python +FROM python:3.10 + +# Establece el directorio de trabajo en /app +WORKDIR /app + +# Copia el archivo de requerimientos al contenedor +COPY requirements.txt . +# Instala las dependencias +RUN pip install -r requirements.txt + +COPY main.py . + +# Ejecuta el programa cuando el contenedor se inicie +CMD ["python", "main.py"] \ No newline at end of file diff --git a/Bots/Ollama-Bridge-LiteLLM/docker-compose.yml b/Bots/Ollama-Bridge-LiteLLM/docker-compose.yml new file mode 100644 index 0000000..008c90a --- /dev/null +++ b/Bots/Ollama-Bridge-LiteLLM/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' +services: + bridge-ollama-litellm: + build: . + image: makespacemadrid/olm-bridge:1.0 + hostname: bridge-ollama-litellm + env_file: + - .env diff --git a/Bots/Ollama-Bridge-LiteLLM/main.py b/Bots/Ollama-Bridge-LiteLLM/main.py new file mode 100644 index 0000000..7bc17b0 --- /dev/null +++ b/Bots/Ollama-Bridge-LiteLLM/main.py @@ -0,0 +1,136 @@ +import requests +import json +import os + + +ollama_url=os.environ['OLLAMA_URL'] +litellm_url=os.environ['LITELLM_URL'] +litellm_key=os.environ['LITELLM_KEY'] + +def fetch_ollama_models(): +#response example: +#{ +# "models": [ +# { +# "name": "dolphin-mistral:7b", +# "model": "dolphin-mistral:7b", +# "modified_at": "2024-03-17T23:28:34.032Z", +# "size": 4109870615, +# "digest": "ecbf896611f5b38fddc717b2e5609956980dc81187031eccc238a2547378862d", +# "details": { +# "parent_model": "", +# "format": "gguf", +# "family": "llama", +# "families": [ +# "llama" +# ], +# "parameter_size": "7B", +# "quantization_level": "Q4_0" +# } +# } +# ] +#} + + + + + url = f'{ollama_url}/api/tags' + response = requests.get(url) + + if response.status_code == 200: + data = response.json() + for model in data.get('models', []): + print(f"Name: {model['name']}") + print(f"Model: {model['model']}") + print(f"Modified At: {model['modified_at']}") + print(f"Size: {model['size']}") + print(f"Digest: {model['digest']}") + print(f"Details: {model['details']}") + print('-----------------------------') + return data.get('models', []) + else: + print(f"Failed to fetch models. Status code: {response.status_code}") + return None + +def fetch_litellm_models(): +#example output: +#{ +# "data": [ +# { +# "id": "dolphin-mistral:7b", +# "object": "model", +# "created": 1677610602, +# "owned_by": "openai" +# } +# ], +# "object": "list" +#} + url = f'{litellm_url}/v1/models' + headers = { + 'Authorization': 'Bearer sk-1234makespace' + } + + response = requests.get(url, headers=headers) + + if response.status_code == 200: + data = response.json() + for model in data['data']: + print(f"Name: {model['id']}") + return data['data'] + else: + print(f"Failed to fetch OpenAI models. Status code: {response.status_code}") + return None + +def check_chatmode(model_name): + chatmodels = ['vicuna', 'wizard-vicuna'] + no_chatmodels = ['wizardcoder','starcoder','codestral','deepseek','nomic-embed-text'] + for name in chatmodels: + if name in model_name: + return True + for name in no_chatmodels: + if name in model_name: + return False + return True + +def add_model_to_litellm(model_name): + + input_cost_per_token = 0.0001 + output_cost_per_token = 0.0001 + max_tokens = 4096 + + headers = { 'Authorization': f'Bearer {litellm_key}', 'Content-Type': 'application/json' } + response='' + + if '16k' in model_name: + max_tokens = 16000 + + if check_chatmode(model_name): + mode = 'chat' + data = { 'model_name': model_name, 'litellm_params': { 'api_base': ollama_url, 'model': f"ollama_chat/{model_name}", 'input_cost_per_token': input_cost_per_token, 'output_cost_per_token': output_cost_per_token, 'max_tokens': max_tokens }, 'model_info': { 'id': model_name, 'mode': mode, 'input_cost_per_token': input_cost_per_token, 'output_cost_per_token': output_cost_per_token, 'max_tokens': max_tokens } } + response = requests.post(f'{litellm_url}/model/new', headers=headers, json=data) + else: + data = { 'model_name': model_name, 'litellm_params': { 'api_base': ollama_url, 'model': f"ollama/{model_name}", 'input_cost_per_token': input_cost_per_token, 'output_cost_per_token': output_cost_per_token, 'max_tokens': max_tokens }, 'model_info': { 'id': model_name,'input_cost_per_token': input_cost_per_token, 'output_cost_per_token': output_cost_per_token, 'max_tokens': max_tokens } } + response = requests.post(f'{litellm_url}/model/new', headers=headers, json=data) + print(response.json()) + + + + +#get ollama models +ollama_models = fetch_ollama_models() +litellm_models = fetch_litellm_models() + +if ollama_models is None or litellm_models is None: + print("Failed to fetch models") + exit(1) +#make a list with the models that are present in ollama but not in openai +missing_models = [] +for model in ollama_models: + if model['name'] not in [model['id'] for model in litellm_models]: + missing_models.append(model) +print("Missing models name:") + +for model in missing_models: + name=model['name'] + print(name) + add_model_to_litellm(name) diff --git a/Bots/Ollama-Bridge-LiteLLM/requirements.txt b/Bots/Ollama-Bridge-LiteLLM/requirements.txt new file mode 100644 index 0000000..3fa77be --- /dev/null +++ b/Bots/Ollama-Bridge-LiteLLM/requirements.txt @@ -0,0 +1,3 @@ +requests +python-dotenv +#json diff --git a/Bots/Slack/docker-compose.yml b/Bots/Slack/docker-compose.yml index ec95825..a15d49b 100644 --- a/Bots/Slack/docker-compose.yml +++ b/Bots/Slack/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: - mqtt-slack: + glados-slack: build: . image: makespacemadrid/glados-mqtt-slack:latest hostname: mqtt-slack diff --git a/Bots/Slack/main.py b/Bots/Slack/main.py index 6d12d8d..9a4d596 100644 --- a/Bots/Slack/main.py +++ b/Bots/Slack/main.py @@ -15,7 +15,7 @@ slack_port = os.environ.get("SLACK_PORT") # Variables globales para mantener el estado -last_open_status = False +last_open_status = None report_open = True # Verificación de variables críticas @@ -36,7 +36,7 @@ # Instancia de GladosMQTT glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName) -glados_mqtt.set_topics([topic_spaceapi, topic_last_open_status, topic_slack_send_msg_id, topic_slack_send_msg_name,topic_slack_edit_msg]) +glados_mqtt.set_topics([topic_last_open_status,topic_spaceapi, topic_slack_send_msg_id, topic_slack_send_msg_name,topic_slack_edit_msg]) # Cliente de Slack slack_client = WebClient(token=slack_token) @@ -98,23 +98,23 @@ def on_mqtt_message(client, userdata, msg): def openSpace(status): global last_open_status global report_open - - if not report_open: - return + if last_open_status is None : + last_open_status = status == 'false' #Si por alguna razon last_status no esta inicializado cargamos el valor contratio para que si mande el mensaje. if status and not last_open_status: glados_mqtt.debug("Espacio abierto") glados_mqtt.publish(topic_last_open_status, 'true', True) last_open_status = True - sendSlackMsgbyName("abierto-cerrado", "¡Espacio Abierto! Let's Make!") - glados_mqtt.publish(topic_slack_send_msg_name, json.dumps({'dest': "abierto-cerrado", 'msg': "¡Espacio Abierto! Let's Make!"}), True) + if report_open : + sendSlackMsgbyName("abierto-cerrado", "¡Espacio Abierto! Let's Make!") elif not status and last_open_status: glados_mqtt.debug("Espacio cerrado") glados_mqtt.publish(topic_last_open_status, 'false', True) last_open_status = False - sendSlackMsgbyName("abierto-cerrado", "¡Espacio Cerrado! ZZzzZZ") + if report_open : + sendSlackMsgbyName("abierto-cerrado", "¡Espacio Cerrado! ZZzzZZ") + - glados_mqtt.publish(topic_slack_send_msg_name, json.dumps({'dest': "abierto-cerrado", 'msg': "¡Espacio Cerrado! ZZzzZZ"}), True) # Configuración de callbacks MQTT glados_mqtt.mqttClient.on_message = on_mqtt_message @@ -218,6 +218,12 @@ def processSlackEvents(event): subtype in ("thread_broadcast", "message_changed", "message_deleted") ): return False + if data['channel_type'] == "channel": + respondTo = data['channel'] + msg = data['text'] + # Si es un mensaje en un canal pero no se nos menciona no respondemos. + if not ('<@U05LXTJ7Q66>' in msg or 'glados' in msg.lower()): + return payload = getEventInfo(event) # Send a reply that we are 'Working' on it @@ -287,27 +293,32 @@ def publishHomeView(user_id, view): # Rutas Flask @app.route('/slack/events', methods=['POST']) def slack_events(): - data = request.json - # Desafío de URL para la verificación con Slack - if data.get('type') == 'url_verification': - return jsonify({'challenge': data.get('challenge')}) + try: + data = request.json + # Desafío de URL para la verificación con Slack + if data.get('type') == 'url_verification': + return jsonify({'challenge': data.get('challenge')}), 200 # Manejo de eventos de callback - if data.get('type') == 'event_callback': - event = data.get('event', {}) - glados_mqtt.publish(topic_slack_event, json.dumps(event)) # Enviamos el evento a la cola mqtt - processSlackEvents(event) - - if event.get('type') == 'app_home_opened': - user_id = event.get('user') - if user_id: - publishHomeView(user_id) - - return jsonify({'status': 'ok'}), 200 + if data.get('type') == 'event_callback': + event = data.get('event', {}) + glados_mqtt.publish(topic_slack_event, json.dumps(event)) # Enviamos el evento a la cola mqtt + processSlackEvents(event) + + if event.get('type') == 'app_home_opened': + user_id = event.get('user') + if user_id: + publishHomeView(user_id) + + except Exception as e: + glados_mqtt.debug(f'Error procesando el evento de slack: {e}') + finally: + # Usamos el bloque finally para asegurarnos de que siempre ejecutamos el retorno. + return jsonify({'status': 'ok'}), 200 if __name__ == "__main__": # Cargar listas en caché antes de iniciar el MQTT y Flask cache_slack_channels() cache_slack_users() glados_mqtt.init_mqtt() - app.run(host='0.0.0.0', port=slack_port) + app.run(host='0.0.0.0', port=slack_port) \ No newline at end of file diff --git a/Bots/Telegram/Dockerfile b/Bots/TelegramBot/Dockerfile similarity index 100% rename from Bots/Telegram/Dockerfile rename to Bots/TelegramBot/Dockerfile diff --git a/Bots/Telegram/docker-compose.yml b/Bots/TelegramBot/docker-compose.yml similarity index 100% rename from Bots/Telegram/docker-compose.yml rename to Bots/TelegramBot/docker-compose.yml diff --git a/Bots/Telegram/main.py b/Bots/TelegramBot/main.py similarity index 72% rename from Bots/Telegram/main.py rename to Bots/TelegramBot/main.py index 8100bfa..86ca0f1 100644 --- a/Bots/Telegram/main.py +++ b/Bots/TelegramBot/main.py @@ -37,10 +37,10 @@ # Temas MQTT topic_telegram = "comms/telegram" +topic_telegram_edit_msg = topic_telegram + "/edit_msg" topic_telegram_send_msg = topic_telegram + "/send_id" topic_telegram_event = topic_telegram + "/event" - # Definición de los callbacks para MQTT def on_mqtt_message(client, userdata, msg): if msg.topic == topic_telegram_send_msg: @@ -53,10 +53,20 @@ def on_mqtt_message(client, userdata, msg): generate_and_send_audio_response(dest,content) except Exception as e: glados_mqtt.debug(f"Error al enviar mensaje: {e}") - + elif msg.topic == topic_telegram_edit_msg: + try: + payload = msg.payload.decode('utf-8') + data = json.loads(payload) + channel_id = data['channel_id'] + reply_id = data['reply_msg_id'] + content = data['message_text'] + edit_telegram_message_sync(channel_id,reply_id,content) + generate_and_send_audio_response(channel_id,content) + except Exception as e: + glados_mqtt.debug(f"Error al editar mensaje: {e}") def generate_and_send_audio_response(user_id, text): - tts_url = tts_url # Reemplaza con la URL de tu API de Coqui TTS + global tts_url headers = {'Content-Type': 'application/json'} data = { "text": text, @@ -104,23 +114,44 @@ def send_telegram_message_sync(user_id, message): def send_telegram_audio_sync(user_id, audio_bytes): asyncio.run_coroutine_threadsafe(telegram_client.send_file(user_id, audio_bytes, voice_note=True), loop) +def edit_telegram_message_sync(chat_id, message_id, new_text): + asyncio.run_coroutine_threadsafe(telegram_client.edit_message(chat_id,message_id, new_text), loop) + +async def edit_telegram_message(chat_id, message_id, new_text): + await telegram_client.edit_message(chat_id, message_id, new_text) + # Evento para manejar nuevos mensajes en Telegram @telegram_client.on(events.NewMessage) async def handle_new_message(event): glados_mqtt.debug(event.stringify()) + # Enviar respuesta inicial "Procesando..." + response = await telegram_client.send_message(event.chat_id, "Procesando...") + # Intenta obtener el nombre completo del usuario + sender_name = "Desconocido" + if event.sender: + # Si el evento tiene un enviador, construye el nombre completo si es posible + sender_name = event.sender.first_name or "" + if event.sender.last_name: + sender_name += f" {event.sender.last_name}" + # Procesamiento de mensajes de voz o audio if event.message.voice or event.message.audio: # Descargar el archivo de audio audio_file_path = await telegram_client.download_media(event.message, file='/tmp/audio/') + await edit_telegram_message(event.chat_id,response.id, "Whisper...") # Procesar el archivo de audio + #mas adelante pensar en agregar un campo para especificar que es una transcripcion para poder pasarle esa info al LLM transcription = await send_audio_to_transcription_api(audio_file_path) event_data = { 'message_text': transcription, 'sender_id': event.sender_id, - 'chat_id': event.chat_id, + 'channel_id': event.chat_id, + 'reply_msg_id' : response.id, + 'sender_name': sender_name # Agrega aquí otros campos relevantes } + await edit_telegram_message(event.chat_id,response.id, "LLM...") glados_mqtt.publish(topic_telegram_event, json.dumps(event_data)) return @@ -129,16 +160,19 @@ async def handle_new_message(event): event_data = { 'message_text': event.message.message if event.message else None, 'sender_id': event.sender_id, - 'chat_id': event.chat_id, + 'channel_id': event.chat_id, + 'reply_msg_id' : response.id, + 'sender_name': sender_name # Agrega aquí otros campos relevantes } + await edit_telegram_message(event.chat_id,response.id, "LLM...") glados_mqtt.publish(topic_telegram_event, json.dumps(event_data)) # Iniciar la instancia de GladosMQTT glados_mqtt = GladosMQTT(host=mqHost, port=mqPort, name=nodeName, msg_callback=on_mqtt_message) -glados_mqtt.set_topics([topic_telegram_send_msg]) +glados_mqtt.set_topics([topic_telegram_send_msg,topic_telegram_edit_msg]) if __name__ == "__main__": diff --git a/Bots/Telegram/requirements.txt b/Bots/TelegramBot/requirements.txt similarity index 100% rename from Bots/Telegram/requirements.txt rename to Bots/TelegramBot/requirements.txt diff --git a/Bots/spaceAPI/docker-compose.yml b/Bots/spaceAPI/docker-compose.yml index c3c836b..8ef47ee 100644 --- a/Bots/spaceAPI/docker-compose.yml +++ b/Bots/spaceAPI/docker-compose.yml @@ -1,6 +1,6 @@ version: '3' services: - mqtt-spaceapi: + glados-spaceapi: build: . image: makespacemadrid/glados-mqtt-spaceapi:latest hostname: mqtt-spaceapi diff --git a/Bots/tts/main.py b/Bots/tts/main.py index ced2806..f1bd25f 100644 --- a/Bots/tts/main.py +++ b/Bots/tts/main.py @@ -39,27 +39,10 @@ # Carga el modelo y el vocoder model_path = "/data/glados.pth" # Actualiza esto con la ruta de tu modelo config_path = "/data/glados_tts.json" # Actualiza esto con la ruta de tu configuración -#synthesizer = Synthesizer(model_path, config_path) +synthesizer = Synthesizer(model_path, config_path) app = Flask(__name__) -# Función para manejar mensajes MQTT -def on_mqtt_message(client, userdata, msg): - global last_open_status - global report_open - - # Manejar mensaje de SpaceAPI - if msg.topic == topic_spaceapi: - try: - payload = msg.payload.decode('utf-8') - data = json.loads(payload) - open_status = data['state']['open'] - openSpace(open_status) - except json.JSONDecodeError as e: - glados_mqtt.debug("Error al parsear JSON: " + str(e)) - - - @app.route('/synthesize', methods=['POST']) def synthesize(): data = request.json @@ -71,8 +54,8 @@ def synthesize(): if not language: language = 'es' - wav = tts.tts(text=text, speaker_wav='/data/glados.wav', language=language) -# wav = synthesizer.tts(text) +# wav = tts.tts(text=text, speaker_wav='/data/glados.wav', language=language) + wav = synthesizer.tts(text) # Guarda el archivo de audio temporalmente with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp: sf.write(tmp, wav, 22050) # Asumiendo que la frecuencia de muestreo es 22050 Hz