Inicio

Steps y Eventos

Capítulo 3: Herramientas Avanzadas y Coordinación

En el capítulo anterior creamos un agente simple para una taquería. Ahora vamos a llevarlo al siguiente nivel: herramientas más sofisticadas y coordinación entre múltiples agentes especializados.

Imagínate que la Taquería Doña Carmen ha crecido tanto que ahora necesita:

  • Un agente para pedidos (lo que ya tenemos)
  • Un agente para inventario y proveedores
  • Un agente para atención al cliente y quejas
  • Un agente coordinador que los organice a todos

¡Vamos a construir este sistema completo!

El problema que vamos a resolver

La taquería ahora recibe diferentes tipos de mensajes:

  • Pedidos: "Quiero 3 tacos de pastor"
  • Consultas de inventario: "¿Tienen tacos de barbacoa?"
  • Quejas: "Mi pedido llegó frío"
  • Información general: "¿A qué hora abren?"

Necesitamos un sistema que:

  1. Clasifique automáticamente el tipo de mensaje
  2. Derive al agente especializado correcto
  3. Coordine la respuesta entre múltiples agentes si es necesario
  4. Mantenga contexto de conversaciones largas

Herramientas avanzadas

Primero, vamos a crear herramientas más sofisticadas que pueden manejar datos complejos y tomar decisiones inteligentes.

Herramienta de clasificación inteligente

1import { agent, tool } from "llamaindex"; 2 3// Herramienta que clasifica el tipo de mensaje 4const clasificarMensaje = tool( 5 async ({ mensaje }: { mensaje: string }) => { 6 const texto = mensaje.toLowerCase(); 7 8 // Palabras clave para cada categoría 9 const patrones = { 10 pedido: [ 11 "quiero", 12 "necesito", 13 "me das", 14 "pido", 15 "orden", 16 "llevar", 17 "tacos", 18 "quesadillas", 19 "tortas", 20 "bebidas", 21 ], 22 inventario: [ 23 "tienen", 24 "hay", 25 "disponible", 26 "stock", 27 "existe", 28 "qué tienen", 29 "qué hay", 30 "menú", 31 "carta", 32 ], 33 queja: [ 34 "problema", 35 "queja", 36 "mal", 37 "frío", 38 "tardó", 39 "error", 40 "equivocaron", 41 "reclamo", 42 "devolver", 43 "reembolso", 44 ], 45 informacion: [ 46 "horario", 47 "abren", 48 "cierran", 49 "ubicación", 50 "dirección", 51 "teléfono", 52 "dónde", 53 "cuándo", 54 "cómo llegar", 55 ], 56 saludo: [ 57 "hola", 58 "buenos días", 59 "buenas tardes", 60 "buenas noches", 61 "saludos", 62 "qué tal", 63 ], 64 }; 65 66 // Calcular puntuación para cada categoría 67 const puntuaciones = {}; 68 69 for (const [categoria, palabras] of Object.entries(patrones)) { 70 let puntuacion = 0; 71 for (const palabra of palabras) { 72 if (texto.includes(palabra)) { 73 puntuacion += 1; 74 } 75 } 76 puntuaciones[categoria] = puntuacion; 77 } 78 79 // Encontrar la categoría con mayor puntuación 80 const categoriaDetectada = Object.entries(puntuaciones).sort( 81 ([, a], [, b]) => (b as number) - (a as number) 82 )[0][0]; 83 84 // Si no hay puntuación clara, es consulta general 85 const puntuacionMaxima = Math.max( 86 ...(Object.values(puntuaciones) as number[]) 87 ); 88 const categoria = puntuacionMaxima > 0 ? categoriaDetectada : "informacion"; 89 90 return { 91 categoria, 92 confianza: puntuacionMaxima / 10, // Normalizar a 0-1 93 mensaje: `Mensaje clasificado como: ${categoria} (confianza: ${Math.round( 94 puntuacionMaxima * 10 95 )}%)`, 96 }; 97 }, 98 { 99 name: "clasificar_mensaje", 100 description: 101 "Clasifica un mensaje en categorías: pedido, inventario, queja, informacion, saludo", 102 } 103); 104

