From Excel to AI Agent in 30 Minutes
Your Excel spreadsheet has evolved over years. It handles edge cases, implements complex business rules, and embodies deep domain knowledge. Now you want an AI agent to use it.
Most tutorials will tell you to "just export to CSV" or "rebuild in Python." We're going to do something better: give your AI agent direct access to Excel's calculation engine.
What We're Building
A customer service AI agent that can:
- Calculate accurate quotes using your pricing Excel
- Check delivery dates with your logistics model
- Apply discounts based on complex business rules
- Handle edge cases exactly like your team does
All using your existing Excel files. No rebuilding required.
Prerequisites
# You'll need:
npm install langchain @langchain/openai
# or
pip install langchain openai
# And a SpreadAPI account (free tier works)
# Sign up at https://spreadapi.io
Step 1: Prepare Your Excel for AI
Your Excel Structure
PricingModel.xlsx
├── Inputs
│ ├── B2: Product Code
│ ├── B3: Quantity
│ ├── B4: Customer Type
│ └── B5: Region
├── Calculations (Hidden from AI)
│ ├── Complex VLOOKUP formulas
│ ├── Discount matrices
│ └── Business rules
└── Outputs
├── E10: Base Price
├── E11: Discount Amount
├── E12: Final Price
└── E13: Delivery Date
Upload to SpreadAPI
- Log into SpreadAPI Dashboard
- Create new service called "pricing-model"
- Upload your Excel
- Define interface:
{
"inputs": {
"productCode": "B2",
"quantity": "B3",
"customerType": "B4",
"region": "B5"
},
"outputs": {
"basePrice": "E10",
"discount": "E11",
"finalPrice": "E12",
"deliveryDate": "E13"
}
}
Step 2: Create the AI Agent
Basic Agent with Function Calling
import { ChatOpenAI } from '@langchain/openai';
import { SpreadAPITool } from './spreadapi-tool';
// Define the Excel calculation tool
const pricingTool = {
name: "calculate_pricing",
description: "Calculate accurate pricing using the company pricing model. Use this whenever you need to quote prices or check discounts.",
parameters: {
type: "object",
properties: {
productCode: {
type: "string",
description: "Product code (e.g., 'PRO-001')"
},
quantity: {
type: "number",
description: "Number of units"
},
customerType: {
type: "string",
enum: ["standard", "premium", "enterprise"],
description: "Type of customer account"
},
region: {
type: "string",
enum: ["US", "EU", "APAC"],
description: "Customer's region"
}
},
required: ["productCode", "quantity", "customerType", "region"]
},
execute: async (params) => {
// Call 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;
}
};
// Create the AI agent
const model = new ChatOpenAI({
modelName: "gpt-4",
temperature: 0
});
const tools = [pricingTool];
const modelWithTools = model.bind({ tools });
Implementing the Agent Logic
class CustomerServiceAgent {
constructor(model, tools) {
this.model = model;
this.tools = tools;
this.conversation = [];
}
async respond(userMessage) {
// Add user message to conversation
this.conversation.push({
role: 'user',
content: userMessage
});
// Get AI response with potential tool calls
const response = await this.model.invoke(this.conversation);
// Handle tool calls
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) {
// Execute the Excel calculation
const result = await tool.execute(toolCall.arguments);
// Add tool result to conversation
this.conversation.push({
role: 'tool',
content: JSON.stringify(result),
tool_call_id: toolCall.id
});
}
}
// Get final response after tool execution
const finalResponse = await this.model.invoke(this.conversation);
this.conversation.push({
role: 'assistant',
content: finalResponse.content
});
return finalResponse.content;
}
// No tool calls needed
this.conversation.push({
role: 'assistant',
content: response.content
});
return response.content;
}
}
Step 3: Production-Ready Patterns
Pattern 1: Multi-Tool Agent
// Add multiple Excel-based tools
const tools = [
{
name: "calculate_pricing",
description: "Calculate product pricing and discounts",
spreadapiService: "pricing-model",
execute: spreadapiExecutor("pricing-model")
},
{
name: "check_inventory",
description: "Check product availability and lead times",
spreadapiService: "inventory-tracker",
execute: spreadapiExecutor("inventory-tracker")
},
{
name: "calculate_shipping",
description: "Calculate shipping costs and delivery dates",
spreadapiService: "logistics-calculator",
execute: spreadapiExecutor("logistics-calculator")
}
];
// Helper function for SpreadAPI execution
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(`Excel calculation failed: ${response.statusText}`);
}
const result = await response.json();
return result.outputs;
};
}
Pattern 2: Context-Aware Agent
class ContextAwareAgent {
constructor() {
this.customerContext = {};
this.calculationCache = new Map();
}
async handleQuery(query, customerId) {
// Load customer context
if (!this.customerContext[customerId]) {
this.customerContext[customerId] = await this.loadCustomerData(customerId);
}
const context = this.customerContext[customerId];
// Enhance prompt with context
const enhancedPrompt = `
Customer Information:
- Type: ${context.customerType}
- Region: ${context.region}
- Purchase History: ${context.totalPurchases} orders
User Query: ${query}
Instructions:
- Use the calculate_pricing tool for any price quotes
- Apply appropriate customer type automatically
- Consider their region for shipping calculations
`;
return await this.respond(enhancedPrompt);
}
async loadCustomerData(customerId) {
// Load from your database
return {
customerType: 'enterprise',
region: 'US',
totalPurchases: 47
};
}
}
Pattern 3: Validation and Error Handling
class RobustAgent {
async executeToolSafely(tool, params) {
try {
// Validate inputs before sending to Excel
const validation = this.validateInputs(tool.name, params);
if (!validation.valid) {
return {
error: `Invalid input: ${validation.message}`,
suggestion: validation.suggestion
};
}
// Check cache first
const cacheKey = `${tool.name}:${JSON.stringify(params)}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Execute with timeout
const result = await Promise.race([
tool.execute(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Calculation timeout')), 5000)
)
]);
// Cache successful results
this.cache.set(cacheKey, result);
// Validate output
if (result.finalPrice < 0) {
return {
error: 'Invalid calculation result',
suggestion: 'Please check product code and quantity'
};
}
return result;
} catch (error) {
console.error('Tool execution failed:', error);
// Fallback response
return {
error: 'Unable to calculate at this time',
suggestion: 'Please try again or contact support',
reference: error.message
};
}
}
validateInputs(toolName, params) {
if (toolName === 'calculate_pricing') {
if (params.quantity < 1) {
return {
valid: false,
message: 'Quantity must be at least 1',
suggestion: 'Please specify a valid quantity'
};
}
if (!params.productCode.match(/^[A-Z]{3}-\d{3}$/)) {
return {
valid: false,
message: 'Invalid product code format',
suggestion: 'Product codes should be like ABC-123'
};
}
}
return { valid: true };
}
}
Step 4: Advanced Agent Capabilities
Capability 1: Multi-Step Calculations
const complexWorkflowTool = {
name: "quote_with_options",
description: "Generate a complete quote with multiple product options",
execute: async (params) => {
const { products, customerType, region } = params;
// Calculate pricing for each product
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
};
})
);
// Calculate bundle discount if applicable
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 };
}
};
Capability 2: Explanations and Reasoning
class ExplainableAgent {
async respondWithExplanation(query) {
const response = await this.model.invoke([
{
role: 'system',
content: `You are a helpful customer service agent.
When using pricing tools, always explain:
1. What factors affected the price
2. Any discounts applied
3. Why this is the best option for the customer`
},
{
role: 'user',
content: query
}
]);
// Process tool calls and add explanations
if (response.tool_calls) {
const explanations = [];
for (const toolCall of response.tool_calls) {
const result = await this.executeTool(toolCall);
// Generate explanation based on results
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: `
Base price: $${result.basePrice}
${discount > 0 ? `Discount applied: $${discount} (${discountPercent}%)` : 'No discount applicable'}
Final price: $${result.finalPrice}
Delivery by: ${result.deliveryDate}
`
});
}
}
// Get final response with explanations
const finalResponse = await this.model.invoke([
...this.conversation,
{
role: 'system',
content: `Include these calculation details in your response: ${JSON.stringify(explanations)}`
}
]);
return finalResponse.content;
}
return response.content;
}
}
Capability 3: Scenario Comparison
const scenarioTool = {
name: "compare_scenarios",
description: "Compare different purchase scenarios to find the best option",
execute: async (params) => {
const scenarios = [
{
name: "Single Purchase",
params: {
quantity: params.quantity,
customerType: params.customerType
}
},
{
name: "Bulk Purchase",
params: {
quantity: params.quantity * 3,
customerType: params.customerType
}
},
{
name: "Annual Contract",
params: {
quantity: params.quantity * 12,
customerType: 'enterprise' // Automatic upgrade
}
}
];
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
};
})
);
// Find best option
const bestOption = results.reduce((best, current) =>
current.unitPrice < best.unitPrice ? current : best
);
return {
scenarios: results,
recommendation: bestOption,
potentialSavings: results[0].totalPrice - bestOption.totalPrice
};
}
};
Step 5: Deployment and Monitoring
Production Configuration
// 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 minutes
maxSize: 1000
},
monitoring: {
logLevel: process.env.LOG_LEVEL || 'info',
metricsEnabled: true,
tracingEnabled: process.env.NODE_ENV === 'production'
}
};
Monitoring and Analytics
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}] Processing query:`, query);
const response = await this.agent.respond(query);
const duration = Date.now() - startTime;
this.metrics.responseTime.push(duration);
this.metrics.totalRequests++;
console.log(`[${requestId}] Completed in ${duration}ms`);
// Send to analytics
await this.sendAnalytics({
requestId,
duration,
toolsUsed: this.agent.lastToolCalls,
success: true
});
return response;
} catch (error) {
const errorType = error.name || 'Unknown';
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
};
}
}
Common Pitfalls and Solutions
Pitfall 1: Overloading the Agent
// ❌ Bad: Giving agent too much freedom
const badPrompt = "Help the customer with anything they need";
// ✅ Good: Clear boundaries and capabilities
const goodPrompt = `You are a customer service agent specializing in:
1. Product pricing (use calculate_pricing tool)
2. Inventory availability (use check_inventory tool)
3. Shipping estimates (use calculate_shipping tool)
For other requests, politely explain what you can help with.`;
Pitfall 2: Not Handling Excel Errors
// ✅ Robust error handling
const toolWithErrorHandling = {
execute: async (params) => {
try {
const result = await spreadapiCall(params);
// Validate Excel results
if (result.outputs.error) {
return {
success: false,
error: 'Calculation error in Excel',
details: result.outputs.error,
suggestion: 'Please verify product code and try again'
};
}
return { success: true, ...result.outputs };
} catch (error) {
if (error.status === 422) {
return {
success: false,
error: 'Invalid input parameters',
suggestion: 'Please check your product code format'
};
}
throw error; // Re-throw unexpected errors
}
}
};
Pitfall 3: Ignoring Performance
// ✅ Performance optimization
class OptimizedAgent {
constructor() {
this.cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 5 });
this.batchQueue = [];
this.batchTimer = null;
}
async calculatePricing(params) {
// Check cache first
const cacheKey = JSON.stringify(params);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Batch similar requests
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); // Process up to 50 at once
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;
}
}
Testing Your Agent
// test/agent.test.js
describe('Customer Service Agent', () => {
let agent;
beforeEach(() => {
agent = new CustomerServiceAgent();
});
test('should calculate pricing accurately', async () => {
const response = await agent.respond(
"What's the price for 100 units of PRO-001 for an enterprise customer in the US?"
);
expect(response).toContain('price');
expect(response).toMatch(/\$[0-9,]+/);
});
test('should handle invalid product codes', async () => {
const response = await agent.respond(
"Price for product XYZ"
);
expect(response).toContain('valid product code');
});
test('should compare scenarios when asked', async () => {
const response = await agent.respond(
"Should I buy 10 units now or wait for a bulk order?"
);
expect(response).toContain('scenario');
expect(response).toContain('recommendation');
});
});
Go Live Checklist
- [ ] Excel models uploaded to SpreadAPI
- [ ] Input/output mappings defined
- [ ] API keys securely stored
- [ ] Agent system prompt refined
- [ ] Tool descriptions clear and specific
- [ ] Error handling implemented
- [ ] Caching strategy in place
- [ ] Monitoring and logging configured
- [ ] Rate limiting enabled
- [ ] Test coverage > 80%
- [ ] Load testing completed
- [ ] Fallback responses defined
- [ ] Documentation updated
Next Steps
- Start Simple: One Excel model, one tool, basic agent
- Add Intelligence: Context awareness, multi-step workflows
- Scale Up: Multiple models, caching, monitoring
- Optimize: Performance tuning, cost optimization
Ready to build your AI agent? Get started with SpreadAPI
Questions? Examples? Email us at hello@airrange.io
P.S. - Every AI agent needs accurate calculations. Your Excel models already have them. It's time to connect the dots.
Related Articles
Explore more Excel API and AI integration guides: