Capítulo 2: Tu Primer Workflow
¡Es hora de crear tu primer agente realmente complejo! En este capítulo construiremos un sistema completo para una taquería que puede procesar pedidos automáticamente usando el enfoque funcional de LlamaIndex TypeScript.
El problema que vamos a resolver
Doña Carmen tiene una taquería muy popular, pero está abrumada con los pedidos por WhatsApp. Cada día recibe mensajes como:
- "Hola, quiero 3 tacos de pastor y 2 de carnitas"
- "Buenos días, me das 4 quesadillas de queso y un agua de horchata"
- "Necesito 5 tacos de suadero para llevar"
Ella tiene que:
- Leer cada mensaje
- Entender qué quiere el cliente
- Verificar si tiene ingredientes
- Calcular el precio
- Responder al cliente
- Actualizar su inventario
¡Vamos a automatizar todo esto con un agente inteligente!
Configuración inicial
Primero, necesitamos instalar LlamaIndex TypeScript:
1# Crear un nuevo proyecto 2mkdir taqueria-agent 3cd taqueria-agent 4npm init -y 5 6# Instalar dependencias 7npm install llamaindex 8npm install -D typescript @types/node ts-node 9 10# Configurar TypeScript 11npx tsc --init 12
Configuración de TypeScript
Actualiza tu tsconfig.json:
1{ 2 "compilerOptions": { 3 "target": "ES2022", 4 "module": "commonjs", 5 "lib": ["ES2022"], 6 "outDir": "./dist", 7 "rootDir": "./src", 8 "strict": true, 9 "esModuleInterop": true, 10 "skipLibCheck": true, 11 "forceConsistentCasingInFileNames": true, 12 "moduleResolution": "node" 13 }, 14 "include": ["src/**/*"], 15 "exclude": ["node_modules", "dist"] 16} 17
Diseñando nuestro agente
Antes de escribir código, pensemos en las herramientas que necesitamos:
1. Procesar pedido → 2. Verificar inventario → 3. Calcular precio → 4. Generar respuesta
Las herramientas que necesitamos
procesarPedido: Extrae productos y cantidades del mensajeverificarInventario: Verifica disponibilidad de ingredientescalcularPrecio: Calcula el total del pedidoactualizarInventario: Actualiza el stock después de confirmar
Implementación completa
Aquí está nuestro agente completo para la taquería:
1import { agent, tool } from "llamaindex"; 2 3// Base de datos simulada del menú 4const menu = { 5 "taco de pastor": { 6 precio: 15, 7 ingredientes: ["tortilla", "pastor", "piña"], 8 }, 9 "taco de carnitas": { precio: 15, ingredientes: ["tortilla", "carnitas"] }, 10 "taco de suadero": { precio: 16, ingredientes: ["tortilla", "suadero"] }, 11 "taco de chorizo": { precio: 14, ingredientes: ["tortilla", "chorizo"] }, 12 "quesadilla de queso": { precio: 25, ingredientes: ["tortilla", "queso"] }, 13 "quesadilla de flor de calabaza": { 14 precio: 30, 15 ingredientes: ["tortilla", "queso", "flor de calabaza"], 16 }, 17 "agua de horchata": { precio: 20, ingredientes: ["horchata"] }, 18 "agua de jamaica": { precio: 18, ingredientes: ["jamaica"] }, 19 "coca cola": { precio: 25, ingredientes: ["refresco"] }, 20}; 21 22// Inventario disponible (simulado) 23let inventario = { 24 tortilla: 100, 25 pastor: 50, 26 carnitas: 30, 27 suadero: 25, 28 chorizo: 40, 29 queso: 20, 30 "flor de calabaza": 15, 31 piña: 10, 32 horchata: 10, 33 jamaica: 8, 34 refresco: 15, 35}; 36 37// Herramienta para procesar pedidos 38const procesarPedido = tool( 39 async ({ mensaje }: { mensaje: string }) => { 40 const texto = mensaje.toLowerCase(); 41 const items = []; 42 43 // Buscar tacos 44 const regexTacos = /(\d+)\s*tacos?\s*de\s*(\w+)/g; 45 let match; 46 while ((match = regexTacos.exec(texto)) !== null) { 47 const cantidad = parseInt(match[1]); 48 const tipo = match[2]; 49 const producto = `taco de ${tipo}`; 50 51 if (menu[producto as keyof typeof menu]) { 52 items.push({ producto, cantidad }); 53 } 54 } 55 56 // Buscar quesadillas 57 const regexQuesadillas = /(\d+)\s*quesadillas?\s*de\s*([\w\s]+)/g; 58 while ((match = regexQuesadillas.exec(texto)) !== null) { 59 const cantidad = parseInt(match[1]); 60 const tipo = match[2].trim(); 61 const producto = `quesadilla de ${tipo}`; 62 63 if (menu[producto as keyof typeof menu]) { 64 items.push({ producto, cantidad }); 65 } 66 } 67 68 // Buscar bebidas 69 if (texto.includes("horchata")) { 70 items.push({ producto: "agua de horchata", cantidad: 1 }); 71 } 72 if (texto.includes("jamaica")) { 73 items.push({ producto: "agua de jamaica", cantidad: 1 }); 74 } 75 if (texto.includes("coca") || texto.includes("refresco")) { 76 items.push({ producto: "coca cola", cantidad: 1 }); 77 } 78 79 return { 80 items, 81 mensaje: `Encontré ${items.length} productos en tu pedido`, 82 }; 83 }, 84 { 85 name: "procesar_pedido", 86 description: 87 "Extrae productos y cantidades de un mensaje de pedido en español", 88 } 89); 90 91// Herramienta para verificar inventario 92const verificarInventario = tool( 93 async ({ 94 items, 95 }: { 96 items: Array<{ producto: string; cantidad: number }>; 97 }) => { 98 const disponibles = []; 99 const noDisponibles = []; 100 101 for (const item of items) { 102 const productoInfo = menu[item.producto as keyof typeof menu]; 103 104 if (!productoInfo) { 105 noDisponibles.push(`${item.producto} (no está en el menú)`); 106 continue; 107 } 108 109 // Verificar cada ingrediente 110 let disponible = true; 111 for (const ingrediente of productoInfo.ingredientes) { 112 const stockDisponible = 113 inventario[ingrediente as keyof typeof inventario]; 114 if (!stockDisponible || stockDisponible < item.cantidad) { 115 disponible = false; 116 break; 117 } 118 } 119 120 if (disponible) { 121 disponibles.push({ 122 ...item, 123 precio: productoInfo.precio, 124 subtotal: productoInfo.precio * item.cantidad, 125 }); 126 } else { 127 noDisponibles.push( 128 `${item.cantidad} ${item.producto} (sin ingredientes suficientes)` 129 ); 130 } 131 } 132 133 return { 134 disponibles, 135 noDisponibles, 136 mensaje: `${disponibles.length} productos disponibles, ${noDisponibles.length} no disponibles`, 137 }; 138 }, 139 { 140 name: "verificar_inventario", 141 description: 142 "Verifica la disponibilidad de productos según el inventario actual", 143 } 144); 145 146// Herramienta para calcular precios 147const calcularPrecio = tool( 148 async ({ 149 items, 150 }: { 151 items: Array<{ 152 producto: string; 153 cantidad: number; 154 precio: number; 155 subtotal: number; 156 }>; 157 }) => { 158 const total = items.reduce((sum, item) => sum + item.subtotal, 0); 159 const tiempoEstimado = Math.max(5, items.length * 3); // 3 minutos por producto, mínimo 5 160 161 // Aplicar descuentos si aplica 162 let descuento = 0; 163 let mensajeDescuento = ""; 164 165 if (total > 100) { 166 descuento = total * 0.05; // 5% descuento por compra mayor a $100 167 mensajeDescuento = "¡Descuento del 5% por compra mayor a $100!"; 168 } 169 170 const totalFinal = total - descuento; 171 172 return { 173 subtotal: total, 174 descuento, 175 total: totalFinal, 176 tiempoEstimado, 177 mensajeDescuento, 178 resumen: `Subtotal: $${total}, Descuento: $${descuento}, Total: $${totalFinal}, Tiempo: ${tiempoEstimado} min`, 179 }; 180 }, 181 { 182 name: "calcular_precio", 183 description: 184 "Calcula el precio total, descuentos y tiempo estimado de preparación", 185 } 186); 187 188// Herramienta para actualizar inventario 189const actualizarInventario = tool( 190 async ({ 191 items, 192 }: { 193 items: Array<{ producto: string; cantidad: number }>; 194 }) => { 195 const actualizaciones = []; 196 197 for (const item of items) { 198 const productoInfo = menu[item.producto as keyof typeof menu]; 199 if (productoInfo) { 200 for (const ingrediente of productoInfo.ingredientes) { 201 const stockAnterior = 202 inventario[ingrediente as keyof typeof inventario]; 203 inventario[ingrediente as keyof typeof inventario] -= item.cantidad; 204 const stockNuevo = inventario[ingrediente as keyof typeof inventario]; 205 206 actualizaciones.push( 207 `${ingrediente}: ${stockAnterior} → ${stockNuevo}` 208 ); 209 } 210 } 211 } 212 213 return { 214 actualizaciones, 215 mensaje: `Inventario actualizado para ${items.length} productos`, 216 }; 217 }, 218 { 219 name: "actualizar_inventario", 220 description: "Actualiza el inventario después de confirmar un pedido", 221 } 222); 223 224// Crear el agente de la taquería 225const taqueriaAgent = agent({ 226 tools: [ 227 procesarPedido, 228 verificarInventario, 229 calcularPrecio, 230 actualizarInventario, 231 ], 232 systemPrompt: ` 233 Eres el asistente inteligente de la Taquería "Doña Carmen", una taquería familiar mexicana. 234 235 Tu trabajo es procesar pedidos de WhatsApp de manera amigable y eficiente: 236 237 1. SIEMPRE saluda al cliente de manera cálida 238 2. Usa la herramienta procesar_pedido para entender qué quiere el cliente 239 3. Usa verificar_inventario para confirmar disponibilidad 240 4. Si hay productos no disponibles, ofrece alternativas similares 241 5. Usa calcular_precio para obtener el total y tiempo estimado 242 6. Presenta un resumen claro del pedido con precios 243 7. Pregunta si confirma el pedido 244 8. Si confirma, usa actualizar_inventario 245 9. Da un mensaje final con tiempo de preparación y agradecimiento 246 247 IMPORTANTE: 248 - Sé siempre amigable y usa emojis apropiados 249 - Habla como una persona real, no como un robot 250 - Si algo no está disponible, sugiere alternativas 251 - Siempre confirma antes de actualizar el inventario 252 - Menciona el tiempo estimado de preparación 253 254 Ejemplo de flujo: 255 "¡Hola! Bienvenido a Taquería Doña Carmen 🌮 256 257 Veo que quieres [productos]. Déjame verificar qué tenemos disponible... 258 259 ✅ Disponible: [lista] 260 ❌ No disponible: [lista con alternativas] 261 262 Tu pedido sería: 263 [resumen con precios] 264 265 ¿Confirmas tu pedido?" 266 `, 267}); 268
Probando nuestro agente
Ahora vamos a probar nuestro sistema:
1async function probarTaqueria() { 2 console.log("🌮 TAQUERÍA DOÑA CARMEN - Sistema de Pedidos"); 3 console.log("=".repeat(50)); 4 5 // Ejemplo 1: Pedido normal 6 console.log("\n📱 Pedido 1: Cliente María"); 7 const stream1 = await taqueriaAgent.runStream({ 8 message: 9 "Hola, quiero 3 tacos de pastor y 2 quesadillas de queso, por favor", 10 }); 11 12 for await (const chunk of stream1) { 13 process.stdout.write(chunk.delta); 14 } 15 16 console.log("\n\n" + "=".repeat(50)); 17 18 // Ejemplo 2: Pedido más complejo 19 console.log("\n📱 Pedido 2: Cliente Carlos"); 20 const stream2 = await taqueriaAgent.runStream({ 21 message: 22 "Buenos días, me das 5 tacos de carnitas, 1 quesadilla de flor de calabaza y una horchata", 23 }); 24 25 for await (const chunk of stream2) { 26 process.stdout.write(chunk.delta); 27 } 28 29 console.log("\n\n" + "=".repeat(50)); 30 31 // Ejemplo 3: Pedido con productos no disponibles 32 console.log("\n📱 Pedido 3: Cliente Ana"); 33 const stream3 = await taqueriaAgent.runStream({ 34 message: "Quiero 10 tacos de pastor y 5 quesadillas de queso", 35 }); 36 37 for await (const chunk of stream3) { 38 process.stdout.write(chunk.delta); 39 } 40} 41 42// Ejecutar la prueba 43probarTaqueria().catch(console.error); 44
Ejecutando el código
Guarda todo en un archivo src/taqueria.ts y ejecuta:
1npx ts-node src/taqueria.ts 2
Deberías ver algo como:
🌮 TAQUERÍA DOÑA CARMEN - Sistema de Pedidos
==================================================
📱 Pedido 1: Cliente María
¡Hola! Bienvenido a Taquería Doña Carmen 🌮
Veo que quieres 3 tacos de pastor y 2 quesadillas de queso. Déjame verificar qué tenemos disponible...
✅ Productos disponibles:
• 3 tacos de pastor - $45
• 2 quesadillas de queso - $50
💰 Resumen de tu pedido:
Subtotal: $95
Total: $95 pesos
⏱️ Tiempo estimado: 15 minutos
¿Confirmas tu pedido?
==================================================
📱 Pedido 2: Cliente Carlos
¡Buenos días! Bienvenido a Taquería Doña Carmen 🌮
Perfecto, quieres 5 tacos de carnitas, 1 quesadilla de flor de calabaza y un agua de horchata.
✅ Todos los productos están disponibles:
• 5 tacos de carnitas - $75
• 1 quesadilla de flor de calabaza - $30
• 1 agua de horchata - $20
💰 Resumen de tu pedido:
Subtotal: $125
¡Descuento del 5% por compra mayor a $100! - $6.25
Total: $118.75 pesos
⏱️ Tiempo estimado: 21 minutos
¿Confirmas tu pedido?
¿Qué acabamos de lograr?
- Procesamiento de lenguaje natural: El agente entiende pedidos en español natural
- Validación de inventario: Verifica disponibilidad antes de confirmar
- Cálculos automáticos: Precios, descuentos y tiempos se calculan automáticamente
- Respuestas en streaming: El cliente ve la respuesta conforme se genera
- Gestión de inventario: Actualiza stock automáticamente
- Experiencia conversacional: Se siente como hablar con una persona real
La magia del enfoque funcional
Composición natural
Cada herramienta hace una cosa específica y se combina naturalmente:
Mensaje → procesarPedido → verificarInventario → calcularPrecio → actualizarInventario
Funciones puras
Cada herramienta es una función pura que:
- Recibe inputs específicos
- Produce outputs predecibles
- No tiene efectos secundarios (excepto actualizar inventario al final)
Streaming nativo
El agente responde en tiempo real, creando una experiencia fluida para el cliente.
Mejoras que puedes hacer
1. Agregar más productos
1const menu = { 2 // Tacos 3 "taco de pastor": { 4 precio: 15, 5 ingredientes: ["tortilla", "pastor", "piña"], 6 }, 7 "taco de barbacoa": { precio: 18, ingredientes: ["tortilla", "barbacoa"] }, 8 "taco de cochinita": { precio: 17, ingredientes: ["tortilla", "cochinita"] }, 9 10 // Tortas 11 "torta de milanesa": { 12 precio: 45, 13 ingredientes: ["pan", "milanesa", "verduras"], 14 }, 15 "torta de pastor": { precio: 40, ingredientes: ["pan", "pastor", "piña"] }, 16 17 // Más bebidas 18 "agua de tamarindo": { precio: 18, ingredientes: ["tamarindo"] }, 19 cerveza: { precio: 30, ingredientes: ["cerveza"] }, 20}; 21
2. Manejar promociones por día
1const verificarPromociones = tool( 2 async () => { 3 const hoy = new Date().getDay(); // 0 = domingo, 1 = lunes, etc. 4 5 const promociones = { 6 1: { nombre: "Lunes de tacos", descuento: 0.15, productos: ["taco"] }, 7 5: { 8 nombre: "Viernes de bebidas", 9 descuento: 0.2, 10 productos: ["agua", "coca"], 11 }, 12 }; 13 14 return promociones[hoy] || null; 15 }, 16 { 17 name: "verificar_promociones", 18 description: "Verifica si hay promociones activas para el día actual", 19 } 20); 21
3. Integrar con WhatsApp real
1const enviarWhatsApp = tool( 2 async ({ telefono, mensaje }: { telefono: string; mensaje: string }) => { 3 // Usar API de WhatsApp Business 4 // const response = await whatsappAPI.sendMessage(telefono, mensaje); 5 console.log(`📱 Enviando a ${telefono}: ${mensaje}`); 6 return { enviado: true }; 7 }, 8 { 9 name: "enviar_whatsapp", 10 description: "Envía un mensaje por WhatsApp al cliente", 11 } 12); 13
4. Persistir datos
1const guardarPedido = tool( 2 async ({ pedido }: { pedido: any }) => { 3 // Guardar en base de datos 4 // await db.pedidos.create(pedido); 5 console.log("💾 Pedido guardado en base de datos"); 6 return { guardado: true, id: Date.now() }; 7 }, 8 { 9 name: "guardar_pedido", 10 description: "Guarda el pedido en la base de datos", 11 } 12); 13
Conceptos clave aprendidos
Agente como orquestador
El agente coordina todas las herramientas sin hacer el trabajo pesado él mismo.
Herramientas especializadas
Cada herramienta tiene una responsabilidad específica y bien definida.
Streaming para UX
El streaming hace que la experiencia se sienta natural y responsiva.
Funciones puras
Las herramientas son predecibles y fáciles de probar.
Composición flexible
Puedes agregar, quitar o modificar herramientas sin afectar el resto.
Lo que viene
En el siguiente capítulo profundizaremos en Agentes Múltiples y Colaboración, aprendiendo:
- Cómo crear múltiples agentes especializados
- Patrones de comunicación entre agentes
- Coordinación de tareas complejas
- Manejo de conflictos y prioridades
¡Ya tienes tu primer agente complejo funcionando! 🎉
Ejercicio práctico
Antes de continuar, intenta expandir el agente de la taquería:
Nivel 1: Básico
- Agregar validación de horarios - verificar si la taquería está abierta
- Implementar sistema de descuentos por cliente frecuente
- Crear herramienta de sugerencias cuando algo no esté disponible
Nivel 2: Intermedio
- Agregar gestión de mesas para pedidos en el local
- Implementar sistema de puntos de lealtad
- Crear estimación de entrega para pedidos a domicilio
Nivel 3: Avanzado
- Integrar base de datos real (SQLite o MongoDB)
- Crear dashboard de administración para ver pedidos
- Implementar notificaciones cuando el pedido esté listo
¿Te animas a intentarlo? En el próximo capítulo veremos implementaciones avanzadas de estos conceptos.