Herramienta de gestión de contexto

1// Base de datos simulada de conversaciones 2const conversaciones = new Map< 3 string, 4 Array<{ 5 timestamp: number; 6 tipo: string; 7 mensaje: string; 8 respuesta?: string; 9 }> 10>(); 11 12const gestionarContexto = tool( 13 async ({ 14 clienteId, 15 mensaje, 16 tipo, 17 respuesta, 18 }: { 19 clienteId: string; 20 mensaje: string; 21 tipo: string; 22 respuesta?: string; 23 }) => { 24 // Obtener historial existente 25 const historial = conversaciones.get(clienteId) || []; 26 27 // Agregar nueva interacción 28 const nuevaInteraccion = { 29 timestamp: Date.now(), 30 tipo, 31 mensaje, 32 respuesta, 33 }; 34 35 historial.push(nuevaInteraccion); 36 conversaciones.set(clienteId, historial); 37 38 // Analizar patrones del cliente 39 const tiposRecientes = historial 40 .slice(-5) // Últimas 5 interacciones 41 .map((h) => h.tipo); 42 43 const esClienteFrecuente = historial.length > 3; 44 const tieneQuejasPrevias = historial.some((h) => h.tipo === "queja"); 45 const ultimaInteraccion = historial[historial.length - 2]; // Anterior a la actual 46 47 return { 48 historialLength: historial.length, 49 esClienteFrecuente, 50 tieneQuejasPrevias, 51 tiposRecientes, 52 ultimaInteraccion, 53 contexto: `Cliente ${esClienteFrecuente ? "frecuente" : "nuevo"} con ${ 54 historial.length 55 } interacciones`, 56 }; 57 }, 58 { 59 name: "gestionar_contexto", 60 description: 61 "Gestiona el historial y contexto de conversaciones con clientes", 62 } 63); 64

Herramienta de coordinación entre agentes

