este agent quedo funcionando al 100 en local al menos
All checks were successful
build-and-deploy / filter (push) Successful in 2s
build-and-deploy / build (push) Successful in 48s
build-and-deploy / deploy (push) Successful in 27s

This commit is contained in:
2025-06-06 14:56:44 -06:00
parent 0ecb80d45a
commit b751cb2911
17 changed files with 315 additions and 59 deletions

View File

@@ -2,18 +2,18 @@ import { genAI, getMcpTool } from '../llm/gemini';
import { FunctionCallingConfigMode } from '@google/genai';
export async function executeTools(instruction: string): Promise<string> {
console.log('llamando herramienta');
if (!genAI) throw new Error('LLM not configured');
const mcpTool = await getMcpTool();
const executionPrompt = `Vas a ejecutar una sola herramienta del MCP seg\xFAn el plan. Explic\xE1 brevemente la acci\xF3n y devuelve s\xF3lo el resultado.`;
const fullPrompt = `${executionPrompt}\n${instruction}`
console.log('---prompt---', fullPrompt);
const result = await genAI.models.generateContent({
model: 'gemini-pro',
contents: [{ role: 'user', parts: [{ text: `${executionPrompt}\n${instruction}` }] }],
model: 'gemini-2.0-flash',
contents: [{ role: 'user', parts: [{ text: fullPrompt }] }],
config: {
tools: [mcpTool],
toolConfig: {
functionCallingConfig: { mode: FunctionCallingConfigMode.ANY },
},
},
});

View File

@@ -4,30 +4,27 @@ import { systemPrompt } from '../systemPrompt';
import type { Conversation } from '../types';
export async function generatePlan(conversation: Conversation, cognitionPrompt: string): Promise<string> {
console.log('generando plan');
if (!genAI) throw new Error('LLM not configured');
const mcpTool = await getMcpTool();
const planExecutionPrompt = `Est\xE1s generando el plan de acci\xF3n. Solo deb\xE9s consultar el MCP para listar sus capacidades disponibles y luego describir qu\xE9 pasos seguir. Si ya no hay tareas, inclu\xED la frase \"respuesta final\".`;
const planExecutionPrompt = `Est\xE1s generando el plan de acci\xF3n.
Solo deb\xE9s consultar el MCP para listar sus capacidades disponibles
y luego describir qu\xE9 pasos seguir. Si ya no hay tareas, inclu\xED
la frase \"respuesta final\".`;
const context = conversation.messages
.map(m => {
const sender = conversation.participants.find(p => p.id === m.from)?.name || m.from;
const content = m.text || `[${m.type}]`;
return `${sender}: ${content}`;
})
.join('\n');
const prompt = `${systemPrompt}\nConversation:\n${context}\n\nCognition:\n${cognitionPrompt}\n\n${planExecutionPrompt}\nPlan:`;
const prompt = `${systemPrompt}\nConversation:\n${conversation}\n\nCognition:\n${JSON.stringify(conversation)}\n\n${planExecutionPrompt}\nPlan:`;
const result = await genAI.models.generateContent({
model: 'gemini-pro',
contents: [{ role: 'user', parts: [{ text: prompt }] }],
model: 'gemini-2.0-flash',
contents: prompt,
config: {
tools: [mcpTool],
toolConfig: {
functionCallingConfig: { mode: FunctionCallingConfigMode.ANY },
},
},
});
console.log('------plan generado--------');
const text = (result as any).text || '';
return text.trim();
}

View File

@@ -11,21 +11,21 @@ export async function iniciarProcesoCognitivo({ conversation }: CognitionArgs) {
console.log('Iniciando proceso cognitivo...');
let cognitionPrompt = '';
let loopCount = 0;
const plan = await generatePlan(conversation, cognitionPrompt);
while (loopCount < 5) {
const plan = await generatePlan(conversation, cognitionPrompt);
cognitionPrompt += `\n## Plan\n${plan}\n`;
if (/respuesta final/i.test(plan)) {
break;
}
const toolResult = await executeTools(plan);
console.log('resultado de la herramienta ', toolResult);
cognitionPrompt += `\n## Resultado\n${toolResult}\n`;
loopCount += 1;
}
console.log('Proceso cognitivo completado.');
console.log('Proceso cognitivo completado.✅✅✅✅✅');
return { text: cognitionPrompt };
} catch (error) {
console.error('Error al iniciar el proceso cognitivo:', error);

View File

@@ -0,0 +1,29 @@
import { genAI, getMcpTool } from '../llm/gemini';
import { FunctionCallingConfigMode, GenerateContentResponse } from '@google/genai';
import { systemPrompt } from '../systemPrompt';
import type { Conversation } from '../types';
export async function normalResponse(conversation: Conversation): Promise<GenerateContentResponse> {
console.log('generando plan');
if (!genAI) throw new Error('LLM not configured');
const mcpTool = await getMcpTool();
const planExecutionPrompt = `
si necesitas ejecutar mas de una herremienta despues de otra para obtener la respuesta hacelo
`;
const prompt = `${systemPrompt}\nConversation:\n${JSON.stringify(conversation)}\n\nCognition:\n${planExecutionPrompt}`;
const result = await genAI.models.generateContent({
model: 'gemini-2.0-flash',
contents: prompt,
config: {
tools: [mcpTool],
},
});
console.log('------respuesta normal generada--------');
const text = (result as any).text || '';
return result
}

View File

@@ -4,6 +4,7 @@ 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 {normalResponse} from './cognition/normalResponse'
import type { Conversation, Msg, Participant } from './types'; // Import the Conversation type
import dotenv from 'dotenv';
@@ -12,8 +13,6 @@ dotenv.config();
const PORT = Number(process.env.PORT) || 8001;
const API_KEY = process.env.GEMINI_API_KEY || '';
console.log(`Using Gemini API key: ${API_KEY}`);
@@ -44,14 +43,14 @@ app.post('/', async (req, res) => {
return `${sender}: ${content}`;
})
.join('\n');
console.log('primero')
if (!genAI) {
return res.json({ reply: systemPrompt });
return res.json({ reply: "por el momento parece que no tengo acceso a ningun llm ⚠️" });
}
try {
const contents = `${systemPrompt}\nConversation:\n${context}\n`;
const result = await iniciarProcesoCognitivo({ conversation })
const contents = `${systemPrompt}\nConversation:\n${conversation}\n`;
const result = await normalResponse(conversation)
const reply = (result.text || '').trim();
res.json({ reply });
} catch (err: any) {

View File

@@ -1,20 +1,48 @@
import { GoogleGenAI, mcpToTool } from '@google/genai';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import dotenv from 'dotenv';
dotenv.config();
if (process.env.NODE_ENV === 'development') {
console.log('modo desarrollo');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
}else{
console.log('modo produccion');
}
if (
process.env.NODE_ENV !== 'development' &&
process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0'
) {
throw new Error('NODE_TLS_REJECT_UNAUTHORIZED está activado en producción. Abortando.');
}
const API_KEY = process.env.GEMINI_API_KEY || '';
const MCP_URL = process.env.MCP_URL || 'http://planilla.interno.com/mcp';
console.log('segundo')
export const genAI = API_KEY ? new GoogleGenAI({ apiKey: API_KEY }) : null;
console.log(`Using Gemini API key: ${API_KEY}- ${!!genAI}`);
let mcpClient: Client | undefined;
let mcpTransport: StreamableHTTPClientTransport | undefined;
export async function getMcpTool() {
console.log('mcpURL ', MCP_URL);
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);
// console.log('mcpClient ', mcpClient);
const tool = mcpToTool(mcpClient);
console.log('---------------------terminado getMcpTool---------------------------------------------------');
// console.log('tool ', tool);
return tool;
}

View File

@@ -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.
---
`;

View File

@@ -12,29 +12,8 @@ export const systemPrompt = `
- 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.
- tenes la capacidad de llamar de manera secuencial a las herramientas del mcp planilla, eso significa que podes llamar una herramienta, esperar su respuesta y usar esa respuesta para llamar a la siguiente
- la respuesta final siempre la tenes que dar hasta terminar de llamar a todas tus herramientas y obtener un resultado de cada una de ellas para luego usar eso para guiar tu respuesta final
## 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**.