Construir Agentes de IA que Realmente Usen tus Modelos de Excel

De Excel a Agente de IA en 30 Minutos

Tu hoja de cálculo de Excel ha evolucionado durante años. Maneja casos especiales, implementa reglas complejas de negocio e incorpora conocimiento profundo del dominio. Ahora quieres que un agente de IA la use.

La mayoría de los tutoriales te dirán que "simplemente exportes a CSV" o "reconstruyas en Python." Vamos a hacer algo mejor: dar a tu agente de IA acceso directo al motor de cálculo de Excel.

Lo que Estamos Construyendo

Un agente de IA de servicio al cliente que puede:

  • Calcular cotizaciones precisas usando tu Excel de precios
  • Verificar fechas de entrega con tu modelo de logística
  • Aplicar descuentos basados en reglas complejas de negocio
  • Manejar casos especiales exactamente como lo hace tu equipo

Todo usando tus archivos de Excel existentes. No se requiere reconstrucción.

Prerrequisitos

# Necesitarás:
npm install langchain @langchain/openai
# o
pip install langchain openai

# Y una cuenta de SpreadAPI (el nivel gratuito funciona)
# Regístrate en https://spreadapi.io

Paso 1: Preparar tu Excel para IA

Tu Estructura de Excel

PricingModel.xlsx
├── Entradas
│   ├── B2: Código de Producto
│   ├── B3: Cantidad
│   ├── B4: Tipo de Cliente
│   └── B5: Región
├── Cálculos (Ocultos de la IA)
│   ├── Fórmulas VLOOKUP complejas
│   ├── Matrices de descuentos
│   └── Reglas de negocio
└── Salidas
    ├── E10: Precio Base
    ├── E11: Monto de Descuento
    ├── E12: Precio Final
    └── E13: Fecha de Entrega

Subir a SpreadAPI

  1. Iniciar sesión en el Dashboard de SpreadAPI
  2. Crear nuevo servicio llamado "pricing-model"
  3. Subir tu Excel
  4. Definir interfaz:
{
  "inputs": {
    "productCode": "B2",
    "quantity": "B3",
    "customerType": "B4",
    "region": "B5"
  },
  "outputs": {
    "basePrice": "E10",
    "discount": "E11",
    "finalPrice": "E12",
    "deliveryDate": "E13"
  }
}

Paso 2: Crear el Agente de IA

Agente Básico con Function Calling

import { ChatOpenAI } from '@langchain/openai';
import { SpreadAPITool } from './spreadapi-tool';

// Definir la herramienta de cálculo de Excel
const pricingTool = {
  name: "calculate_pricing",
  description: "Calcular precios precisos usando el modelo de precios de la empresa. Úsalo siempre que necesites cotizar precios o verificar descuentos.",
  parameters: {
    type: "object",
    properties: {
      productCode: {
        type: "string",
        description: "Código de producto (ej. 'PRO-001')"
      },
      quantity: {
        type: "number",
        description: "Número de unidades"
      },
      customerType: {
        type: "string",
        enum: ["standard", "premium", "enterprise"],
        description: "Tipo de cuenta del cliente"
      },
      region: {
        type: "string",
        enum: ["US", "EU", "APAC"],
        description: "Región del cliente"
      }
    },
    required: ["productCode", "quantity", "customerType", "region"]
  },
  execute: async (params) => {
    // Llamar a SpreadAPI
    const response = await fetch('https://api.spreadapi.io/v1/services/pricing-model/execute', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SPREADAPI_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ inputs: params })
    });
    
    const result = await response.json();
    return result.outputs;
  }
};

// Crear el agente de IA
const model = new ChatOpenAI({
  modelName: "gpt-4",
  temperature: 0
});

const tools = [pricingTool];
const modelWithTools = model.bind({ tools });

Implementar la Lógica del Agente

class CustomerServiceAgent {
  constructor(model, tools) {
    this.model = model;
    this.tools = tools;
    this.conversation = [];
  }
  
