Construye un Agente de IA para WhatsApp con NestJS y Zavu
En este tutorial, construiras un agente de IA para WhatsApp listo para produccion usando NestJS y Zavu. La mejor parte? No necesitas gestionar ninguna API key de IA externa - el AI Gateway de Zavu te da acceso a todos los modelos de IA de primer nivel (GPT-4, Claude, Gemini, Mistral y mas) directamente desde tu dashboard de Zavu.
Lo Que Construiremos
Una aplicacion NestJS que:- Recibe mensajes entrantes de WhatsApp via webhooks
- Verifica firmas de webhook para seguridad
- Usa los agentes de IA administrados de Zavu para respuestas inteligentes
- Aprovecha la inyeccion de dependencias y arquitectura modular de NestJS
- Mantiene automaticamente el contexto de la conversacion
Requisitos Previos
- Node.js 18+
- Una cuenta de Zavu con credenciales de API
- Conocimiento basico de TypeScript
Instalacion
Crea un nuevo proyecto NestJS e instala las dependencias:
Elige tu gestor de paquetes:
Bun (recomendado):npm:bashbun install @zavudev/sdk @nestjs/config dotenv
pnpm:bashnpm install @zavudev/sdk @nestjs/config dotenv
bashpnpm add @zavudev/sdk @nestjs/config dotenv
Luego crea el proyecto:
bashnpx @nestjs/cli new whatsapp-agent cd whatsapp-agent
Estructura del Proyecto
textwhatsapp-agent/ ├── src/ │ ├── app.module.ts │ ├── main.ts │ ├── webhook/ │ │ ├── webhook.module.ts │ │ ├── webhook.controller.ts │ │ ├── webhook.service.ts │ │ └── dto/ │ │ └── webhook-event.dto.ts │ └── zavu/ │ ├── zavu.module.ts │ └── zavu.service.ts ├── .env └── nest-cli.json
Configuracion del Entorno
Crea un archivo .env en la raiz de tu proyecto:
bashZAVUDEV_API_KEY=tu_api_key_de_zavu ZAVU_WEBHOOK_SECRET=tu_secreto_de_webhook PORT=3000
Eso es todo! No necesitas keys de OpenAI, Anthropic u otros proveedores de IA.
Como Funciona el AI Gateway de Zavu
Zavu proporciona un AI Gateway unificado que te da acceso a todos los modelos de IA de primer nivel sin gestionar API keys individuales:
- GPT-4o, GPT-4o-mini - Los modelos mas recientes de OpenAI
- Claude 3.5 Sonnet, Claude 3 Opus - Modelos de Anthropic
- Gemini Pro - Modelos de IA de Google
- Mistral Large - Modelos de Mistral AI
Crear el Modulo de Zavu
Crea src/zavu/zavu.module.ts:
typescriptimport { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { ZavuService } from './zavu.service'; @Module({ imports: [ConfigModule], providers: [ZavuService], exports: [ZavuService], }) export class ZavuModule {}
Crea src/zavu/zavu.service.ts:
typescriptimport { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Zavudev from '@zavudev/sdk'; @Injectable() export class ZavuService { private readonly zavu: Zavu; constructor(private configService: ConfigService) { this.zavu = new Zavu({ apiKey: this.configService.get<string>('ZAVUDEV_API_KEY'), }); } getClient(): Zavu { return this.zavu; } async createAgent(senderId: string, name: string, systemPrompt: string, model = 'gpt-4o-mini') { return await this.zavu.senders.agent.create({ senderId, name, provider: 'zavu', model, systemPrompt, contextWindowMessages: 10, includeContactMetadata: true, enabled: true }); } async createKnowledgeBase(senderId: string, name: string, description?: string) { return await this.zavu.senders.agent.knowledgeBases.create({ senderId, name, description }); } async addDocumentToKB(senderId: string, kbId: string, title: string, content: string) { return await this.zavu.senders.agent.knowledgeBases.documents.create({ senderId, kbId, title, content }); } async createTool(senderId: string, name: string, description: string, webhookUrl: string, parameters: any) { return await this.zavu.senders.agent.tools.create({ senderId, name, description, webhookUrl, parameters }); } }
Crear el Modulo de Webhook
Crea src/webhook/dto/webhook-event.dto.ts:
typescriptexport class WebhookEventDto { type: string; data: any; timestamp?: string; }
Crea src/webhook/webhook.service.ts:
typescriptimport { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as crypto from 'crypto'; @Injectable() export class WebhookService { private readonly logger = new Logger(WebhookService.name); constructor(private configService: ConfigService) {} verifyWebhookSignature(payload: Buffer, signature: string): boolean { const secret = this.configService.get<string>('ZAVU_WEBHOOK_SECRET'); if (!signature || !secret) { return false; } const expected = crypto .createHmac('sha256', secret) .update(payload) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(sha256=${expected}), Buffer.from(signature) ); } async handleWebhookEvent(event: any) { this.logger.log(Evento de webhook recibido: ${event.type}); if (event.type === 'message.inbound') { const message = event.data; this.logger.log(Mensaje de ${message.from}: ${message.text}); } return { status: 'ok' }; } }
Crea src/webhook/webhook.controller.ts:
typescriptimport { Controller, Post, Body, Headers, RawBody, HttpStatus, HttpException } from '@nestjs/common'; import { WebhookService } from './webhook.service'; @Controller('webhook') export class WebhookController { constructor(private readonly webhookService: WebhookService) {} @Post() async handleWebhook( @Body() body: any, @Headers('x-zavu-signature') signature: string, @RawBody() rawBody: Buffer, ) { if (!this.webhookService.verifyWebhookSignature(rawBody, signature)) { throw new HttpException('No autorizado', HttpStatus.UNAUTHORIZED); } try { return await this.webhookService.handleWebhookEvent(body); } catch (error) { throw new HttpException( 'Error interno del servidor', HttpStatus.INTERNAL_SERVER_ERROR ); } } }
Crea src/webhook/webhook.module.ts:
typescriptimport { Module } from '@nestjs/common'; import { WebhookController } from './webhook.controller'; import { WebhookService } from './webhook.service'; import { ZavuModule } from '../zavu/zavu.module'; @Module({ imports: [ZavuModule], controllers: [WebhookController], providers: [WebhookService], }) export class WebhookModule {}
Actualizar el Modulo de la Aplicacion
Actualiza src/app.module.ts:
typescriptimport { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; import { WebhookModule } from './webhook/webhook.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, }), WebhookModule, ], }) export class AppModule {}
Actualizar main.ts
Actualiza src/main.ts:
typescriptimport { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use((req, res, next) => { if (req.path === '/webhook' && req.method === 'POST') { let data = ''; req.setEncoding('utf8'); req.on('data', (chunk) => { data += chunk; }); req.on('end', () => { req.rawBody = Buffer.from(data); next(); }); } else { next(); } }); app.useGlobalPipes(new ValidationPipe()); const PORT = process.env.PORT || 3000; await app.listen(PORT); console.log(Aplicacion corriendo en: http://localhost:${PORT}); } bootstrap();
Desplegar y Configurar
bashnpm run start:prod
Crear un Agente de IA Administrado
Opcion 1: Via Dashboard de Zavu
Crea un agente directamente en el Dashboard de Zavu.
Opcion 2: Via Servicio NestJS
Crea un controlador src/agent/agent.controller.ts:
typescriptimport { Controller, Post, Body } from '@nestjs/common'; import { ZavuService } from '../zavu/zavu.service'; @Controller('agent') export class AgentController { constructor(private readonly zavuService: ZavuService) {} @Post('create') async createAgent(@Body() body: { senderId: string; name: string; systemPrompt: string; model?: string; }) { const agent = await this.zavuService.createAgent( body.senderId, body.name, body.systemPrompt, body.model ); return { agent }; } }
Bases de Conocimiento
typescript// En zavu.service.ts async createKnowledgeBase(senderId: string, name: string, description?: string) { const response = await this.zavu.senders.agent.knowledgeBases.create({ senderId, name, description }); return response.knowledgeBase; } async addDocumentToKB(senderId: string, kbId: string, title: string, content: string) { const response = await this.zavu.senders.agent.knowledgeBases.documents.create({ senderId, kbId, title, content }); return response.document; }
Herramientas Personalizadas
typescript// En zavu.service.ts async createTool( senderId: string, name: string, description: string, webhookUrl: string, parameters: any ) { const response = await this.zavu.senders.agent.tools.create({ senderId, name, description, webhookUrl, parameters }); return response.tool; }
Crea un controlador para el endpoint de la herramienta:
typescriptimport { Controller, Post, Body } from '@nestjs/common'; @Controller('tools') export class ToolsController { @Post('order-status') async getOrderStatus(@Body() body: { order_id: string }) { const orderStatus = await this.getOrderFromDatabase(body.order_id); return { success: true, data: { order_id: body.order_id, status: orderStatus.status, estimated_delivery: orderStatus.delivery_date, tracking_url: orderStatus.tracking_url } }; } private async getOrderFromDatabase(orderId: string) { return { status: 'enviado', delivery_date: '2024-01-20', tracking_url: 'https://tracker.example.com/ORD-12345' }; } }
Usando Tus Propias Credenciales de IA (Opcional)
Si prefieres usar tus propias credenciales de proveedor de IA:
provider: "openai" en lugar de provider: "zavu"Proximos Pasos
- Explora el dashboard de AI Agents para monitorear conversaciones
- Agrega bases de conocimiento para respuestas especificas del dominio
- Crea herramientas personalizadas para integrar con tus sistemas backend