cambiado nombre del directorio
This commit is contained in:
185
agent/src/index.ts
Normal file
185
agent/src/index.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
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 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<Client> {
|
||||
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 = `
|
||||
📦 RESUMEN
|
||||
Este repo orquesta tres servicios complementarios:
|
||||
1. whatsapp-router → Recibe webhooks de OpenWA y re-expide los mensajes al agente o la UI.
|
||||
2. conversation-layer-agent → LLM que responde dudas sobre el código y ejecuta acciones.
|
||||
3. chat-ui → Interfaz web mínima que conversa con el agente.
|
||||
|
||||
🗂 ESTRUCTURA CLAVE
|
||||
│
|
||||
├─ docker-compose.yml # Levanta todo el stack
|
||||
├─ whatsapp-router/
|
||||
│ ├─ src/chatHandlers.ts # Mapeo chatId → handler; ¡editá aquí para nuevos agentes!
|
||||
│ └─ … # Lógica de ruteo y validaciones
|
||||
├─ conversation-layer-agent/
|
||||
│ ├─ src/index.ts # Entrada principal del agente
|
||||
│ └─ prompts/system.ts # Prompt base; importa repoInfo
|
||||
└─ chat-ui/ # Frontend Vite + React (TypeScript)
|
||||
└─ …
|
||||
|
||||
⚙️ VARIABLES DE ENTORNO (ejemplo .env)
|
||||
OPEN_WA_URL=http://openwa:8080
|
||||
LLM_AGENT_URL=http://conversation-layer-agent:8000
|
||||
PORT=3001 # Puerto del router
|
||||
NODE_ENV=development # Cambiá a production para desactivar logs verbosos
|
||||
|
||||
🚀 CÓMO LEVANTAR
|
||||
1) cp .env.example .env && edítalo según tu entorno
|
||||
2) docker-compose up -d --build
|
||||
3) Visita http://localhost:3000 (UI) o revisá logs con \`docker-compose logs -f\`.
|
||||
|
||||
🔄 FLUJO DE MENSAJES
|
||||
OpenWA → whatsapp-router (/webhook) → handler ↔ conversation-layer-agent ↔ chat-ui
|
||||
|
||||
🤖 GUÍA RÁPIDA PARA AGENTES LLM
|
||||
- Responde corto, en el tono del usuario (“vos”, español hondureño).
|
||||
- Usa SOLO la info del objeto \`Conversation\`; no mantengas estado entre turnos.
|
||||
- Si falta contexto, pedilo en una línea.
|
||||
- No repitas instrucciones ni digas que sos IA.
|
||||
- Devuelve \`{ reply: string, actions?: any[] }\` (JSON puro) para facilitar parsing.
|
||||
|
||||
🔧 COMANDOS ÚTILES
|
||||
npm run dev # Hot-reload del router
|
||||
npm test # Ejecuta los tests
|
||||
docker exec -it openwa sh # Shell dentro del contenedor OpenWA
|
||||
git remote -v # Confirma remotos (origin: Gitea, github: mirror)
|
||||
|
||||
🔐 SEGURIDAD
|
||||
- Los tokens/API keys van en variables de entorno; nunca los subas al repo.
|
||||
- Usa certificados válidos o \`NODE_TLS_REJECT_UNAUTHORIZED=0\` SOLO en dev.
|
||||
|
||||
✍️ CONTRIBUCIONES
|
||||
Push a rama feature/* → CI/CD en Gitea valida lint, tests y build.
|
||||
Crea PR para revisión; no mezcles cambios de lógica y formato en el mismo commit.
|
||||
|
||||
📜 LICENCIA
|
||||
GPL-3.0 — libre de usar, modificar y redistribuir mientras mantengas la misma licencia.
|
||||
|
||||
¡Listo! Con esto cualquier agente debería orientarse y empezar a trabajar sin drama.
|
||||
`;
|
||||
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/', async (req, res) => {
|
||||
const conversation = req.body?.conversation as Conversation | undefined;
|
||||
if (!conversation) return res.status(400).json({ error: 'Missing conversation' });
|
||||
const lastMsg = conversation.messages[conversation.messages.length - 1];
|
||||
const message = lastMsg?.text || '';
|
||||
|
||||
const context = conversation.messages
|
||||
.slice(-10)
|
||||
.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');
|
||||
|
||||
if (!genAI) {
|
||||
return res.json({ reply: repoInfo });
|
||||
}
|
||||
|
||||
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 reply = (result.text || '').trim();
|
||||
res.json({ reply });
|
||||
} catch (err: any) {
|
||||
console.error('Gemini error', err.message);
|
||||
res.status(500).json({ error: 'Failed to generate reply' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send(`
|
||||
<h1>Conversation Layer Agent</h1>
|
||||
<p>This service answers questions about the repository.</p>
|
||||
<p>Send a POST request to / with a JSON body containing {"conversation": {...}}</p>
|
||||
<p>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}}]}}</p>
|
||||
<p>It will respond with a JSON object containing {"reply": "the answer"}</p>
|
||||
|
||||
<p>Repository info: ${repoInfo}</p>
|
||||
`);
|
||||
}
|
||||
);
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`conversation-layer-agent listening on ${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user