  async respond(userMessage) {
    // Agregar mensaje del usuario a la conversación
    this.conversation.push({
      role: 'user',
      content: userMessage
    });
    
    // Obtener respuesta de IA con posibles llamadas a herramientas
    const response = await this.model.invoke(this.conversation);
    
    // Manejar llamadas a herramientas
    if (response.tool_calls && response.tool_calls.length > 0) {
      for (const toolCall of response.tool_calls) {
        const tool = this.tools.find(t => t.name === toolCall.name);
        
        if (tool) {
          // Ejecutar el cálculo de Excel
          const result = await tool.execute(toolCall.arguments);
          
          // Agregar resultado de herramienta a la conversación
          this.conversation.push({
            role: 'tool',
            content: JSON.stringify(result),
            tool_call_id: toolCall.id
          });
        }
      }
      
      // Obtener respuesta final después de la ejecución de herramientas
      const finalResponse = await this.model.invoke(this.conversation);
      this.conversation.push({
        role: 'assistant',
        content: finalResponse.content
      });
      
      return finalResponse.content;
    }
    
    // No se necesitan llamadas a herramientas
    this.conversation.push({
      role: 'assistant',
      content: response.content
    });
    
    return response.content;
  }
}

Paso 3: Patrones Listos para Producción

Patrón 1: Agente Multi-Herramienta

// Agregar múltiples herramientas basadas en Excel
const tools = [
  {
    name: "calculate_pricing",
    description: "Calcular precios de productos y descuentos",
    spreadapiService: "pricing-model",
    execute: spreadapiExecutor("pricing-model")
  },
  {
    name: "check_inventory",
    description: "Verificar disponibilidad de productos y tiempos de entrega",
    spreadapiService: "inventory-tracker",
    execute: spreadapiExecutor("inventory-tracker")
  },
  {
    name: "calculate_shipping",
    description: "Calcular costos de envío y fechas de entrega",
    spreadapiService: "logistics-calculator",
    execute: spreadapiExecutor("logistics-calculator")
  }
];

// Función auxiliar para ejecución de SpreadAPI
function spreadapiExecutor(serviceName) {
  return async (params) => {
    const response = await fetch(
      `https://api.spreadapi.io/v1/services/${serviceName}/execute`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.SPREADAPI_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ inputs: params })
      }
    );
    
    if (!response.ok) {
      throw new Error(`Fallo en cálculo de Excel: ${response.statusText}`);
    }
    
    const result = await response.json();
    return result.outputs;
  };
}

Patrón 2: Agente Consciente del Contexto

class ContextAwareAgent {
  constructor() {
    this.customerContext = {};
    this.calculationCache = new Map();
  }
  
  async handleQuery(query, customerId) {
    // Cargar contexto del cliente
    if (!this.customerContext[customerId]) {
      this.customerContext[customerId] = await this.loadCustomerData(customerId);
    }
    
    const context = this.customerContext[customerId];
    
    // Mejorar prompt con contexto
    const enhancedPrompt = `
      Información del Cliente:
      - Tipo: ${context.customerType}
      - Región: ${context.region}
      - Historial de Compras: ${context.totalPurchases} pedidos
      
      Consulta del Usuario: ${query}
      
      Instrucciones:
      - Usa la herramienta calculate_pricing para cualquier cotización de precios
      - Aplica automáticamente el tipo de cliente apropiado
      - Considera su región para cálculos de envío
    `;
    
    return await this.respond(enhancedPrompt);
  }
  
  async loadCustomerData(customerId) {
    // Cargar desde tu base de datos
    return {
      customerType: 'enterprise',
      region: 'US',
      totalPurchases: 47
    };
  }
}

Patrón 3: Validación y Manejo de Errores

