Introduction

Vous utilisez des API d'intelligence artificielle et vous souhaitez comprendre exactement combien vous dépensez en tokens à chaque requête ? Ce tutoriel est fait pour vous. Nous allons apprendre ensemble à créer un système de visualisation de votre consommation de tokens, idéal pour les développeurs débutants qui veulent maîtriser leur budget API.

HolySheep AI propose des tarifs imbattables avec un taux de change de ¥1=$1, soit une économie de plus de 85% par rapport aux providers traditionnels. Leur plateforme offre une latence inférieure à 50ms et accepte WeChat et Alipay pour les paiements. S'inscrire ici et recevez des crédits gratuits pour commencer.

Comprendre les tokens et le système de facturation

Avant de commencer le code, expliquons simplement ce qu'est un token. Un token est une unité de texte que les modèles d'IA traitent. En général, 1000 tokens représentent environ 750 mots en anglais. Chaque requête que vous envoyez à une API génère des tokens d'entrée (votre texte) et des tokens de sortie (la réponse du modèle).

Prix actuels HolySheep AI (2026)

Comme vous pouvez le voir, DeepSeek V3.2 est 35 fois moins cher que Claude Sonnet 4.5 pour des cas d'usage similaires. C'est pourquoi visualiser votre consommation est essentiel pour optimiser vos coûts.

Architecture du système de visualisation

Notre système va utiliser une approche simple en trois couches :

  1. Collecte :拦截 chaque requête API et enregistrer les métriques
  2. Stockage : sauvegarder les données dans un fichier JSON
  3. Visualisation : générer des graphiques avec Chart.js

Implémentation paso a paso

Étape 1 : Configuration initiale du projet

Créez un nouveau dossier pour votre projet et initialisez-le avec npm. Ouvrez votre terminal et exécutez :

mkdir token-visualizer
cd token-visualizer
npm init -y
npm install express chart.js axios

Étape 2 : Serveur Express avec tracking des tokens

Créons le fichier server.js qui va intercepter les appels API et enregistrer la consommation :

const express = require('express');
const axios = require('axios');
const fs = require('fs');
const path = require('path');

const app = express();
app.use(express.json());

const HOLYSHEEP_API_KEY = 'YOUR_HOLYSHEEP_API_KEY';
const BASE_URL = 'https://api.holysheep.ai/v1';

// Fichier de stockage des métriques
const METRICS_FILE = path.join(__dirname, 'metrics.json');

// Charger les métriques existantes ou créer un nouveau fichier
function loadMetrics() {
    if (fs.existsSync(METRICS_FILE)) {
        return JSON.parse(fs.readFileSync(METRICS_FILE, 'utf8'));
    }
    return { requests: [], dailyTotals: {} };
}

function saveMetrics(metrics) {
    fs.writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2));
}

function calculateCost(promptTokens, completionTokens, model) {
    const prices = {
        'deepseek-chat': { input: 0.42, output: 1.12 },
        'gpt-4.1': { input: 8, output: 24 },
        'claude-sonnet-4-5': { input: 15, output: 75 },
        'gemini-2.5-flash': { input: 2.50, output: 10 }
    };
    
    const price = prices[model] || prices['deepseek-chat'];
    const inputCost = (promptTokens / 1000000) * price.input;
    const outputCost = (completionTokens / 1000000) * price.output;
    
    return { inputCost, outputCost, total: inputCost + outputCost };
}