1const coordinarAgentes = tool( 2 async ({ 3 categoria, 4 mensaje, 5 contexto, 6 }: { 7 categoria: string; 8 mensaje: string; 9 contexto: any; 10 }) => { 11 const estrategias = { 12 pedido: { 13 agentePrincipal: "pedidos", 14 agentesApoyo: ["inventario"], 15 prioridad: "alta", 16 tiempoRespuesta: "inmediato", 17 }, 18 inventario: { 19 agentePrincipal: "inventario", 20 agentesApoyo: [], 21 prioridad: "media", 22 tiempoRespuesta: "rapido", 23 }, 24 queja: { 25 agentePrincipal: "atencion_cliente", 26 agentesApoyo: ["pedidos", "inventario"], 27 prioridad: "muy_alta", 28 tiempoRespuesta: "inmediato", 29 }, 30 informacion: { 31 agentePrincipal: "informacion", 32 agentesApoyo: [], 33 prioridad: "baja", 34 tiempoRespuesta: "normal", 35 }, 36 saludo: { 37 agentePrincipal: "general", 38 agentesApoyo: [], 39 prioridad: "media", 40 tiempoRespuesta: "rapido", 41 }, 42 }; 43 44 const estrategia = estrategias[categoria] || estrategias.informacion; 45 46 // Ajustar estrategia basada en contexto 47 if (contexto.tieneQuejasPrevias && categoria === "pedido") { 48 estrategia.prioridad = "muy_alta"; 49 estrategia.agentesApoyo.push("atencion_cliente"); 50 } 51 52 if (contexto.esClienteFrecuente) { 53 estrategia.prioridad = 54 estrategia.prioridad === "baja" ? "media" : estrategia.prioridad; 55 } 56 57 return { 58 ...estrategia, 59 instruccionesEspeciales: contexto.tieneQuejasPrevias 60 ? "Tratar con especial cuidado - cliente con quejas previas" 61 : contexto.esClienteFrecuente 62 ? "Cliente frecuente - dar trato preferencial" 63 : "Cliente estándar", 64 }; 65 }, 66 { 67 name: "coordinar_agentes", 68 description: 69 "Determina qué agentes usar y cómo coordinarlos según el tipo de mensaje y contexto", 70 } 71); 72

Agentes especializados

Ahora vamos a crear agentes especializados para cada tipo de tarea:

Agente de Pedidos (mejorado)

1// Reutilizamos las herramientas del capítulo anterior 2import { 3 procesarPedido, 4 verificarInventario, 5 calcularPrecio, 6 actualizarInventario, 7} from "./capitulo-02"; 8 9const agentePedidos = agent({ 10 tools: [ 11 procesarPedido, 12 verificarInventario, 13 calcularPrecio, 14 actualizarInventario, 15 ], 16 systemPrompt: ` 17 Eres el especialista en pedidos de Taquería Doña Carmen. 18 19 Tu única responsabilidad es procesar pedidos de comida de manera eficiente: 20 21 1. Procesa el pedido usando las herramientas disponibles 22 2. Verifica inventario y ofrece alternativas si algo no está disponible 23 3. Calcula precios con descuentos aplicables 24 4. Presenta un resumen claro y atractivo 25 5. Confirma antes de actualizar inventario 26 27 IMPORTANTE: 28 - Sé eficiente pero amigable 29 - Siempre ofrece alternativas si algo no está disponible 30 - Menciona promociones cuando apliquen 31 - Confirma el pedido antes de procesar 32 33 Si el cliente pregunta algo que no sea sobre pedidos, responde: 34 "Para esa consulta, déjame conectarte con mi compañero especialista" 35 `, 36}); 37

Agente de Inventario

1const consultarInventarioDetallado = tool( 2 async ({ consulta }: { consulta: string }) => { 3 const texto = consulta.toLowerCase(); 4 5 // Información detallada del menú 6 const menuCompleto = { 7 tacos: { 8 disponibles: ["pastor", "carnitas", "suadero", "chorizo"], 9 noDisponibles: ["barbacoa", "cochinita"], 10 precios: { pastor: 15, carnitas: 15, suadero: 16, chorizo: 14 }, 11 }, 12 quesadillas: { 13 disponibles: ["queso", "flor de calabaza"], 14 noDisponibles: ["huitlacoche", "champiñones"], 15 precios: { queso: 25, "flor de calabaza": 30 }, 16 }, 17 bebidas: { 18 disponibles: ["horchata", "jamaica", "coca cola"], 19 noDisponibles: ["tamarindo", "limón"], 20 precios: { horchata: 20, jamaica: 18, "coca cola": 25 }, 21 }, 22 }; 23 24 // Buscar en el menú 25 let resultados = []; 26 27 for (const [categoria, info] of Object.entries(menuCompleto)) { 28 if (texto.includes(categoria.slice(0, -1))) { 29 // quitar 's' final 30 resultados.push({ 31 categoria, 32 disponibles: info.disponibles, 33 noDisponibles: info.noDisponibles, 34 precios: info.precios, 35 }); 36 } else { 37 // Buscar productos específicos 38 for (const producto of [...info.disponibles, ...info.noDisponibles]) { 39 if (texto.includes(producto)) { 40 resultados.push({ 41 producto, 42 disponible: info.disponibles.includes(producto), 43 precio: info.precios[producto] || "No disponible", 44 categoria, 45 }); 46 } 47 } 48 } 49 } 50 51 return { 52 resultados, 53 mensaje: 54 resultados.length > 0 55 ? `Encontré información sobre ${resultados.length} productos` 56 : "No encontré información específica, pero puedo ayudarte con nuestro menú completo", 57 }; 58 }, 59 { 60 name: "consultar_inventario_detallado", 61 description: 62 "Consulta información detallada sobre disponibilidad, precios y productos del menú", 63 } 64); 65 66const agenteInventario = agent({ 67 tools: [consultarInventarioDetallado], 68 systemPrompt: ` 69 Eres el especialista en inventario y menú de Taquería Doña Carmen. 70 71 Tu responsabilidad es proporcionar información precisa sobre: 72 - Qué productos tenemos disponibles 73 - Precios actuales 74 - Alternativas cuando algo no esté disponible 75 - Recomendaciones basadas en disponibilidad 76 77 IMPORTANTE: 78 - Sé específico con precios y disponibilidad 79 - Siempre sugiere alternativas si algo no está disponible 80 - Menciona productos populares o recomendados 81 - Si te preguntan sobre pedidos, deriva al especialista en pedidos 82 83 Ejemplo de respuesta: 84 "Tenemos tacos de pastor ($15), carnitas ($15) y suadero ($16) disponibles. 85 Lamentablemente no tenemos barbacoa hoy, pero te recomiendo el suadero que está delicioso." 86 `, 87}); 88

Agente de Atención al Cliente

1const gestionarQueja = tool( 2 async ({ 3 tipoQueja, 4 descripcion, 5 clienteId, 6 }: { 7 tipoQueja: string; 8 descripcion: string; 9 clienteId: string; 10 }) => { 11 const solucionesPorTipo = { 12 comida_fria: { 13 solucion: "Reemplazo gratuito + bebida de cortesía", 14 compensacion: "Descuento 20% próximo pedido", 15 tiempoResolucion: "15 minutos", 16 }, 17 pedido_incorrecto: { 18 solucion: "Corrección inmediata del pedido", 19 compensacion: "Descuento 15% próximo pedido", 20 tiempoResolucion: "10 minutos", 21 }, 22 demora_entrega: { 23 solucion: "Descuento en pedido actual", 24 compensacion: "Entrega gratuita próximo pedido", 25 tiempoResolucion: "Inmediato", 26 }, 27 mala_atencion: { 28 solucion: "Disculpa personal del gerente", 29 compensacion: "Combo gratuito próxima visita", 30 tiempoResolucion: "5 minutos", 31 }, 32 }; 33 34 const solucion = solucionesPorTipo[tipoQueja] || { 35 solucion: "Revisión personalizada del caso", 36 compensacion: "Compensación a determinar", 37 tiempoResolucion: "24 horas", 38 }; 39 40 // Registrar la queja 41 const ticketId = `Q${Date.now()}`; 42 43 return { 44 ticketId, 45 ...solucion, 46 mensaje: `Queja registrada con ID: ${ticketId}. Procederemos con: ${solucion.solucion}`, 47 }; 48 }, 49 { 50 name: "gestionar_queja", 51 description: "Gestiona quejas de clientes y propone soluciones apropiadas", 52 } 53); 54 55const agenteAtencionCliente = agent({ 56 tools: [gestionarQueja], 57 systemPrompt: ` 58 Eres el especialista en atención al cliente de Taquería Doña Carmen. 59 60 Tu misión es resolver problemas y mantener clientes satisfechos: 61 62 1. Escucha activamente la queja o problema 63 2. Muestra empatía y comprensión 64 3. Usa la herramienta para gestionar la queja apropiadamente 65 4. Ofrece soluciones concretas e inmediatas 66 5. Asegúrate de que el cliente se sienta valorado 67 68 IMPORTANTE: 69 - Siempre pide disculpas primero, sin importar la situación 70 - Sé empático: "Entiendo tu frustración..." 71 - Ofrece soluciones concretas, no solo palabras 72 - Haz seguimiento: "¿Esto resuelve tu problema?" 73 - Convierte la experiencia negativa en positiva 74 75 Ejemplo de respuesta: 76 "Lamento mucho que tu pedido haya llegado frío. Eso no es la experiencia que queremos darte. 77 Voy a enviarte un reemplazo caliente inmediatamente y una bebida de cortesía. 78 Además, tendrás 20% de descuento en tu próximo pedido. ¿Te parece bien esta solución?" 79 `, 80}); 81

Agente Coordinador Principal

Ahora creamos el agente que coordina todo el sistema:

1const agenteCoordinador = agent({ 2 tools: [clasificarMensaje, gestionarContexto, coordinarAgentes], 3 systemPrompt: ` 4 Eres el coordinador principal de Taquería Doña Carmen. 5 6 Tu trabajo es: 7 1. Clasificar cada mensaje que llega 8 2. Gestionar el contexto del cliente 9 3. Coordinar con el agente especializado apropiado 10 4. Asegurar una experiencia fluida 11 12 PROCESO: 13 1. Usa clasificar_mensaje para entender el tipo de consulta 14 2. Usa gestionar_contexto para obtener historial del cliente 15 3. Usa coordinar_agentes para determinar la estrategia 16 4. Deriva al agente especializado con contexto completo 17 18 IMPORTANTE: 19 - Siempre saluda cálidamente 20 - Reconoce si es cliente frecuente 21 - Deriva rápidamente al especialista correcto 22 - No intentes resolver consultas especializadas tú mismo 23 24 Ejemplo: 25 "¡Hola! Bienvenido a Taquería Doña Carmen 🌮 26 Veo que quieres hacer un pedido. Te conecto con nuestro especialista en pedidos 27 que te ayudará inmediatamente." 28 `, 29}); 30

Sistema completo en acción

Ahora vamos a crear una función que simule el sistema completo:

1async function sistemaCompleto() { 2 console.log("🌮 SISTEMA COMPLETO - TAQUERÍA DOÑA CARMEN"); 3 console.log("=".repeat(60)); 4 5 // Simular diferentes tipos de mensajes 6 const mensajes = [ 7 { 8 cliente: "maria_123", 9 mensaje: "Hola, quiero 3 tacos de pastor y 2 quesadillas", 10 tipo: "pedido", 11 }, 12 { 13 cliente: "carlos_456", 14 mensaje: "¿Tienen tacos de barbacoa disponibles?", 15 tipo: "inventario", 16 }, 17 { 18 cliente: "ana_789", 19 mensaje: "Mi pedido llegó frío y tardó mucho", 20 tipo: "queja", 21 }, 22 { 23 cliente: "luis_012", 24 mensaje: "¿A qué hora abren los domingos?", 25 tipo: "informacion", 26 }, 27 ]; 28 29 for (const { cliente, mensaje, tipo } of mensajes) { 30 console.log(`\n📱 Cliente ${cliente}: "${mensaje}"`); 31 console.log("-".repeat(40)); 32 33 // 1. Coordinador clasifica y deriva 34 console.log("🎯 Coordinador analizando..."); 35 const streamCoordinador = await agenteCoordinador.runStream({ 36 message: `Cliente: ${cliente}, Mensaje: ${mensaje}`, 37 }); 38 39 for await (const chunk of streamCoordinador) { 40 process.stdout.write(chunk.delta); 41 } 42 43 console.log("\n"); 44 45 // 2. Agente especializado responde 46 let agenteEspecializado; 47 switch (tipo) { 48 case "pedido": 49 agenteEspecializado = agentePedidos; 50 console.log("🍽️ Especialista en pedidos respondiendo..."); 51 break; 52 case "inventario": 53 agenteEspecializado = agenteInventario; 54 console.log("📦 Especialista en inventario respondiendo..."); 55 break; 56 case "queja": 57 agenteEspecializado = agenteAtencionCliente; 58 console.log("🤝 Especialista en atención al cliente respondiendo..."); 59 break; 60 default: 61 console.log("ℹ️ Información general..."); 62 continue; 63 } 64 65 if (agenteEspecializado) { 66 const streamEspecialista = await agenteEspecializado.runStream({ 67 message: mensaje, 68 }); 69 70 for await (const chunk of streamEspecialista) { 71 process.stdout.write(chunk.delta); 72 } 73 } 74 75 console.log("\n" + "=".repeat(60)); 76 } 77} 78 79// Ejecutar el sistema completo 80sistemaCompleto().catch(console.error); 81

Resultado esperado

🌮 SISTEMA COMPLETO - TAQUERÍA DOÑA CARMEN
============================================================

📱 Cliente maria_123: "Hola, quiero 3 tacos de pastor y 2 quesadillas"
----------------------------------------
🎯 Coordinador analizando...
¡Hola! Bienvenido a Taquería Doña Carmen 🌮

Veo que quieres hacer un pedido. Te conecto inmediatamente con nuestro especialista en pedidos que te ayudará con todo lo que necesitas.

🍽️ Especialista en pedidos respondiendo...
¡Perfecto! Quieres 3 tacos de pastor y 2 quesadillas de queso.

✅ Todos los productos están disponibles:
• 3 tacos de pastor - $45
• 2 quesadillas de queso - $50

💰 Total: $95 pesos
⏱️ Tiempo estimado: 15 minutos

¿Confirmas tu pedido?

============================================================

📱 Cliente carlos_456: "¿Tienen tacos de barbacoa disponibles?"
----------------------------------------
🎯 Coordinador analizando...
¡Hola! Veo que quieres consultar sobre nuestro inventario. Te conecto con nuestro especialista que te dará información precisa sobre disponibilidad.

📦 Especialista en inventario respondiendo...
Lamentablemente no tenemos tacos de barbacoa disponibles hoy.

Pero tenemos otras opciones deliciosas:
• Tacos de pastor - $15
• Tacos de carnitas - $15
• Tacos de suadero - $16
• Tacos de chorizo - $14

Te recomiendo especialmente el suadero, está muy sabroso hoy. ¿Te interesa alguna de estas opciones?

============================================================

¿Qué acabamos de lograr?

  1. Sistema de clasificación inteligente: Identifica automáticamente el tipo de consulta
  2. Agentes especializados: Cada uno experto en su área específica
  3. Coordinación fluida: El coordinador deriva al especialista correcto
  4. Gestión de contexto: Recuerda interacciones previas del cliente
  5. Experiencia personalizada: Trato diferenciado según historial del cliente

Conceptos clave aprendidos

Especialización de agentes

Cada agente se enfoca en una tarea específica, haciéndolo más efectivo.

Coordinación inteligente

Un agente coordinador toma decisiones sobre qué especialista usar.

Herramientas sofisticadas

Las herramientas pueden tomar decisiones complejas y manejar datos estructurados.

Contexto persistente

El sistema recuerda interacciones previas para personalizar la experiencia.

Composición escalable

Puedes agregar nuevos agentes especializados sin afectar el sistema existente.

Lo que viene

En el siguiente capítulo exploraremos Workflows con Múltiples Steps, donde aprenderemos:

  • Cómo crear secuencias complejas de operaciones
  • Manejo de dependencias entre tareas
  • Paralelización de operaciones independientes
  • Manejo de errores en workflows complejos

¡Ya tienes un sistema multi-agente funcionando! 🎉

Ejercicio práctico

Expande el sistema agregando:

Nivel 1: Básico

  1. Agente de promociones que maneje descuentos y ofertas especiales
  2. Herramienta de horarios que verifique si la taquería está abierta
  3. Sistema de notificaciones para avisar cuando el pedido esté listo

Nivel 2: Intermedio

  1. Agente de delivery que calcule rutas y tiempos de entrega
  2. Sistema de puntos de lealtad con agente especializado
  3. Agente de análisis que genere reportes de ventas

Nivel 3: Avanzado

  1. Integración con WhatsApp Business API real
  2. Base de datos persistente para clientes y pedidos
  3. Dashboard en tiempo real para monitorear el sistema

¿Te animas a expandir el sistema? En el próximo capítulo veremos cómo manejar workflows aún más complejos.