class RobustAgent {
  async executeToolSafely(tool, params) {
    try {
      // Validar entradas antes de enviar a Excel
      const validation = this.validateInputs(tool.name, params);
      if (!validation.valid) {
        return {
          error: `Entrada inválida: ${validation.message}`,
          suggestion: validation.suggestion
        };
      }
      
      // Verificar caché primero
      const cacheKey = `${tool.name}:${JSON.stringify(params)}`;
      if (this.cache.has(cacheKey)) {
        return this.cache.get(cacheKey);
      }
      
      // Ejecutar con timeout
      const result = await Promise.race([
        tool.execute(params),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Timeout de cálculo')), 5000)
        )
      ]);
      
      // Cachear resultados exitosos
      this.cache.set(cacheKey, result);
      
      // Validar salida
      if (result.finalPrice < 0) {
        return {
          error: 'Resultado de cálculo inválido',
          suggestion: 'Por favor verifica el código de producto y la cantidad'
        };
      }
      
      return result;
      
    } catch (error) {
      console.error('Fallo en ejecución de herramienta:', error);
      
      // Respuesta de respaldo
      return {
        error: 'No se puede calcular en este momento',
        suggestion: 'Por favor intenta de nuevo o contacta soporte',
        reference: error.message
      };
    }
  }
  
  validateInputs(toolName, params) {
    if (toolName === 'calculate_pricing') {
      if (params.quantity < 1) {
        return {
          valid: false,
          message: 'La cantidad debe ser al menos 1',
          suggestion: 'Por favor especifica una cantidad válida'
        };
      }
      
      if (!params.productCode.match(/^[A-Z]{3}-\d{3}$/)) {
        return {
          valid: false,
          message: 'Formato de código de producto inválido',
          suggestion: 'Los códigos de producto deben ser como ABC-123'
        };
      }
    }
    
    return { valid: true };
  }
}

Paso 4: Capacidades Avanzadas del Agente

Capacidad 1: Cálculos Multi-Paso

const complexWorkflowTool = {
  name: "quote_with_options",
  description: "Generar una cotización completa con múltiples opciones de producto",
  execute: async (params) => {
    const { products, customerType, region } = params;
    
    // Calcular precios para cada producto
    const quotes = await Promise.all(
      products.map(async (product) => {
        const pricing = await spreadapiExecutor('pricing-model')({
          productCode: product.code,
          quantity: product.quantity,
          customerType,
          region
        });
        
        const shipping = await spreadapiExecutor('logistics-calculator')({
          productCode: product.code,
          quantity: product.quantity,
          region,
          expedited: product.expedited || false
        });
        
        return {
          product: product.code,
          quantity: product.quantity,
          pricing,
          shipping,
          total: pricing.finalPrice + shipping.cost
        };
      })
    );
    
    // Calcular descuento de paquete si aplica
    if (quotes.length > 1) {
      const bundleResult = await spreadapiExecutor('bundle-calculator')({
        products: products.map(p => p.code),
        quantities: products.map(p => p.quantity),
        customerType
      });
      
      return {
        individualQuotes: quotes,
        bundleDiscount: bundleResult.discount,
        bundleTotal: bundleResult.total
      };
    }
    
    return { quotes };
  }
};

Capacidad 2: Explicaciones y Razonamiento

class ExplainableAgent {
  async respondWithExplanation(query) {
    const response = await this.model.invoke([
      {
        role: 'system',
        content: `Eres un agente de servicio al cliente útil.
        Al usar herramientas de precios, siempre explica:
        1. Qué factores afectaron el precio
        2. Qué descuentos se aplicaron
        3. Por qué esta es la mejor opción para el cliente`
      },
      {
        role: 'user',
        content: query
      }
    ]);
    
    // Procesar llamadas de herramientas y agregar explicaciones
    if (response.tool_calls) {
      const explanations = [];
      
      for (const toolCall of response.tool_calls) {
        const result = await this.executeTool(toolCall);
        
        // Generar explicación basada en resultados
        if (toolCall.name === 'calculate_pricing') {
          const discount = result.basePrice - result.finalPrice;
          const discountPercent = (discount / result.basePrice * 100).toFixed(1);
          
          explanations.push({
            calculation: toolCall.name,
            explanation: `
              Precio base: $${result.basePrice}
              ${discount > 0 ? `Descuento aplicado: $${discount} (${discountPercent}%)` : 'No hay descuento aplicable'}
              Precio final: $${result.finalPrice}
              Entrega antes del: ${result.deliveryDate}
            `
          });
        }
      }
      
      // Obtener respuesta final con explicaciones
      const finalResponse = await this.model.invoke([
        ...this.conversation,
        {
          role: 'system',
          content: `Incluye estos detalles de cálculo en tu respuesta: ${JSON.stringify(explanations)}`
        }
      ]);
      
      return finalResponse.content;
    }
    
    return response.content;
  }
}

Capacidad 3: Comparación de Escenarios