// Endpoint pour envoyer des requêtes à l'API HolySheep
app.post('/api/chat', async (req, res) => {
    const { message, model = 'deepseek-chat' } = req.body;
    const startTime = Date.now();
    
    try {
        const response = await axios.post(${BASE_URL}/chat/completions, {
            model: model,
            messages: [{ role: 'user', content: message }]
        }, {
            headers: {
                'Authorization': Bearer ${HOLYSHEEP_API_KEY},
                'Content-Type': 'application/json'
            }
        });
        
        const endTime = Date.now();
        const latency = endTime - startTime;
        
        const data = response.data;
        const usage = data.usage || {};
        const promptTokens = usage.prompt_tokens || 0;
        const completionTokens = usage.completion_tokens || 0;
        const totalTokens = usage.total_tokens || 0;
        
        const costs = calculateCost(promptTokens, completionTokens, model);
        const today = new Date().toISOString().split('T')[0];
        
        // Enregistrer la métrique
        const metrics = loadMetrics();
        metrics.requests.push({
            timestamp: new Date().toISOString(),
            model,
            promptTokens,
            completionTokens,
            totalTokens,
            latency,
            cost: costs.total,
            responseTime: data.created ? new Date(data.created * 1000).toISOString() : null
        });
        
        // Mettre à jour les totaux quotidiens
        if (!metrics.dailyTotals[today]) {
            metrics.dailyTotals[today] = { tokens: 0, cost: 0, requests: 0 };
        }
        metrics.dailyTotals[today].tokens += totalTokens;
        metrics.dailyTotals[today].cost += costs.total;
        metrics.dailyTotals[today].requests += 1;
        
        saveMetrics(metrics);
        
        res.json({
            success: true,
            response: data.choices?.[0]?.message?.content,
            usage: {
                promptTokens,
                completionTokens,
                totalTokens,
                latency: ${latency}ms,
                cost: $${costs.total.toFixed(6)}
            }
        });
        
    } catch (error) {
        console.error('Erreur API HolySheep:', error.response?.data || error.message);
        res.status(500).json({ 
            success: false, 
            error: error.response?.data?.error?.message || 'Erreur de requête' 
        });
    }
});

// Endpoint pour récupérer les métriques
app.get('/api/metrics', (req, res) => {
    const metrics = loadMetrics();
    res.json(metrics);
});

// Endpoint pour les statistiques agrégées
app.get('/api/stats', (req, res) => {
    const metrics = loadMetrics();
    const totalTokens = metrics.requests.reduce((sum, r) => sum + r.totalTokens, 0);
    const totalCost = metrics.requests.reduce((sum, r) => sum + r.cost, 0);
    const avgLatency = metrics.requests.length > 0 
        ? metrics.requests.reduce((sum, r) => sum + r.latency, 0) / metrics.requests.length 
        : 0;
    
    res.json({
        totalRequests: metrics.requests.length,
        totalTokens,
        totalCost: totalCost.toFixed(6),
        averageLatency: ${avgLatency.toFixed(2)}ms,
        dailyBreakdown: metrics.dailyTotals
    });
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(🚀 Serveur actif sur http://localhost:${PORT});
    console.log(📊 Visualisation disponible sur http://localhost:${PORT}/dashboard.html);
});

Étape 3 : Interface de visualisation HTML

Créons maintenant le fichier dashboard.html avec des graphiques interactifs :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Token Consumption Dashboard - HolySheep AI</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: 'Segoe UI', Tahoma, sans-serif; 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container { max-width: 1200px; margin: 0 auto; }
        h1 { color: white; text-align: center; margin-bottom: 10px; }
        .subtitle { color: rgba(255,255,255,0.8); text-align: center; margin-bottom: 30px; }
        
        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        .stat-card {
            background: white;
            border-radius: 15px;
            padding: 25px;
            text-align: center;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .stat-card h3 { color: #666; font-size: 14px; margin-bottom: 10px; }
        .stat-card .value { font-size: 28px; font-weight: bold; color: #333; }
        .stat-card .value.green { color: #10b981; }
        .stat-card .value.blue { color: #3b82f6; }
        
        .charts-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
            gap: 20px;
        }
        .chart-container {
            background: white;
            border-radius: 15px;
            padding: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .chart-title { font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333; }
        
        .test-section {
            background: white;
            border-radius: 15px;
            padding: 25px;
            margin-top: 30px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
        }
        .test-section h2 { margin-bottom: 15px; color: #333; }
        textarea {
            width: 100%;
            height: 100px;
            padding: 15px;
            border: 2px solid #e5e7eb;
            border-radius: 10px;
            font-size: 14px;
            resize: vertical;
            margin-bottom: 15px;
        }
        .model-select {
            padding: 10px 15px;
            border: 2px solid #e5e7eb;
            border-radius: 10px;
            font-size: 14px;
            margin-right: 10px;
        }
        button {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 12px 30px;
            border-radius: 10px;
            font-size: 16px;
            cursor: pointer;
            transition: transform 0.2s;
        }
        button:hover { transform: scale(1.05); }
        button:disabled { opacity: 0.6; cursor: not-allowed; }
        
        .result {
            margin-top: 20px;
            padding: 20px;
            background: #f3f4f6;
            border-radius: 10px;
            display: none;
        }
        .result.show { display: block; }
        .result pre { 
            background: #1f2937; 
            color: #10b981; 
            padding: 15px; 
            border-radius: 8px;
            overflow-x: auto;
            font-size: 13px;
        }
        .error { background: #fee; color: #c00; }
    </style>
</head>
<body>
    <div class="container">
        <h1>📊 Token Consumption Dashboard</h1>
        <p class="subtitle">Surveillez votre consommation en temps réel avec HolySheep AI</p>
        
        <div class="stats-grid">
            <div class="stat-card">
                <h3>Total des Tokens</h3>
                <div class="value blue" id="totalTokens">0</div>
            </div>
            <div class="stat-card">
                <h3>Coût Total</h3>
                <div class="value green" id="totalCost">$0.00</div>
            </div>
            <div class="stat-card">
                <h3>Nombre de Requêtes</h3>
                <div class="value" id="totalRequests">0</div>
            </div>
            <div class="stat-card">
                <h3>Latence Moyenne</h3>
                <div class="value" id="avgLatency">0ms</div>
            </div>
        </div>
        
        <div class="charts-grid">
            <div class="chart-container">
                <h3 class="chart-title">📈 Tokens par Jour</h3>
                <canvas id="tokensChart"></canvas>
            </div>
            <div class="chart-container">
                <h3 class="chart-title">💰 Coût par Jour</h3>
                <canvas id="costChart"></canvas>
            </div>
            <div class="chart-container">
                <h3 class="chart-title">⏱️ Latence des Requêtes</h3>
                <canvas id="latencyChart"></canvas>
            </div>
            <div class="chart-container">
                <h3 class="chart-title">📊 Répartition par Modèle</h3>
                <canvas id="modelChart"></canvas>
            </div>
        </div>
        
        <div class="test-section">
            <h2>🧪 Tester une Requête</h2>
            <textarea id="testMessage" placeholder="Entrez votre message ici...">Explique-moi ce qu'est un token en IA de manière simple.</textarea>
            <br>
            <select class="model-select" id="modelSelect">
                <option value="deepseek-chat">DeepSeek V3.2 ($0.42/M tok)</option>
                <option value="gemini-2.5-flash">Gemini 2.5 Flash ($2.50/M tok)</option>
                <option value="gpt-4.1">GPT-4.1 ($8/M tok)</option>
                <option value="claude-sonnet-4-5">Claude Sonnet 4.5 ($15/M tok)</option>
            </select>
            <button onclick="sendTestRequest()" id="sendBtn">Envoyer et Tracker</button>
            <div class="result" id="result"></div>
        </div>
    </div>
    
    <script>
        let tokensChart, costChart, latencyChart, modelChart;
        
        async function loadMetrics() {
            try {
                const response = await fetch('/api/metrics');
                const metrics = await response.json();
                updateStats(metrics);
                updateCharts(metrics);
            } catch (error) {
                console.error('Erreur chargement métriques:', error);
            }
        }
        
        function updateStats(metrics) {
            const totalTokens = metrics.requests.reduce((sum, r) => sum + r.totalTokens, 0);
            const totalCost = metrics.requests.reduce((sum, r) => sum + r.cost, 0);
            const avgLatency = metrics.requests.length > 0 
                ? metrics.requests.reduce((sum, r) => sum + r.latency, 0) / metrics.requests.length 
                : 0;
            
            document.getElementById('totalTokens').textContent = totalTokens.toLocaleString();
            document.getElementById('totalCost').textContent = '$' + totalCost.toFixed(4);
            document.getElementById('totalRequests').textContent = metrics.requests.length;
            document.getElementById('avgLatency').textContent = avgLatency.toFixed(0) + 'ms';
        }
        
        function updateCharts(metrics) {
            // Graphique des tokens par jour
            const dailyData = {};
            metrics.requests.forEach(r => {
                const day = r.timestamp.split('T')[0];
                dailyData[day] = (dailyData[day] || 0) + r.totalTokens;
            });
            
            const days = Object.keys(dailyData).sort();
            const tokenValues = days.map(d => dailyData[d]);
            
            if (tokensChart) tokensChart.destroy();
            tokensChart = new Chart(document.getElementById('tokensChart'), {
                type: 'line',
                data: {
                    labels: days,
                    datasets: [{
                        label: 'Tokens',
                        data: tokenValues,
                        borderColor: '#3b82f6',
                        backgroundColor: 'rgba(59, 130, 246, 0.1)',
                        fill: true,
                        tension: 0.4
                    }]
                },
                options: { responsive: true, plugins: { legend: { display: false } } }
            });
            
            // Graphique des coûts par jour
            const dailyCost = {};
            metrics.requests.forEach(r => {
                const day = r.timestamp.split('T')[0];
                dailyCost[day] = (dailyCost[day] || 0) + r.cost;
            });
            
            const costValues = days.map(d => dailyCost[d]);
            
            if (costChart) costChart.destroy();
            costChart = new Chart(document.getElementById('costChart'), {
                type: 'bar',
                data: {
                    labels: days,
                    datasets: [{
                        label: 'Coût ($)',
                        data: costValues,
                        backgroundColor: '#10b981'
                    }]
                },
                options: { responsive: true }
            });
            
            // Graphique de latence
            const last20 = metrics.requests.slice(-20);
            const latencyLabels = last20.map((r, i) => '#' + (metrics.requests.length - last20.length + i + 1));
            const latencyValues = last20.map(r => r.latency);
            
            if (latencyChart) latencyChart.destroy();
            latencyChart = new Chart(document.getElementById('latencyChart'), {
                type: 'line',
                data: {
                    labels: latencyLabels,
                    datasets: [{
                        label: 'Latence (ms)',
                        data: latencyValues,
                        borderColor: '#f59e0b',
                        backgroundColor: 'rgba(245, 158, 11, 0.1)',
                        fill: true,
                        tension: 0.4
                    }]
                },
                options: { 
                    responsive: true, 
                    scales: { y: { beginAtZero: true } },
                    plugins: { legend: { display: false } }
                }
            });
            
            // Graphique des modèles
            const modelStats = {};
            metrics.requests.forEach(r => {
                modelStats[r.model] = (modelStats[r.model] || 0) + r.totalTokens;
            });
            
            if (modelChart) modelChart.destroy();
            modelChart = new Chart(document.getElementById('modelChart'), {
                type: 'doughnut',
                data: {
                    labels: Object.keys(modelStats),
                    datasets: [{
                        data: Object.values(modelStats),
                        backgroundColor: ['#667eea', '#764ba2', '#f59e0b', '#10b981']
                    }]
                },
                options: { responsive: true }
            });
        }
        
        async function sendTestRequest() {
            const message = document.getElementById('testMessage').value;
            const model = document.getElementById('modelSelect').value;
            const btn = document.getElementById('sendBtn');
            const resultDiv = document.getElementById('result');
            
            if (!message.trim()) {
                alert('Veuillez entrer un message');
                return;
            }
            
            btn.disabled = true;
            btn.textContent = 'Envoi en cours...';
            resultDiv.className = 'result show';
            resultDiv.innerHTML = '<p>Chargement...</p>';
            
            try {
                const response = await fetch('/api/chat', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message, model })
                });
                
                const data = await response.json();
                
                if (data.success) {
                    resultDiv.innerHTML = `
                        <h4>✅ Succès</h4>
                        <pre>${JSON.stringify(data.usage, null, 2)}</pre>
                        <p><strong>Réponse</strong>: ${data.response.substring(0, 200)}...</p>
                    `;
                    // Recharger les métriques après une requête réussie
                    setTimeout(loadMetrics, 500);
                } else {
                    resultDiv.className = 'result show error';
                    resultDiv.innerHTML = <h4>❌ Erreur</h4><p>${data.error}</p>;
                }
            } catch (error) {
                resultDiv.className = 'result show error';
                resultDiv.innerHTML = <h4>❌ Erreur</h4><p>${error.message}</p>;
            }
            
            btn.disabled = false;
            btn.textContent = 'Envoyer et Tracker';
        }
        
        // Charger les métriques au démarrage et toutes les 5 secondes
        loadMetrics();
        setInterval(loadMetrics, 5000);
    </script>
</body>
</html>

Étape 4 : Exporter vers un fichier CSV

Pour une analyse plus approfondie avec Excel ou Google Sheets, ajoutons un endpoint d'export :

// Ajouter dans server.js avant app.listen()

// Endpoint pour exporter en CSV
app.get('/api/export/csv', (req, res) => {
    const metrics = loadMetrics();
    
    let csv = 'Timestamp,Model,Prompt Tokens,Completion Tokens,Total Tokens,Latency (ms),Cost ($)\n';
    
    metrics.requests.forEach(r => {
        csv += ${r.timestamp},${r.model},${r.promptTokens},${r.completionTokens},${r.totalTokens},${r.latency},${r.cost.toFixed(6)}\n;
    });
    
    res.setHeader('Content-Type', 'text/csv');
    res.setHeader('Content-Disposition', attachment; filename=tokens_${new Date().toISOString().split('T')[0]}.csv);
    res.send(csv);
});

// Endpoint pour générer un rapport HTML
app.get('/api/report', (req, res) => {
    const metrics = loadMetrics();
    
    // Calculer les statistiques par modèle
    const modelStats = {};
    metrics.requests.forEach(r => {
        if (!modelStats[r.model]) {
            modelStats[r.model] = { requests: 0, tokens: 0, cost: 0 };
        }
        modelStats[r.model].requests++;
        modelStats[r.model].tokens += r.totalTokens;
        modelStats[r.model].cost += r.cost;
    });
    
    const html = `
    <!DOCTYPE html>
    <html>
    <head>
        <title>Rapport de Consommation - HolySheep AI</title>
        <style>
            body { font-family: Arial, sans-serif; padding: 40px; }
            table { border-collapse: collapse; width: 100%; margin-top: 20px; }
            th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
            th { background: #667eea; color: white; }
            tr:nth-child(even) { background: #f9f9f9; }
            h1 { color: #333; }
            .summary { background: #f0f0f0; padding: 20px; border-radius: 10px; margin: 20px 0; }
        </style>
    </head>
    <body>
        <h1>📊 Rapport de Consommation Tokens</h1>
        <p>Généré le: ${new Date().toLocaleString()}</p>
        
        <div class="summary">
            <h2>Récapitulatif</h2>
            <p><strong>Total des requêtes:</strong> ${metrics.requests.length}</p>
            <p><strong>Total des tokens:</strong> ${metrics.requests.reduce((s,r) => s+r.totalTokens, 0).toLocaleString()}</p>
            <p><strong>Coût total:</strong> $${metrics.requests.reduce((s,r) => s+r.cost, 0).toFixed(4)}</p>
        </div>
        
        <h2>Statistiques par Modèle</h2>
        <table>
            <tr>
                <th>Modèle</th>
                <th>Requêtes</th>
                <th>Tokens Totaux</th>
                <th>Coût</th>
            </tr>
            ${Object.entries(modelStats).map(([model, stats]) => `
                <tr>
                    <td>${model}</td>
                    <td>${stats.requests}</td>
                    <td>${stats.tokens.toLocaleString()}</td>
                    <td>$${stats.cost.toFixed(4)}</td>
                </tr>
            `).join('')}
        </table>
    </body>
    </html>
    `;
    
    res.send(html);
});

console.log('📁 Export CSV: http://localhost:3000/api/export/csv');
console.log('📄 Rapport HTML: http://localhost:3000/api/report');

Comment exécuter le projet

  1. Ouvrez votre terminal dans le dossier du projet
  2. Exécutez node server.js
  3. Ouvrez votre navigateur sur http://localhost:3000/dashboard.html
  4. Entrez votre message, sélectionnez un modèle, et cliquez sur "Envoyer et Tracker"
  5. Observez les graphiques se mettre à jour en temps réel

Vous verrez apparaître :

Indications visuelles pour les captures d'écran

Capture 1 — Dashboard principal : Montrez la grille de 4 cartes statistiques en haut de page, avec le gradient violet en arrière-plan.

Capture 2 — Graphique des coûts : Zoomez sur le graphique en barres des coûts quotidiens avec les barres vertes.

Capture 3 — Section de test : Montrez le formulaire avec le texte "Explique-moi ce qu'est un token", le menu déroulant DeepSeek V3.2 sélectionné, et le bouton violet "Envoyer et Tracker".

Capture 4 — Résultat après requête : Affichez le panneau de résultat avec les tokens affichés, montrant par exemple "promptTokens: 25, completionTokens: 150, totalTokens: 175, latency: 45ms, cost: $0.000073".

Erreurs courantes et solutions

Erreur 1 : "401 Unauthorized" — Clé API invalide

// ❌ Erreur : Clé mal définie
const HOLYSHEEP_API_KEY = 'YOUR_HOLYSHEEP_API_KEY';

// ✅ Solution : Vérifier que la clé est correctement définie
//дите переменную HOLYSHEEP_API_KEY из переменной окружения
const HOLYSHEEP_API_KEY = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';

// Dans votre terminal, définissez la variable d'environnement:
// Windows (CMD): set HOLYSHEEP_API_KEY=votre_cle_ici
// Windows (PowerShell): $env:HOLYSHEEP_API_KEY="votre_cle_ici"
// macOS/Linux: export HOLYSHEEP_API_KEY=votre_cle_ici

Erreur 2 : "ECONNREFUSED" — Serveur non démarré

// ❌ Erreur : Le serveur n'est pas accessible
// Error: connect ECONNREFUSED 127.0.0.1:3000

// ✅ Solution : Vérifiez que le serveur est démarré
// 1. Vérifiez qu'aucun autre processus n'utilise le port 3000
// Windows: netstat -ano | findstr :3000
// macOS/Linux: lsof -i :3000

// 2. Si le port est occupé, changez le port dans server.js
const PORT = 3001; // ou un autre port libre

// 3. Redémarrez le serveur
node server.js

// 4. Mettez à jour l'URL dans dashboard.html si nécessaire

Erreur 3 : "metrics.json: No such file or directory"

// ❌ Erreur : Le fichier de métriques n'existe pas
// Error: ENOENT: no such file or directory, open 'metrics.json'

// ✅ Solution : La fonction loadMetrics() crée automatiquement le fichier
// Assurez-vous que le répertoire est accessible en écriture

// Vérifiez les permissions du dossier
// Windows: icacls . /grant Everyone:(OI)(CI)M
// macOS/Linux: chmod 755 .

// OU créez manuellement le fichier
const fs = require('fs');
if (!fs.existsSync('./metrics.json')) {
    fs.writeFileSync('./metrics.json', JSON.stringify({ requests: [], dailyTotals: {} }));
}

Erreur 4 : "CORS policy" — Requêtes bloquées

// ❌ Erreur : Bloqué par la politique CORS
// Access to fetch at 'http://localhost:3000' from origin 'null' 
// has been blocked by CORS policy

// ✅ Solution : Ajoutez le middleware CORS dans server.js
const cors = require('cors');
app.use(cors({
    origin: '*',  // Pour le développement
    // En production, remplacez par:
    // origin: ['https://votre-domaine.com', 'https://www.votre-domaine.com']
}));

// Installez le package si nécessaire
// npm install cors

Erreur 5 : "undefined usage" — Réponse API mal formée

// ❌ Erreur : Les tokens ne sont pas dans la réponse
// usage: undefined

// ✅ Solution : Vérifiez le format de réponse et ajoutez des valeurs par défaut
const usage = data.usage || {};
const promptTokens = usage.prompt_tokens || 0;
const completionTokens = usage.completion_tokens || 0;

// Debug: Affichez la réponse complète pour diagnostic
console.log('Réponse complète:', JSON.stringify(data, null, 2));

// OU implémentez une estimation si le provider ne retourne pas les tokens
// Estimation: ~4 caractères par token en moyenne
const estimatedTokens = Math.ceil(message.length / 4) + Math.ceil(response.length / 4);

Conclusion

Vous disposez maintenant d'un système complet de