D'Excel à Agent IA en 30 Minutes
Votre feuille de calcul Excel a évolué pendant des années. Elle gère les cas particuliers, implémente des règles métier complexes et incarne une connaissance approfondie du domaine. Maintenant, vous voulez qu'un agent IA l'utilise.
La plupart des tutoriels vous diront de "simplement exporter en CSV" ou "reconstruire en Python." Nous allons faire quelque chose de mieux : donner à votre agent IA un accès direct au moteur de calcul d'Excel.
Ce que Nous Construisons
Un agent IA de service client qui peut :
- Calculer des devis précis en utilisant votre Excel de tarification
- Vérifier les dates de livraison avec votre modèle logistique
- Appliquer des remises basées sur des règles métier complexes
- Gérer les cas particuliers exactement comme votre équipe le fait
Tout en utilisant vos fichiers Excel existants. Aucune reconstruction requise.
Prérequis
# Vous aurez besoin de :
npm install langchain @langchain/openai
# ou
pip install langchain openai
# Et un compte SpreadAPI (le niveau gratuit fonctionne)
# Inscrivez-vous sur https://spreadapi.ioÉtape 1 : Préparer votre Excel pour l'IA
Votre Structure Excel
PricingModel.xlsx
├── Entrées
│ ├── B2: Code Produit
│ ├── B3: Quantité
│ ├── B4: Type de Client
│ └── B5: Région
├── Calculs (Cachés de l'IA)
│ ├── Formules VLOOKUP complexes
│ ├── Matrices de remises
│ └── Règles métier
└── Sorties
├── E10: Prix de Base
├── E11: Montant de Remise
├── E12: Prix Final
└── E13: Date de LivraisonTéléchargement vers SpreadAPI
- Se connecter au Tableau de Bord SpreadAPI
- Créer un nouveau service appelé "pricing-model"
- Télécharger votre Excel
- Définir l'interface :
{
"inputs": {
"productCode": "B2",
"quantity": "B3",
"customerType": "B4",
"region": "B5"
},
"outputs": {
"basePrice": "E10",
"discount": "E11",
"finalPrice": "E12",
"deliveryDate": "E13"
}
}Étape 2 : Créer l'Agent IA
Agent de Base avec Function Calling
import { ChatOpenAI } from '@langchain/openai';
import { SpreadAPITool } from './spreadapi-tool';
// Définir l'outil de calcul Excel
const pricingTool = {
name: "calculate_pricing",
description: "Calculer des prix précis en utilisant le modèle de tarification de l'entreprise. Utilisez ceci chaque fois que vous devez proposer des prix ou vérifier des remises.",
parameters: {
type: "object",
properties: {
productCode: {
type: "string",
description: "Code produit (ex. 'PRO-001')"
},
quantity: {
type: "number",
description: "Nombre d'unités"
},
customerType: {
type: "string",
enum: ["standard", "premium", "enterprise"],
description: "Type de compte client"
},
region: {
type: "string",
enum: ["US", "EU", "APAC"],
description: "Région du client"
}
},
required: ["productCode", "quantity", "customerType", "region"]
},
execute: async (params) => {
// Appeler 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;
}
};
// Créer l'agent IA
const model = new ChatOpenAI({
modelName: "gpt-4",
temperature: 0
});
const tools = [pricingTool];
const modelWithTools = model.bind({ tools });Implémenter la Logique de l'Agent
class CustomerServiceAgent {
constructor(model, tools) {
this.model = model;
this.tools = tools;
this.conversation = [];
}
async respond(userMessage) {
// Ajouter le message utilisateur à la conversation
this.conversation.push({
role: 'user',
content: userMessage
});
// Obtenir la réponse IA avec des appels d'outils potentiels
const response = await this.model.invoke(this.conversation);
// Gérer les appels d'outils
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) {
// Exécuter le calcul Excel
const result = await tool.execute(toolCall.arguments);
// Ajouter le résultat de l'outil à la conversation
this.conversation.push({
role: 'tool',
content: JSON.stringify(result),
tool_call_id: toolCall.id
});
}
}
// Obtenir la réponse finale après l'exécution des outils
const finalResponse = await this.model.invoke(this.conversation);
this.conversation.push({
role: 'assistant',
content: finalResponse.content
});
return finalResponse.content;
}
// Aucun appel d'outil nécessaire
this.conversation.push({
role: 'assistant',
content: response.content
});
return response.content;
}
}Étape 3 : Patterns Prêts pour la Production
Pattern 1 : Agent Multi-Outils
// Ajouter plusieurs outils basés sur Excel
const tools = [
{
name: "calculate_pricing",
description: "Calculer les prix des produits et les remises",
spreadapiService: "pricing-model",
execute: spreadapiExecutor("pricing-model")
},
{
name: "check_inventory",
description: "Vérifier la disponibilité des produits et les délais de livraison",
spreadapiService: "inventory-tracker",
execute: spreadapiExecutor("inventory-tracker")
},
{
name: "calculate_shipping",
description: "Calculer les coûts d'expédition et les dates de livraison",
spreadapiService: "logistics-calculator",
execute: spreadapiExecutor("logistics-calculator")
}
];
// Fonction d'aide pour l'exécution 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(`Échec du calcul Excel : ${response.statusText}`);
}
const result = await response.json();
return result.outputs;
};
}Pattern 2 : Agent Conscient du Contexte
class ContextAwareAgent {
constructor() {
this.customerContext = {};
this.calculationCache = new Map();
}
async handleQuery(query, customerId) {
// Charger le contexte client
if (!this.customerContext[customerId]) {
this.customerContext[customerId] = await this.loadCustomerData(customerId);
}
const context = this.customerContext[customerId];
// Améliorer le prompt avec le contexte
const enhancedPrompt = `
Informations Client :
- Type : ${context.customerType}
- Région : ${context.region}
- Historique d'achat : ${context.totalPurchases} commandes
Requête Utilisateur : ${query}
Instructions :
- Utilisez l'outil calculate_pricing pour tous les devis de prix
- Appliquez automatiquement le type de client approprié
- Considérez leur région pour les calculs d'expédition
`;
return await this.respond(enhancedPrompt);
}
async loadCustomerData(customerId) {
// Charger depuis votre base de données
return {
customerType: 'enterprise',
region: 'US',
totalPurchases: 47
};
}
}Pattern 3 : Validation et Gestion d'Erreurs
class RobustAgent {
async executeToolSafely(tool, params) {
try {
// Valider les entrées avant l'envoi à Excel
const validation = this.validateInputs(tool.name, params);
if (!validation.valid) {
return {
error: `Entrée invalide : ${validation.message}`,
suggestion: validation.suggestion
};
}
// Vérifier le cache d'abord
const cacheKey = `${tool.name}:${JSON.stringify(params)}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Exécuter avec timeout
const result = await Promise.race([
tool.execute(params),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout de calcul')), 5000)
)
]);
// Mettre en cache les résultats réussis
this.cache.set(cacheKey, result);
// Valider la sortie
if (result.finalPrice < 0) {
return {
error: 'Résultat de calcul invalide',
suggestion: 'Veuillez vérifier le code produit et la quantité'
};
}
return result;
} catch (error) {
console.error('Échec de l\'exécution de l\'outil:', error);
// Réponse de secours
return {
error: 'Impossible de calculer en ce moment',
suggestion: 'Veuillez réessayer ou contacter le support',
reference: error.message
};
}
}
validateInputs(toolName, params) {
if (toolName === 'calculate_pricing') {
if (params.quantity < 1) {
return {
valid: false,
message: 'La quantité doit être au moins 1',
suggestion: 'Veuillez spécifier une quantité valide'
};
}
if (!params.productCode.match(/^[A-Z]{3}-\d{3}$/)) {
return {
valid: false,
message: 'Format de code produit invalide',
suggestion: 'Les codes produit doivent être comme ABC-123'
};
}
}
return { valid: true };
}
}Étape 4 : Capacités Avancées de l'Agent
Capacité 1 : Calculs Multi-Étapes
const complexWorkflowTool = {
name: "quote_with_options",
description: "Générer un devis complet avec plusieurs options de produits",
execute: async (params) => {
const { products, customerType, region } = params;
// Calculer les prix pour chaque produit
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
};
})
);
// Calculer la remise groupée si 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 };
}
};Capacité 2 : Explications et Raisonnement
class ExplainableAgent {
async respondWithExplanation(query) {
const response = await this.model.invoke([
{
role: 'system',
content: `Vous êtes un agent de service client utile.
Lors de l'utilisation d'outils de tarification, expliquez toujours :
1. Quels facteurs ont affecté le prix
2. Quelles remises ont été appliquées
3. Pourquoi c'est la meilleure option pour le client`
},
{
role: 'user',
content: query
}
]);
// Traiter les appels d'outils et ajouter des explications
if (response.tool_calls) {
const explanations = [];
for (const toolCall of response.tool_calls) {
const result = await this.executeTool(toolCall);
// Générer une explication basée sur les résultats
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: `
Prix de base : ${result.basePrice}€
${discount > 0 ? `Remise appliquée : ${discount}€ (${discountPercent}%)` : 'Aucune remise applicable'}
Prix final : ${result.finalPrice}€
Livraison avant le : ${result.deliveryDate}
`
});
}
}
// Obtenir la réponse finale avec explications
const finalResponse = await this.model.invoke([
...this.conversation,
{
role: 'system',
content: `Incluez ces détails de calcul dans votre réponse : ${JSON.stringify(explanations)}`
}
]);
return finalResponse.content;
}
return response.content;
}
}Capacité 3 : Comparaison de Scénarios
const scenarioTool = {
name: "compare_scenarios",
description: "Comparer différents scénarios d'achat pour trouver la meilleure option",
execute: async (params) => {
const scenarios = [
{
name: "Achat Individuel",
params: {
quantity: params.quantity,
customerType: params.customerType
}
},
{
name: "Achat en Gros",
params: {
quantity: params.quantity * 3,
customerType: params.customerType
}
},
{
name: "Contrat Annuel",
params: {
quantity: params.quantity * 12,
customerType: 'enterprise' // Mise à niveau automatique
}
}
];
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
};
})
);
// Trouver la meilleure option
const bestOption = results.reduce((best, current) =>
current.unitPrice < best.unitPrice ? current : best
);
return {
scenarios: results,
recommendation: bestOption,
potentialSavings: results[0].totalPrice - bestOption.totalPrice
};
}
};Étape 5 : Déploiement et Surveillance
Configuration de Production
// 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'
}
};Surveillance et Analytiques
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}] Traitement de la requête :`, query);
const response = await this.agent.respond(query);
const duration = Date.now() - startTime;
this.metrics.responseTime.push(duration);
this.metrics.totalRequests++;
console.log(`[${requestId}] Terminé en ${duration}ms`);
// Envoyer aux analytiques
await this.sendAnalytics({
requestId,
duration,
toolsUsed: this.agent.lastToolCalls,
success: true
});
return response;
} catch (error) {
const errorType = error.name || 'Inconnu';
this.metrics.errors[errorType] = (this.metrics.errors[errorType] || 0) + 1;
console.error(`[${requestId}] Erreur :`, 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
};
}
}Pièges Courants et Solutions
Piège 1 : Surcharger l'Agent
// Mauvais : Donner trop de liberté à l'agent
const badPrompt = "Aidez le client avec tout ce dont il a besoin";
// Bon : Limites et capacités claires
const goodPrompt = `Vous êtes un agent de service client spécialisé dans :
1. La tarification des produits (utilisez l'outil calculate_pricing)
2. La disponibilité des stocks (utilisez l'outil check_inventory)
3. Les estimations d'expédition (utilisez l'outil calculate_shipping)
Pour les autres demandes, expliquez poliment ce avec quoi vous pouvez aider.`;Piège 2 : Ne Pas Gérer les Erreurs Excel
// Gestion d'erreurs robuste
const toolWithErrorHandling = {
execute: async (params) => {
try {
const result = await spreadapiCall(params);
// Valider les résultats Excel
if (result.outputs.error) {
return {
success: false,
error: 'Erreur de calcul dans Excel',
details: result.outputs.error,
suggestion: 'Veuillez vérifier le code produit et réessayer'
};
}
return { success: true, ...result.outputs };
} catch (error) {
if (error.status === 422) {
return {
success: false,
error: 'Paramètres d\'entrée invalides',
suggestion: 'Veuillez vérifier le format de votre code produit'
};
}
throw error; // Re-lancer les erreurs inattendues
}
}
};Piège 3 : Ignorer les Performances
// Optimisation des performances
class OptimizedAgent {
constructor() {
this.cache = new LRUCache({ max: 500, ttl: 1000 * 60 * 5 });
this.batchQueue = [];
this.batchTimer = null;
}
async calculatePricing(params) {
// Vérifier le cache d'abord
const cacheKey = JSON.stringify(params);
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
// Grouper les demandes similaires
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); // Traiter jusqu'à 50 à la fois
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;
}
}Tester votre Agent
// test/agent.test.js
describe('Agent de Service Client', () => {
let agent;
beforeEach(() => {
agent = new CustomerServiceAgent();
});
test('devrait calculer les prix avec précision', async () => {
const response = await agent.respond(
"Quel est le prix pour 100 unités de PRO-001 pour un client entreprise aux États-Unis ?"
);
expect(response).toContain('prix');
expect(response).toMatch(/[0-9,]+€/);
});
test('devrait gérer les codes produit invalides', async () => {
const response = await agent.respond(
"Prix pour le produit XYZ"
);
expect(response).toContain('code produit valide');
});
test('devrait comparer les scénarios quand demandé', async () => {
const response = await agent.respond(
"Devrais-je acheter 10 unités maintenant ou attendre une commande en gros ?"
);
expect(response).toContain('scénario');
expect(response).toContain('recommandation');
});
});Liste de Contrôle de Mise en Production
- [ ] Modèles Excel téléchargés vers SpreadAPI
- [ ] Mappages entrée/sortie définis
- [ ] Clés API stockées de manière sécurisée
- [ ] Prompt système de l'agent affiné
- [ ] Descriptions d'outils claires et spécifiques
- [ ] Gestion d'erreurs implémentée
- [ ] Stratégie de mise en cache en place
- [ ] Surveillance et journalisation configurées
- [ ] Limitation de débit activée
- [ ] Couverture de test > 80%
- [ ] Tests de charge terminés
- [ ] Réponses de secours définies
- [ ] Documentation mise à jour
Prochaines Étapes
- Commencer Simple : Un modèle Excel, un outil, agent basique
- Ajouter de l'Intelligence : Conscience du contexte, workflows multi-étapes
- Monter en Échelle : Multiples modèles, mise en cache, surveillance
- Optimiser : Réglage des performances, optimisation des coûts
Prêt à construire votre agent IA ? Commencez avec SpreadAPI
Questions ? Exemples ? Écrivez-nous à hello@airrange.io
Articles Connexes
Explorez plus de guides d'API Excel et d'intégration IA :