const scenarioTool = {
  name: "compare_scenarios",
  description: "Comparar diferentes escenarios de compra para encontrar la mejor opción",
  execute: async (params) => {
    const scenarios = [
      {
        name: "Compra Individual",
        params: {
          quantity: params.quantity,
          customerType: params.customerType
        }
      },
      {
        name: "Compra a Granel",
        params: {
          quantity: params.quantity * 3,
          customerType: params.customerType
        }
      },
      {
        name: "Contrato Anual",
        params: {
          quantity: params.quantity * 12,
          customerType: 'enterprise' // Mejora automática
        }
      }
    ];
    
    const results = await Promise.all(
      scenarios.map(async (scenario) => {
        const pricing = await spreadapiExecutor('pricing-model')({
          ...params,
          ...scenario.params
        });
        
        return {
          scenario: scenario.name,
          totalQuantity: scenario.params.quantity,
          unitPrice: pricing.finalPrice / scenario.params.quantity,
          totalPrice: pricing.finalPrice,
          savings: (params.quantity * (pricing.basePrice / scenario.params.quantity)) - pricing.finalPrice
        };
      })
    );
    
    // Encontrar la mejor opción
    const bestOption = results.reduce((best, current) => 
      current.unitPrice < best.unitPrice ? current : best
    );
    
    return {
      scenarios: results,
      recommendation: bestOption,
      potentialSavings: results[0].totalPrice - bestOption.totalPrice
    };
  }
};

Paso 5: Implementación y Monitoreo

Configuración de Producción

// config/agent.js
export const agentConfig = {
  model: {
    name: process.env.MODEL_NAME || 'gpt-4',
    temperature: 0,
    maxTokens: 1000,
    timeout: 30000
  },
  
  spreadapi: {
    baseUrl: process.env.SPREADAPI_URL || 'https://api.spreadapi.io/v1',
    apiKey: process.env.SPREADAPI_KEY,
    timeout: 5000,
    retries: 3
  },
  
  caching: {
    ttl: 300, // 5 minutos
    maxSize: 1000
  },
  
  monitoring: {
    logLevel: process.env.LOG_LEVEL || 'info',
    metricsEnabled: true,
    tracingEnabled: process.env.NODE_ENV === 'production'
  }
};

Monitoreo y Analíticas

class MonitoredAgent {
  constructor(config) {
    this.metrics = {
      totalRequests: 0,
      toolCalls: {},
      errors: {},
      responseTime: []
    };
  }
  
  async handleRequest(query) {
    const startTime = Date.now();
    const requestId = generateRequestId();
    
    try {
      console.log(`[${requestId}] Procesando consulta:`, query);
      
      const response = await this.agent.respond(query);
      
      const duration = Date.now() - startTime;
      this.metrics.responseTime.push(duration);
      this.metrics.totalRequests++;
      
      console.log(`[${requestId}] Completado en ${duration}ms`);
      
      // Enviar a analíticas
      await this.sendAnalytics({
        requestId,
        duration,
        toolsUsed: this.agent.lastToolCalls,
        success: true
      });
      
      return response;
      
    } catch (error) {
      const errorType = error.name || 'Desconocido';
      this.metrics.errors[errorType] = (this.metrics.errors[errorType] || 0) + 1;
      
      console.error(`[${requestId}] Error:`, error);
      
      await this.sendAnalytics({
        requestId,
        error: error.message,
        success: false
      });
      
      throw error;
    }
  }
  
  getMetrics() {
    const avgResponseTime = 
      this.metrics.responseTime.reduce((a, b) => a + b, 0) / 
      this.metrics.responseTime.length;
    
    return {
      totalRequests: this.metrics.totalRequests,
      averageResponseTime: avgResponseTime,
      toolUsage: this.metrics.toolCalls,
      errorRate: Object.values(this.metrics.errors).reduce((a, b) => a + b, 0) / 
                 this.metrics.totalRequests
    };
  }
}

Errores Comunes y Soluciones

Error 1: Sobrecargar el Agente

//  Malo: Dar demasiada libertad al agente
const badPrompt = "Ayuda al cliente con cualquier cosa que necesite";

//  Bueno: Límites y capacidades claras
const goodPrompt = `Eres un agente de servicio al cliente especializado en:
1. Precios de productos (usa la herramienta calculate_pricing)
2. Disponibilidad de inventario (usa la herramienta check_inventory)
3. Estimaciones de envío (usa la herramienta calculate_shipping)

Para otras solicitudes, explica cortésmente en qué puedes ayudar.`;

Error 2: No Manejar Errores de Excel

//  Manejo robusto de errores
const toolWithErrorHandling = {
  execute: async (params) => {
    try {
      const result = await spreadapiCall(params);
      
      // Validar resultados de Excel
      if (result.outputs.error) {
        return {
          success: false,
          error: 'Error de cálculo en Excel',
          details: result.outputs.error,
          suggestion: 'Por favor verifica el código de producto e intenta de nuevo'
        };
      }
      
      return { success: true, ...result.outputs };
      
    } catch (error) {
      if (error.status === 422) {
        return {
          success: false,
          error: 'Parámetros de entrada inválidos',
          suggestion: 'Por favor verifica el formato de tu código de producto'
        };
      }
      
      throw error; // Re-lanzar errores inesperados
    }
  }
};

Error 3: Ignorar el Rendimiento

//  Optimización del rendimiento
class OptimizedAgent {
  constructor() {
    this.cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 5 });
    this.batchQueue = [];
    this.batchTimer = null;
  }
  
  async calculatePricing(params) {
    // Verificar caché primero
    const cacheKey = JSON.stringify(params);
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    
    // Agrupar solicitudes similares
    return new Promise((resolve) => {
      this.batchQueue.push({ params, resolve });
      
      if (!this.batchTimer) {
        this.batchTimer = setTimeout(() => this.processBatch(), 50);
      }
    });
  }
  
  async processBatch() {
    const batch = this.batchQueue.splice(0, 50); // Procesar hasta 50 a la vez
    
    const results = await spreadapiExecutor('pricing-model').batch(
      batch.map(item => item.params)
    );
    
    results.forEach((result, index) => {
      const { params, resolve } = batch[index];
      this.cache.set(JSON.stringify(params), result);
      resolve(result);
    });
    
    this.batchTimer = null;
  }
}

Probar tu Agente

// test/agent.test.js
describe('Agente de Servicio al Cliente', () => {
  let agent;
  
  beforeEach(() => {
    agent = new CustomerServiceAgent();
  });
  
  test('debería calcular precios con precisión', async () => {
    const response = await agent.respond(
      "¿Cuál es el precio de 100 unidades de PRO-001 para un cliente enterprise en EE.UU.?"
    );
    
    expect(response).toContain('precio');
    expect(response).toMatch(/\$[0-9,]+/);
  });
  
  test('debería manejar códigos de producto inválidos', async () => {
    const response = await agent.respond(
      "Precio para producto XYZ"
    );
    
    expect(response).toContain('código de producto válido');
  });
  
  test('debería comparar escenarios cuando se le pida', async () => {
    const response = await agent.respond(
      "¿Debería comprar 10 unidades ahora o esperar a un pedido a granel?"
    );
    
    expect(response).toContain('escenario');
    expect(response).toContain('recomendación');
  });
});

Lista de Verificación para Lanzamiento

  • [ ] Modelos de Excel subidos a SpreadAPI
  • [ ] Mapeos de entrada/salida definidos
  • [ ] Claves API almacenadas de forma segura
  • [ ] Prompt del sistema del agente refinado
  • [ ] Descripciones de herramientas claras y específicas
  • [ ] Manejo de errores implementado
  • [ ] Estrategia de caché en su lugar
  • [ ] Monitoreo y registro configurados
  • [ ] Limitación de velocidad habilitada
  • [ ] Cobertura de pruebas > 80%
  • [ ] Pruebas de carga completadas
  • [ ] Respuestas de respaldo definidas
  • [ ] Documentación actualizada

Próximos Pasos

  1. Empezar Simple: Un modelo de Excel, una herramienta, agente básico
  2. Agregar Inteligencia: Conciencia del contexto, flujos de trabajo multi-paso
  3. Escalar: Múltiples modelos, caché, monitoreo
  4. Optimizar: Ajuste de rendimiento, optimización de costos

¿Listo para construir tu agente de IA? Comienza con SpreadAPI

¿Preguntas? ¿Ejemplos? Escríbenos a hello@airrange.io

P.D. - Cada agente de IA necesita cálculos precisos. Tus modelos de Excel ya los tienen. Es hora de conectar los puntos.

Artículos Relacionados

Explora más guías de API de Excel e integración de IA: