Capítulo 6: Integrando Tools Externos
En los capítulos anteriores hemos construido workflows complejos y sistemas de streaming, pero todo ha funcionado de manera aislada. En el mundo real, los agentes necesitan conectarse con servicios externos: APIs de pago, sistemas de inventario, servicios de mapas, bases de datos, y mucho más.
Imagínate que la Taquería Doña Carmen quiere integrar:
- Pagos con Stripe para procesar tarjetas de crédito
- Google Maps API para calcular rutas de entrega
- WhatsApp Business API para enviar mensajes automáticos
- Sistema de inventario externo para sincronizar stock
- Servicio de calificaciones para recopilar feedback
¡Vamos a construir un sistema híbrido que conecte nuestros workflows con el mundo exterior!
El problema de la integración
Cuando conectas agentes con servicios externos, enfrentas nuevos desafíos:
Agente → API Externa → ¿Autenticación? → ¿Rate Limiting? → ¿Errores de red?
Necesitamos herramientas robustas que manejen:
- Autenticación con diferentes servicios
- Manejo de errores cuando las APIs fallan
- Rate limiting para no exceder límites
- Transformación de datos entre formatos
- Fallbacks cuando servicios no están disponibles
Configuración para integraciones externas
Primero, vamos a configurar las dependencias necesarias:
1# Instalar librerías para integraciones 2npm install axios stripe google-maps-services-js 3npm install twilio # Para WhatsApp Business API 4npm install redis # Para caché y rate limiting 5npm install -D @types/node 6
Variables de entorno
Crea un archivo .env para las credenciales:
1# APIs de pago 2STRIPE_SECRET_KEY=sk_test_... 3STRIPE_PUBLISHABLE_KEY=pk_test_... 4 5# Google Maps 6GOOGLE_MAPS_API_KEY=AIza... 7 8# WhatsApp Business (Twilio) 9TWILIO_ACCOUNT_SID=AC... 10TWILIO_AUTH_TOKEN=... 11TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886 12 13# Base de datos externa 14EXTERNAL_INVENTORY_API_URL=https://api.inventario.com 15EXTERNAL_INVENTORY_API_KEY=inv_... 16 17# Redis para caché 18REDIS_URL=redis://localhost:6379 19
Herramientas para integración con Stripe
Empezemos con la integración de pagos:
1import { tool } from "llamaindex"; 2import Stripe from "stripe"; 3 4// Configurar Stripe 5const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { 6 apiVersion: "2023-10-16", 7}); 8 9// Herramienta para procesar pagos 10const procesarPagoStripe = tool( 11 async ({ 12 monto, 13 moneda, 14 descripcion, 15 metodoPago, 16 }: { 17 monto: number; 18 moneda: string; 19 descripcion: string; 20 metodoPago: string; 21 }) => { 22 try { 23 // Crear Payment Intent 24 const paymentIntent = await stripe.paymentIntents.create({ 25 amount: Math.round(monto * 100), // Stripe usa centavos 26 currency: moneda, 27 description: descripcion, 28 payment_method: metodoPago, 29 confirm: true, 30 return_url: "https://taqueria.com/pago-completado", 31 }); 32 33 return { 34 exito: true, 35 transaccionId: paymentIntent.id, 36 estado: paymentIntent.status, 37 monto: monto, 38 mensaje: `Pago de $${monto} ${moneda} procesado exitosamente`, 39 clientSecret: paymentIntent.client_secret, 40 }; 41 } catch (error: any) { 42 return { 43 exito: false, 44 error: error.message, 45 codigo: error.code, 46 mensaje: `Error al procesar pago: ${error.message}`, 47 }; 48 } 49 }, 50 { 51 name: "procesar_pago_stripe", 52 description: "Procesa pagos con tarjeta de crédito usando Stripe", 53 } 54); 55 56// Herramienta para verificar estado de pago 57const verificarEstadoPago = tool( 58 async ({ transaccionId }: { transaccionId: string }) => { 59 try { 60 const paymentIntent = await stripe.paymentIntents.retrieve(transaccionId); 61 62 return { 63 transaccionId, 64 estado: paymentIntent.status, 65 monto: paymentIntent.amount / 100, 66 moneda: paymentIntent.currency, 67 fechaCreacion: new Date(paymentIntent.created * 1000).toISOString(), 68 mensaje: `Pago ${paymentIntent.status}`, 69 }; 70 } catch (error: any) { 71 return { 72 error: true, 73 mensaje: `No se pudo verificar el pago: ${error.message}`, 74 }; 75 } 76 }, 77 { 78 name: "verificar_estado_pago", 79 description: "Verifica el estado actual de una transacción de Stripe", 80 } 81); 82
Integración con Google Maps API
Ahora vamos a integrar cálculo de rutas y distancias:
1import { Client } from "@googlemaps/google-maps-services-js"; 2 3// Configurar cliente de Google Maps 4const mapsClient = new Client({}); 5 6// Herramienta para calcular ruta de entrega 7const calcularRutaEntrega = tool( 8 async ({ 9 origen, 10 destino, 11 modo, 12 }: { 13 origen: string; 14 destino: string; 15 modo?: "driving" | "walking" | "bicycling"; 16 }) => { 17 try { 18 const response = await mapsClient.directions({ 19 params: { 20 origin: origen, 21 destination: destino, 22 mode: modo || "driving", 23 key: process.env.GOOGLE_MAPS_API_KEY!, 24 language: "es", 25 region: "mx", 26 }, 27 }); 28 29 const ruta = response.data.routes[0]; 30 const tramo = ruta.legs[0]; 31 32 return { 33 exito: true, 34 distancia: tramo.distance.text, 35 duracion: tramo.duration.text, 36 distanciaMetros: tramo.distance.value, 37 duracionSegundos: tramo.duration.value, 38 instrucciones: tramo.steps.map((step) => step.html_instructions), 39 costoEstimado: Math.ceil(tramo.distance.value / 1000) * 5, // $5 por km 40 mensaje: `Ruta calculada: ${tramo.distance.text} en ${tramo.duration.text}`, 41 }; 42 } catch (error: any) { 43 return { 44 exito: false, 45 error: error.message, 46 mensaje: "No se pudo calcular la ruta de entrega", 47 }; 48 } 49 }, 50 { 51 name: "calcular_ruta_entrega", 52 description: 53 "Calcula ruta, distancia y tiempo de entrega usando Google Maps", 54 } 55); 56 57// Herramienta para geocodificar direcciones 58const geocodificarDireccion = tool( 59 async ({ direccion }: { direccion: string }) => { 60 try { 61 const response = await mapsClient.geocode({ 62 params: { 63 address: direccion, 64 key: process.env.GOOGLE_MAPS_API_KEY!, 65 language: "es", 66 region: "mx", 67 }, 68 }); 69 70 const resultado = response.data.results[0]; 71 72 return { 73 exito: true, 74 direccionFormateada: resultado.formatted_address, 75 latitud: resultado.geometry.location.lat, 76 longitud: resultado.geometry.location.lng, 77 tipoUbicacion: resultado.geometry.location_type, 78 componentes: resultado.address_components, 79 mensaje: `Dirección geocodificada exitosamente`, 80 }; 81 } catch (error: any) { 82 return { 83 exito: false, 84 error: error.message, 85 mensaje: "No se pudo geocodificar la dirección", 86 }; 87 } 88 }, 89 { 90 name: "geocodificar_direccion", 91 description: "Convierte una dirección en coordenadas geográficas", 92 } 93); 94
Integración con WhatsApp Business API
Vamos a conectar con WhatsApp para enviar mensajes automáticos:
1import twilio from "twilio"; 2 3// Configurar cliente de Twilio 4const twilioClient = twilio( 5 process.env.TWILIO_ACCOUNT_SID!, 6 process.env.TWILIO_AUTH_TOKEN! 7); 8 9// Herramienta para enviar mensajes de WhatsApp 10const enviarWhatsApp = tool( 11 async ({ 12 numeroDestino, 13 mensaje, 14 tipoMensaje, 15 }: { 16 numeroDestino: string; 17 mensaje: string; 18 tipoMensaje?: "texto" | "plantilla"; 19 }) => { 20 try { 21 // Formatear número para WhatsApp 22 const numeroWhatsApp = `whatsapp:${numeroDestino}`; 23 24 const mensajeEnviado = await twilioClient.messages.create({ 25 from: process.env.TWILIO_WHATSAPP_NUMBER!, 26 to: numeroWhatsApp, 27 body: mensaje, 28 }); 29 30 return { 31 exito: true, 32 mensajeId: mensajeEnviado.sid, 33 estado: mensajeEnviado.status, 34 numeroDestino: numeroDestino, 35 fechaEnvio: mensajeEnviado.dateCreated, 36 mensaje: `Mensaje de WhatsApp enviado exitosamente`, 37 }; 38 } catch (error: any) { 39 return { 40 exito: false, 41 error: error.message, 42 codigo: error.code, 43 mensaje: `Error al enviar WhatsApp: ${error.message}`, 44 }; 45 } 46 }, 47 { 48 name: "enviar_whatsapp", 49 description: "Envía mensajes automáticos por WhatsApp Business API", 50 } 51); 52 53// Herramienta para enviar confirmación de pedido 54const enviarConfirmacionPedido = tool( 55 async ({ 56 numeroCliente, 57 pedidoId, 58 items, 59 total, 60 tiempoEstimado, 61 }: { 62 numeroCliente: string; 63 pedidoId: string; 64 items: string[]; 65 total: number; 66 tiempoEstimado: number; 67 }) => { 68 const mensaje = ` 69🌮 *Taquería Doña Carmen* 70 71¡Hola! Tu pedido ha sido confirmado: 72 73📋 *Pedido:* ${pedidoId} 74🍽️ *Items:* ${items.join(", ")} 75💰 *Total:* $${total} MXN 76⏰ *Tiempo estimado:* ${tiempoEstimado} minutos 77 78Te mantendremos informado del progreso. 79 80¡Gracias por tu preferencia! 🙏 81 `.trim(); 82 83 return await enviarWhatsApp({ 84 numeroDestino: numeroCliente, 85 mensaje, 86 tipoMensaje: "texto", 87 }); 88 }, 89 { 90 name: "enviar_confirmacion_pedido", 91 description: "Envía confirmación automática de pedido por WhatsApp", 92 } 93); 94
Sistema de inventario externo
Ahora vamos a integrar con un sistema de inventario externo:
1import axios from "axios"; 2 3// Cliente para API de inventario externo 4const inventarioAPI = axios.create({ 5 baseURL: process.env.EXTERNAL_INVENTORY_API_URL, 6 headers: { 7 Authorization: `Bearer ${process.env.EXTERNAL_INVENTORY_API_KEY}`, 8 "Content-Type": "application/json", 9 }, 10 timeout: 5000, // 5 segundos timeout 11}); 12 13// Herramienta para sincronizar inventario 14const sincronizarInventario = tool( 15 async ({ productos }: { productos: string[] }) => { 16 try { 17 const response = await inventarioAPI.post("/sync", { 18 productos, 19 tienda: "taqueria-dona-carmen", 20 timestamp: new Date().toISOString(), 21 }); 22 23 return { 24 exito: true, 25 productosActualizados: response.data.updated, 26 stockDisponible: response.data.stock, 27 ultimaActualizacion: response.data.lastUpdate, 28 mensaje: `${response.data.updated.length} productos sincronizados`, 29 }; 30 } catch (error: any) { 31 // Fallback: usar inventario local si la API falla 32 return { 33 exito: false, 34 usandoFallback: true, 35 error: error.message, 36 mensaje: "API de inventario no disponible, usando datos locales", 37 }; 38 } 39 }, 40 { 41 name: "sincronizar_inventario", 42 description: "Sincroniza inventario con sistema externo", 43 } 44); 45 46// Herramienta para verificar stock en tiempo real 47const verificarStockExterno = tool( 48 async ({ productoId }: { productoId: string }) => { 49 try { 50 const response = await inventarioAPI.get(`/stock/${productoId}`); 51 52 return { 53 exito: true, 54 productoId, 55 stockDisponible: response.data.quantity, 56 reservado: response.data.reserved, 57 disponibleVenta: response.data.available, 58 proximoRestock: response.data.nextRestock, 59 mensaje: `Stock verificado: ${response.data.available} unidades disponibles`, 60 }; 61 } catch (error: any) { 62 return { 63 exito: false, 64 error: error.message, 65 mensaje: `No se pudo verificar stock para ${productoId}`, 66 }; 67 } 68 }, 69 { 70 name: "verificar_stock_externo", 71 description: "Verifica stock disponible en sistema externo", 72 } 73); 74
Sistema de rate limiting y caché
Para manejar límites de API y mejorar performance:
1import Redis from "ioredis"; 2 3// Configurar Redis para caché 4const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379"); 5 6// Herramienta para manejar rate limiting 7const verificarRateLimit = tool( 8 async ({ 9 servicio, 10 identificador, 11 limite, 12 ventanaTiempo, 13 }: { 14 servicio: string; 15 identificador: string; 16 limite: number; 17 ventanaTiempo: number; // en segundos 18 }) => { 19 try { 20 const clave = `rate_limit:${servicio}:${identificador}`; 21 const solicitudesActuales = await redis.incr(clave); 22 23 if (solicitudesActuales === 1) { 24 // Primera solicitud, establecer expiración 25 await redis.expire(clave, ventanaTiempo); 26 } 27 28 const permitido = solicitudesActuales <= limite; 29 30 return { 31 permitido, 32 solicitudesActuales, 33 limite, 34 tiempoRestante: await redis.ttl(clave), 35 mensaje: permitido 36 ? `Solicitud permitida (${solicitudesActuales}/${limite})` 37 : `Rate limit excedido (${solicitudesActuales}/${limite})`, 38 }; 39 } catch (error: any) { 40 // Si Redis falla, permitir la solicitud 41 return { 42 permitido: true, 43 error: error.message, 44 mensaje: "Rate limiting no disponible, permitiendo solicitud", 45 }; 46 } 47 }, 48 { 49 name: "verificar_rate_limit", 50 description: 51 "Verifica si una solicitud está dentro de los límites de rate limiting", 52 } 53); 54 55// Herramienta para caché inteligente 56const manejarCache = tool( 57 async ({ 58 clave, 59 accion, 60 valor, 61 ttl, 62 }: { 63 clave: string; 64 accion: "get" | "set" | "delete"; 65 valor?: any; 66 ttl?: number; // tiempo de vida en segundos 67 }) => { 68 try { 69 switch (accion) { 70 case "get": 71 const valorCache = await redis.get(clave); 72 return { 73 encontrado: valorCache !== null, 74 valor: valorCache ? JSON.parse(valorCache) : null, 75 mensaje: valorCache 76 ? "Datos encontrados en caché" 77 : "No hay datos en caché", 78 }; 79 80 case "set": 81 if (valor) { 82 if (ttl) { 83 await redis.setex(clave, ttl, JSON.stringify(valor)); 84 } else { 85 await redis.set(clave, JSON.stringify(valor)); 86 } 87 return { 88 guardado: true, 89 ttl: ttl || "sin expiración", 90 mensaje: "Datos guardados en caché", 91 }; 92 } 93 break; 94 95 case "delete": 96 const eliminados = await redis.del(clave); 97 return { 98 eliminado: eliminados > 0, 99 mensaje: 100 eliminados > 0 101 ? "Datos eliminados del caché" 102 : "Clave no encontrada", 103 }; 104 } 105 106 return { error: "Acción no válida" }; 107 } catch (error: any) { 108 return { 109 error: true, 110 mensaje: `Error en caché: ${error.message}`, 111 }; 112 } 113 }, 114 { 115 name: "manejar_cache", 116 description: "Maneja operaciones de caché para optimizar performance", 117 } 118); 119
Agente híbrido completo
Ahora vamos a crear un agente que integre todos estos servicios externos:
1import { agent } from "llamaindex"; 2 3const agenteHibrido = agent({ 4 tools: [ 5 // Herramientas de pagos 6 procesarPagoStripe, 7 verificarEstadoPago, 8 9 // Herramientas de mapas 10 calcularRutaEntrega, 11 geocodificarDireccion, 12 13 // Herramientas de WhatsApp 14 enviarWhatsApp, 15 enviarConfirmacionPedido, 16 17 // Herramientas de inventario 18 sincronizarInventario, 19 verificarStockExterno, 20 21 // Herramientas de infraestructura 22 verificarRateLimit, 23 manejarCache, 24 ], 25 systemPrompt: ` 26 Eres el agente híbrido de Taquería Doña Carmen que integra múltiples servicios externos. 27 28 Tu trabajo es coordinar entre nuestro sistema interno y servicios externos: 29 30 FLUJO COMPLETO DE PEDIDO CON INTEGRACIONES: 31 32 1. **Recibir pedido** y verificar rate limits 33 2. **Verificar stock** en sistema externo (con fallback a local) 34 3. **Geocodificar dirección** del cliente 35 4. **Calcular ruta** y costo de entrega 36 5. **Procesar pago** con Stripe 37 6. **Enviar confirmación** por WhatsApp 38 7. **Sincronizar inventario** después de la venta 39 8. **Usar caché** para optimizar consultas frecuentes 40 41 MANEJO DE ERRORES: 42 - Si un servicio externo falla, usa fallbacks 43 - Siempre verifica rate limits antes de hacer llamadas 44 - Usa caché para reducir llamadas a APIs 45 - Informa al usuario sobre cualquier limitación 46 47 OPTIMIZACIONES: 48 - Cachea resultados de geocodificación por 24 horas 49 - Cachea stock de inventario por 5 minutos 50 - Respeta rate limits de cada servicio 51 - Usa datos locales como fallback 52 53 IMPORTANTE: 54 - Sé transparente sobre qué servicios estás usando 55 - Explica si hay demoras por servicios externos 56 - Proporciona alternativas si algo falla 57 - Mantén al usuario informado del progreso 58 `, 59}); 60
Ejemplo completo de integración
Vamos a probar el sistema híbrido completo:
1async function demoIntegracionCompleta() { 2 console.log("🔗 DEMO INTEGRACIÓN COMPLETA - SERVICIOS EXTERNOS"); 3 console.log("=".repeat(70)); 4 5 // Escenario 1: Pedido completo con todas las integraciones 6 console.log("\n🌮 Escenario 1: Pedido completo con integraciones externas"); 7 console.log("-".repeat(60)); 8 9 const streamCompleto = await agenteHibrido.runStream({ 10 message: ` 11 Hola, quiero hacer un pedido completo: 12 - 4 tacos de pastor 13 - 2 quesadillas de queso 14 - 1 agua de horchata 15 16 Para entrega a: Av. Insurgentes 123, Col. Roma Norte, CDMX 17 Voy a pagar con tarjeta de crédito 18 Mi WhatsApp es +52 55 1234 5678 19 `, 20 }); 21 22 for await (const chunk of streamCompleto) { 23 process.stdout.write(chunk.delta); 24 await new Promise((resolve) => setTimeout(resolve, 30)); 25 } 26 27 console.log("\n" + "=".repeat(70)); 28 29 // Escenario 2: Manejo de errores de servicios externos 30 console.log("\n⚠️ Escenario 2: Manejo de errores de servicios externos"); 31 console.log("-".repeat(60)); 32 33 const streamErrores = await agenteHibrido.runStream({ 34 message: ` 35 Quiero verificar el stock de tacos de pastor y procesar un pago, 36 pero simula que algunos servicios externos no están disponibles. 37 `, 38 }); 39 40 for await (const chunk of streamErrores) { 41 process.stdout.write(chunk.delta); 42 await new Promise((resolve) => setTimeout(resolve, 30)); 43 } 44 45 console.log("\n" + "=".repeat(70)); 46 47 // Escenario 3: Optimización con caché 48 console.log("\n⚡ Escenario 3: Optimización con caché y rate limiting"); 49 console.log("-".repeat(60)); 50 51 const streamOptimizado = await agenteHibrido.runStream({ 52 message: ` 53 Calcula la ruta para 3 entregas diferentes: 54 1. Av. Reforma 456, Polanco 55 2. Calle Madero 789, Centro Histórico 56 3. Av. Universidad 321, Coyoacán 57 58 Usa caché para optimizar las consultas repetidas. 59 `, 60 }); 61 62 for await (const chunk of streamOptimizado) { 63 process.stdout.write(chunk.delta); 64 await new Promise((resolve) => setTimeout(resolve, 30)); 65 } 66} 67
Resultado esperado
🔗 DEMO INTEGRACIÓN COMPLETA - SERVICIOS EXTERNOS
======================================================================
🌮 Escenario 1: Pedido completo con integraciones externas
------------------------------------------------------------
¡Hola! Voy a procesar tu pedido completo integrando todos nuestros servicios externos:
🔍 **Paso 1: Verificando rate limits**
✅ Rate limit OK - puedo proceder con las integraciones
📦 **Paso 2: Verificando stock en sistema externo**
🔄 Consultando inventario externo...
✅ Stock verificado:
• Tacos de pastor: 15 disponibles
• Quesadillas de queso: 8 disponibles
• Agua de horchata: 12 disponibles
📍 **Paso 3: Geocodificando tu dirección**
🗺️ Procesando "Av. Insurgentes 123, Col. Roma Norte, CDMX"...
✅ Dirección geocodificada:
• Ubicación: Avenida Insurgentes Sur 123, Roma Norte, CDMX
• Coordenadas: 19.4326, -99.1332
🚚 **Paso 4: Calculando ruta de entrega**
🛣️ Calculando desde nuestra taquería...
✅ Ruta calculada:
• Distancia: 3.2 km
• Tiempo estimado: 12 minutos
• Costo de entrega: $25
💰 **Paso 5: Calculando total**
• 4 tacos de pastor: $60
• 2 quesadillas de queso: $50
• 1 agua de horchata: $20
• Entrega: $25
• **TOTAL: $155 MXN**
💳 **Paso 6: Procesando pago con Stripe**
🔄 Procesando pago de $155 MXN...
✅ Pago procesado exitosamente:
• ID de transacción: pi_1234567890
• Estado: succeeded
📱 **Paso 7: Enviando confirmación por WhatsApp**
📲 Enviando a +52 55 1234 5678...
✅ Confirmación enviada por WhatsApp
🔄 **Paso 8: Sincronizando inventario**
📊 Actualizando stock en sistema externo...
✅ Inventario sincronizado
🎉 **¡Pedido completado exitosamente!**
• Pedido ID: PED789012345
• Tiempo estimado de entrega: 35 minutos
• Recibirás actualizaciones por WhatsApp
======================================================================
⚠️ Escenario 2: Manejo de errores de servicios externos
------------------------------------------------------------
Verificando stock y procesando pago...
📦 **Verificando stock externo**
❌ Sistema de inventario externo no disponible
✅ **Fallback activado**: Usando inventario local
• Tacos de pastor: 20 disponibles (datos locales)
💳 **Procesando pago**
❌ Error en Stripe: "Rate limit exceeded"
⏳ **Reintentando en 30 segundos...**
✅ Pago procesado en segundo intento
💡 **Resumen**: Algunos servicios tuvieron problemas temporales, pero tu pedido se procesó correctamente usando nuestros sistemas de respaldo.
======================================================================
⚡ Escenario 3: Optimización con caché y rate limiting
------------------------------------------------------------
Calculando rutas para 3 entregas...
🔍 **Verificando rate limits para Google Maps API**
✅ Rate limit OK (2/100 solicitudes por minuto)
📍 **Ruta 1: Av. Reforma 456, Polanco**
🗺️ Geocodificando...
✅ Dirección procesada y guardada en caché (24h)
🛣️ Ruta: 4.1 km, 18 minutos, $30 entrega
📍 **Ruta 2: Calle Madero 789, Centro Histórico**
🗺️ Geocodificando...
✅ Dirección procesada y guardada en caché (24h)
🛣️ Ruta: 2.8 km, 15 minutos, $20 entrega
📍 **Ruta 3: Av. Universidad 321, Coyoacán**
🗺️ Geocodificando...
✅ Dirección procesada y guardada en caché (24h)
🛣️ Ruta: 6.2 km, 25 minutos, $40 entrega
⚡ **Optimizaciones aplicadas**:
• 3 direcciones guardadas en caché
• Rate limiting respetado (5/100 solicitudes)
• Próximas consultas serán instantáneas
📊 **Resumen de entregas**:
• Total de rutas: 3
• Distancia total: 13.1 km
• Tiempo total estimado: 58 minutos
• Costo total de entregas: $90
¿Qué acabamos de lograr?
- Integración completa con servicios externos reales
- Manejo robusto de errores con fallbacks automáticos
- Optimización inteligente con caché y rate limiting
- Experiencia fluida a pesar de la complejidad técnica
- Sistema híbrido que combina lo mejor de cada servicio
Conceptos clave aprendidos
Arquitectura híbrida
Combinar servicios internos con APIs externas para crear experiencias más ricas.
Manejo de errores resiliente
Siempre tener un plan B cuando los servicios externos fallan.
Optimización de performance
Usar caché y rate limiting para mejorar velocidad y confiabilidad.
Integración transparente
El usuario no necesita saber la complejidad técnica detrás de escena.
Patrones de integración avanzados
1. Circuit Breaker Pattern
1class CircuitBreaker { 2 private failures = 0; 3 private lastFailTime = 0; 4 private state: "closed" | "open" | "half-open" = "closed"; 5 6 async call<T>(fn: () => Promise<T>, fallback: () => T): Promise<T> { 7 if (this.state === "open") { 8 if (Date.now() - this.lastFailTime > 60000) { 9 // 1 minuto 10 this.state = "half-open"; 11 } else { 12 return fallback(); 13 } 14 } 15 16 try { 17 const result = await fn(); 18 this.reset(); 19 return result; 20 } catch (error) { 21 this.recordFailure(); 22 return fallback(); 23 } 24 } 25 26 private recordFailure() { 27 this.failures++; 28 this.lastFailTime = Date.now(); 29 if (this.failures >= 5) { 30 this.state = "open"; 31 } 32 } 33 34 private reset() { 35 this.failures = 0; 36 this.state = "closed"; 37 } 38} 39
2. Retry con backoff exponencial
1const retryConBackoff = tool( 2 async ({ 3 operacion, 4 maxReintentos = 3, 5 delayInicial = 1000, 6 }: { 7 operacion: string; 8 maxReintentos?: number; 9 delayInicial?: number; 10 }) => { 11 for (let intento = 1; intento <= maxReintentos; intento++) { 12 try { 13 // Ejecutar operación 14 const resultado = await ejecutarOperacion(operacion); 15 return { exito: true, resultado, intento }; 16 } catch (error) { 17 if (intento === maxReintentos) { 18 return { exito: false, error, intentos: intento }; 19 } 20 21 // Backoff exponencial: 1s, 2s, 4s, 8s... 22 const delay = delayInicial * Math.pow(2, intento - 1); 23 await new Promise((resolve) => setTimeout(resolve, delay)); 24 } 25 } 26 }, 27 { 28 name: "retry_con_backoff", 29 description: "Reintenta operaciones con backoff exponencial", 30 } 31); 32
3. Agregación de múltiples APIs
1const consultarMultiplesAPIs = tool( 2 async ({ consulta }: { consulta: string }) => { 3 const promesas = [ 4 consultarAPI1(consulta), 5 consultarAPI2(consulta), 6 consultarAPI3(consulta), 7 ]; 8 9 // Usar Promise.allSettled para manejar fallos parciales 10 const resultados = await Promise.allSettled(promesas); 11 12 const exitosos = resultados 13 .filter((r) => r.status === "fulfilled") 14 .map((r) => (r as PromiseFulfilledResult<any>).value); 15 16 const fallidos = resultados 17 .filter((r) => r.status === "rejected") 18 .map((r) => (r as PromiseRejectedResult).reason); 19 20 return { 21 exitosos: exitosos.length, 22 fallidos: fallidos.length, 23 datos: exitosos, 24 errores: fallidos, 25 mensaje: `${exitosos.length}/${resultados.length} APIs respondieron exitosamente`, 26 }; 27 }, 28 { 29 name: "consultar_multiples_apis", 30 description: "Consulta múltiples APIs y agrega los resultados", 31 } 32); 33
Lo que viene
En el siguiente capítulo exploraremos Patrones y Mejores Prácticas, donde aprenderemos:
- Arquitecturas escalables para workflows complejos
- Patrones de diseño específicos para agentes
- Optimización de performance y recursos
- Debugging y monitoreo de sistemas de agentes
- Casos de uso avanzados y soluciones empresariales
¡Ya tienes un sistema híbrido completo funcionando! 🎉
Ejercicio práctico
Expande el sistema de integraciones:
Nivel 1: Básico
- Integrar con más APIs (clima, noticias, redes sociales)
- Implementar webhooks para recibir notificaciones
- Crear dashboard de monitoreo de APIs externas
Nivel 2: Intermedio
- Implementar autenticación OAuth para servicios que lo requieran
- Crear sistema de métricas para monitorear performance de APIs
- Implementar queue system para procesar integraciones asíncronamente
Nivel 3: Avanzado
- Crear API Gateway para centralizar todas las integraciones
- Implementar sistema de eventos distribuido con Apache Kafka
- Crear orquestador de workflows para manejar procesos complejos
¿Te animas a construir un ecosistema completo? En el próximo capítulo veremos cómo optimizar y escalar todo lo que hemos construido.
