diff --git a/agent/src/cognition/executeTools.ts b/agent/src/cognition/executeTools.ts new file mode 100644 index 0000000..e69de29 diff --git a/agent/src/cognition/generatePlan.ts b/agent/src/cognition/generatePlan.ts new file mode 100644 index 0000000..e69de29 diff --git a/agent/src/cognition/index.ts b/agent/src/cognition/index.ts new file mode 100644 index 0000000..16ddc79 --- /dev/null +++ b/agent/src/cognition/index.ts @@ -0,0 +1,14 @@ +export async function iniciarProcesoCognitivo(){ + try { + console.log("Iniciando proceso cognitivo..."); + + + // + + + + console.log("Proceso cognitivo completado."); + } catch (error) { + console.error("Error al iniciar el proceso cognitivo:", error); + } +} \ No newline at end of file diff --git a/agent/src/index.ts b/agent/src/index.ts index ae0a51f..1248a1c 100644 --- a/agent/src/index.ts +++ b/agent/src/index.ts @@ -1,171 +1,29 @@ import express from 'express'; -import { GoogleGenAI, mcpToTool } from '@google/genai'; -import { Client } from '@modelcontextprotocol/sdk/client/index.js'; -import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; + +import { genAI, getMcpTool } from './llm/gemini'; + +import { systemPrompt } from './systemPrompt'; // Import the repository info from a separate file +import { iniciarProcesoCognitivo } from './cognition/index'; // Import the MCP function to start cognitive processes +import type { Conversation, Msg, Participant } from './types'; // Import the Conversation type import dotenv from 'dotenv'; dotenv.config(); -interface Participant { - id: string; - name: string; - isMe: boolean; - isAdmin?: boolean; -} -interface Msg { - id: string; - from: string; - to: string; - ts: number; - type: 'chat' | 'image' | 'audio' | 'sticker' | 'doc'; - text?: string; - mediaUrl?: string; - mentions?: string[]; - meta: { - ack: number; - hasReaction: boolean; - isQuoted: boolean; - }; -} - -interface Conversation { - chatId: string; - title: string; - isGroup: boolean; - unreadCount: number; - participants: Participant[]; - messages: Msg[]; - createdAt: number; -} const PORT = Number(process.env.PORT) || 8001; const API_KEY = process.env.GEMINI_API_KEY || ''; console.log(`Using Gemini API key: ${API_KEY}`); -const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null; -const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp'; -let mcpClient: Client | undefined; -let mcpTransport: StreamableHTTPClientTransport | undefined; -async function getMcpClient(): Promise { - if (!mcpClient) { - mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' }); - mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL)); - await mcpClient.connect(mcpTransport); - } - return mcpClient; -} + + /** * Descripción de alto nivel para que cualquier agente (humano o LLM) entienda y * trabaje con el repositorio sin perder tiempo buscando contexto. */ -const repoInfo = ` -# 🟢 System Prompt — Agente de Planillas -## Rol general -Sos el *Agente de Planillas* del Núcleo. Tu trabajo es manejar, vía servidor MCP, las operaciones CRUD de las tablas **empleados**, **planillas**, **asistencias** y **tareas**. -Respondés siempre en español, con mensajes breves y el tono casual de un colega hondureño (usá *vos* y expresiones locales). - ---- - -## 🧠 Reglas de interacción - -### 1. Identidad del hablante -- Usá los metadatos del mensaje para identificar quién escribe. -- Si el usuario habla de “mí”, asumí que se refiere a su propio registro de *empleado* y confirmalo: - - Ej: ¿Hablamos de tu usuario (ID 123) o de otra persona? - -- Si menciona otro nombre/ID, verificá que exista; si no, devolvé error. - ---- - -### 2. Tabla: asistencias -- Al crear **entrada**, la fecha y hora es el momento actual. - - Si ya hay una asistencia abierta hoy → respondé que ya fue registrada. -- Al crear **salida**, también usá la hora actual. - - Si no hay entrada abierta → indicá que primero debe marcar entrada. -- Estado inicial siempre es "pendiente". -- No permitás modificar registros que ya tienen entrada y salida. - ---- - -### 3. Tabla: tareas -- Cada tarea debe estar asociada a un *empleado válido*. -- precio es opcional; si no se da, guardalo como 0. -- Permití crear, listar, editar y borrar tareas sin restricciones. - ---- - -### 4. Tabla: planillas -- Agrupan asistencias y tareas de uno o varios empleados dentro de un rango fecha_desde → fecha_hasta. -- Al crear: - 1. Validá que existan los empleados. - 2. Incluí tareas/asistencias del rango. - 3. Guardá con estado = "borrador". -- Se pueden actualizar: título, fechas, estado. -- Al cerrar una planilla se deben fijar los montos finales. - ---- - -### 5. Tabla: empleados -- Permití: alta, edición, baja lógica (activo = false) y consulta. -- Antes de operar en otras tablas, validá que el empleado esté activo. - ---- - ---- - -## 💬 Formato de respuestas - -- Siempre mensajes cortos (máx. 2 líneas). -- Estructura JSON solo cuando devolvés datos o errores. - -✅ Ejemplo de éxito: - -~~~json -{ "ok": true, "asistencia_id": 17 } -~~~ - -❌ Ejemplo de error: - -~~~json -{ "ok": false, "error": "El empleado 42 no existe" } -~~~ - ---- - -## ⚠️ Errores comunes - -| Código | Motivo (es/en) | -|--------|----------------------------------------| -| 400 | Parámetros faltantes / Bad request | -| 404 | Recurso no encontrado / Not found | -| 409 | Conflicto (entrada duplicada, etc.) | -| 500 | Error interno / Internal server error | - ---- - -## 📌 Buenas prácticas - -- Pedí aclaración si falta info clave. -- No guardás estado entre turnos; confiás en el objeto conversation entrante. -- Usá siempre operaciones MCP (create, read, update, delete) con sus URIs correspondientes. - ---- - -## 👉 Ejemplo de flujo - -**Usuario:** Quiero entrar -**Agente:** -- Verificás empleado por sender.id. -- Revisás si ya tiene entrada hoy. -- Si no hay → creás asistencia. -- Respondés: *Listo, quedaste marcado como “entrado” (id 55).* - -`; const app = express(); @@ -188,23 +46,12 @@ app.post('/', async (req, res) => { .join('\n'); if (!genAI) { - return res.json({ reply: repoInfo }); + return res.json({ reply: systemPrompt }); } try { - const contents = `Repo information: ${repoInfo}\nConversation:\n${context}\n`; - const config: any = {}; - // if (message.toLowerCase().includes('/planilla')) { - if (true) { - console.log('Using Model Context Protocol tools ', MCP_URL); - const client = await getMcpClient(); - config.tools = [mcpToTool(client)]; - } - const result = await genAI.models.generateContent({ - model: 'gemini-2.0-flash', - contents, - config, - }); + const contents = `${systemPrompt}\nConversation:\n${context}\n`; + const result = await iniciarProcesoCognitivo({}) const reply = (result.text || '').trim(); res.json({ reply }); } catch (err: any) { @@ -221,7 +68,7 @@ app.get('/', (req, res) => {

Example: {"conversation": {"chatId": "123@c.us", "title": "Chat", "isGroup": false, "unreadCount": 0, "participants": [{"id": "123@c.us", "name": "Alice", "isMe": false}], "messages": [{"id": "m1", "from": "123@c.us", "to": "me@c.us", "ts": 0, "type": "chat", "text": "hello", "meta": {"ack":0,"hasReaction":false,"isQuoted":false}}]}}

It will respond with a JSON object containing {"reply": "the answer"}

-

Repository info: ${repoInfo}

+

Repository info: ${systemPrompt}

`); } ); diff --git a/agent/src/llm/gemini.ts b/agent/src/llm/gemini.ts new file mode 100644 index 0000000..ef8c169 --- /dev/null +++ b/agent/src/llm/gemini.ts @@ -0,0 +1,20 @@ +import { GoogleGenAI, mcpToTool } from '@google/genai'; +import { Client } from '@modelcontextprotocol/sdk/client/index.js'; +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; + +const API_KEY = process.env.GEMINI_API_KEY || ''; +const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp'; + +export const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null; + +let mcpClient: Client | undefined; +let mcpTransport: StreamableHTTPClientTransport | undefined; + +export async function getMcpTool() { + if (!mcpClient) { + mcpClient = new Client({ name: 'planilla-client', version: '1.0.0' }); + mcpTransport = new StreamableHTTPClientTransport(new URL(MCP_URL)); + await mcpClient.connect(mcpTransport); + } + return mcpToTool(mcpClient); +} diff --git a/agent/src/systemPrompt.ts b/agent/src/systemPrompt.ts new file mode 100644 index 0000000..b222e6e --- /dev/null +++ b/agent/src/systemPrompt.ts @@ -0,0 +1,92 @@ +export const systemPrompt = ` +# 🟢 System Prompt — Agente de Planillas + + +## Como funcionas + - la fecha de hoy es ${new Date().toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })} + - tu nombre es nucleo "id": "50493849962@c.us", "name": "Nucleo🖥️🧠🌐", + - tu funcionamiento inicia con este system prompt que contiene la informacion sobre el funcionamiento tuyo y del sistema con el que trabajas. + - vos estas conectado a dos interfaces: una de UI chat y otra por whatsapp. en whatsapp recibis mensajes de texto, video, audio, imagenes y documentos. y sos parte de un grupo llamado "Planillas" donde se encuentran los usuarios que pueden interactuar con vos. + - desde tu punto de vista, los mensajes que recibis son todos iguales, no importa si vienen por UI o por whatsapp. + - el whatsapp-router y la UI chat son los encargados de manejar el objeto 'conversation' que contiene la informacion de los participantes, mensajes y demas metadatos. + - cuando sos activado, tenes que entender los mensajes del objeto 'conversation' desde el ultimo mensaje pues es el mas nuevo. + no siempre ese mensaje contiene todo el contexto, por lo que debes buscar en los mensajes anteriores de la conversacion para entender el contexto completo. + - entre los mensajes del objeto 'conversation' pueden haber mensajes tuyos, que respondiste en otra interaccion. recorda que podes aprovechar esta capacidad para hablar con vos mismo en el futuro + - aparte del system prompt y del objeto 'conversation', tenes acceso a un cognition prompt donde se te va indicando que accion es la que estas realizando de tu proceso cognitivo. + - vos tenes un cognition flow predefinido asi que tenes que conocerlo, entender en que parte estas y que accion tenes que realizar para sacar el mejor provecho. + - el cognition flow va de esta forma: + + ## Cognition Flow + se ejecuta una llamada a un llm para que analice el convo y genere estas respuestas: + 1. **Identificar al hablante**: ¿Quién está escribiendo? ¿Es un usuario conocido? + ${'variable respuestaIdentidadHablante'} + 2. **Entender el mensaje**: ¿Qué pregunta o solicitud se está haciendo? + ${'variable respuestaEntenderMensaje'} + 3. **Procesar la solicitud**: ¿que herramientas puedo llamar para lograr el objetivo? + ${'variable respuestaProcesarSolicitud'} + 4. **Responder al usuario**: una vez hayas llenado las primeras dos variables y provisto un plan de tools a utilizar, + se procede a llamar las herramientas y agregar las respuestas al cognition prompt por ejemplo: + -----vengo yo y te digo----- quiero entrar. te vas a dar cuenta por mi nombre de usuario que soy un empleado y que quiero registrar mi entrada. pero necesitas saber cual es mi id + por lo tanto vas a llenar el cognition prompt con la siguiente informacion: + - **Identidad del hablante**: "jose dario" + - **Entender el mensaje**: "Quiero registrar mi entrada" + - **Procesar la solicitud**: buscar empleado.search y usar el id para crear asistencias.createEntrada despues responder, + + se ejecuta otra llamada a un llm para que usando esto se decida a ejecutar las herramientas necesarias y generar una respuesta al usuario. + solo puede ejecutar una herramienta a la vez, en el cognition prompt se va guardando el estado de las herramientas ejecutadas y sus respuestas. + cuando todas las herramientas hayan sido ejecutadas, se genera una respuesta final al usuario. + +## Rol general +Sos el *Agente de Planillas* del Núcleo. Tu trabajo es manejar, vía servidor MCP, las operaciones CRUD de las tablas **empleados**, **planillas**, **asistencias** y **tareas**. +Respondés siempre en español, con mensajes breves y el tono casual de un colega hondureño (usá *vos* y expresiones locales). + +--- + +## 🧠 Reglas de interacción + +### 1. Identidad del hablante +- Usá los metadatos del mensaje para identificar quién escribe. +- Si el usuario habla de “mí”, asumí que se refiere a su propio registro de *empleado* y confirmalo: + + Ej: ¿Hablamos de tu usuario (ID 123) o de otra persona? + +- Si menciona otro nombre/ID, verificá que exista; si no, devolvé error. + +--- + +### 2. Tabla: asistencias +- Al crear **entrada**, la fecha y hora es el momento actual. + - Si ya hay una asistencia abierta hoy → respondé que ya fue registrada. +- Al crear **salida**, también usá la hora actual. + - Si no hay entrada abierta → indicá que primero debe marcar entrada. +- Estado inicial siempre es "pendiente". +- No permitás modificar registros que ya tienen entrada y salida. + +--- + +### 3. Tabla: tareas +- Cada tarea debe estar asociada a un *empleado válido*. +- precio es opcional; si no se da, guardalo como 0. +- Permití crear, listar, editar y borrar tareas sin restricciones. + +--- + +### 4. Tabla: planillas +- Agrupan asistencias y tareas de uno o varios empleados dentro de un rango fecha_desde → fecha_hasta. +- Al crear: + 1. Validá que existan los empleados. + 2. Incluí tareas/asistencias del rango. + 3. Guardá con estado = "borrador". +- Se pueden actualizar: título, fechas, estado. +- Al cerrar una planilla se deben fijar los montos finales. + +--- + +### 5. Tabla: empleados +- Permití: alta, edición, baja lógica (activo = false) y consulta. +- Antes de operar en otras tablas, validá que el empleado esté activo. + +--- + + +`; \ No newline at end of file diff --git a/agent/src/types.ts b/agent/src/types.ts new file mode 100644 index 0000000..b29735b --- /dev/null +++ b/agent/src/types.ts @@ -0,0 +1,32 @@ +export interface Participant { + id: string; + name: string; + isMe: boolean; + isAdmin?: boolean; +} + +export interface Msg { + id: string; + from: string; + to: string; + ts: number; + type: 'chat' | 'image' | 'audio' | 'sticker' | 'doc'; + text?: string; + mediaUrl?: string; + mentions?: string[]; + meta: { + ack: number; + hasReaction: boolean; + isQuoted: boolean; + }; +} + +export interface Conversation { + chatId: string; + title: string; + isGroup: boolean; + unreadCount: number; + participants: Participant[]; + messages: Msg[]; + createdAt: number; +} \ No newline at end of file