En tant qu'auteur technique chez HolySheep AI, j'ai migré des dizaines d'architectures vers des solutions résilientes et économiques. Aujourd'hui, je partage une étude de cas complète qui démontre comment une optimisation intelligente des appels API peut réduire vos factures de 84% tout en améliorant les performances.
Étude de Cas : Migration d'une Scale-up SaaS Parisienne
Contexte Métier
Pendant trois ans, j'ai accompagné une scale-up SaaS parisienne spécialisée dans l'analyse prédictive pour le commerce de détail. Leur plateforme traite environ 15 millions de requêtes mensuelles vers des API d'IA pour alimenter des modèles de recommandation en temps réel. L'équipe technique, basée à Station F, comptait 8 développeurs backend.
Stack technique initial :
- Infrastructure AWS (eu-west-1)
- Node.js 20 avec TypeScript
- Base de données PostgreSQL 15
- Cache Redis 7
- Microservices Kubernetes sur EKS
Les Douleurs du Fournisseur Précédent
La facture mensuelle explosait littéralement. Voici les problèmes critiques identifiés :
- Coût prohibitif : $4 200/mois pour les appels API, soit 35% du budget infrastructure total
- Latence instable : moyenne de 420ms avec des pics à 2,3 secondes en période de forte charge
- Rate limiting agressif : 500 req/min limit atteint quotidiennement, causant des erreurs 429 en production
- Gestion des retries inexistante : les échecs temporaires étaient traités comme des erreurs définitives
- Pas de fallback intelligent : un seul modèle utilisé, sans possibilité de dégradation gracieuse
Le directeur technique de l'époque m'a confié : « Nous dépensions plus en API qu'en salaires développeurs. C'était intenable à long terme. »
Pourquoi HolySheep AI
Après analyse comparative, l'équipe a migré vers HolySheep AI pour plusieurs raisons déterminantes :
- Économie de 85% : Le taux de change avantageux (¥1 = $1) permet d'accéder aux mêmes modèles à une fraction du prix
- Latence médiane <50ms : infrastructure optimisée pour la performance
- Paiement local : WeChat Pay et Alipay disponibles, simplifies les transactions pour les équipes avec des contacts en Asie
- Crédits gratuits : 500 crédits offerts à l'inscription pour tester en conditions réelles
- Diversité des modèles : Du plus économique (DeepSeek V3.2 à $0.42/MTok) au plus puissant (Claude Sonnet 4.5 à $15/MTok)
Étapes Concrètes de Migration
Étape 1 : Configuration Initiale
La première étape consistait à configurer le nouveau provider avec une migration progressive. J'ai personnellement supervisé le changement de base_url et la rotation sécurisée des clés API.
// config/api-client.ts - Configuration HolySheep AI
import axios, { AxiosInstance, AxiosError } from 'axios';
// Ancienne configuration (À SUPPRIMER)
// const OLD_BASE_URL = 'https://api.openai.com/v1';
// Nouvelle configuration HolySheep AI
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
const HOLYSHEEP_API_KEY = process.env.HOLYSHEEP_API_KEY; // YOUR_HOLYSHEEP_API_KEY
interface RateLimitConfig {
maxRequestsPerMinute: number;
maxRequestsPerSecond: number;
backoffMs: number;
maxRetries: number;
}
interface CircuitBreakerState {
failures: number;
lastFailure: number;
state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
}
class HolySheepAIClient {
private client: AxiosInstance;
private rateLimiter: RateLimitConfig;
private circuitBreaker: CircuitBreakerState;
private requestQueue: Array<() => Promise<any>> = [];
private processingQueue: boolean = false;
constructor() {
this.client = axios.create({
baseURL: HOLYSHEEP_BASE_URL,
timeout: 30000,
headers: {
'Authorization': Bearer ${HOLYSHEEP_API_KEY},
'Content-Type': 'application/json',
},
});
this.rateLimiter = {
maxRequestsPerMinute: 3000,
maxRequestsPerSecond: 50,
backoffMs: 1000,
maxRetries: 3,
};
this.circuitBreaker = {
failures: 0,
lastFailure: 0,
state: 'CLOSED',
};
this.setupInterceptors();
}
private setupInterceptors(): void {
// Intercepteur de réponse
this.client.interceptors.response.use(
(response) => {
this.circuitBreaker.failures = 0;
this.circuitBreaker.state = 'CLOSED';
return response;
},
async (error: AxiosError) => {
const status = error.response?.status;
// Gestion du rate limiting (429)
if (status === 429) {
console.log('[HolySheep] Rate limit atteint - application du backoff');
await this.exponentialBackoff(this.rateLimiter.backoffMs);
return this.retryRequest(error.config);
}
// Erreurs serveur temporaires (5xx)
if (status && status >= 500) {
this.circuitBreaker.failures++;
if (this.circuitBreaker.failures >= 5) {
this.circuitBreaker.state = 'OPEN';
console.log('[HolySheep] Circuit breaker OUVERT - pause de 60s');
}
return this.retryRequest(error.config);
}
throw error;
}
);
}
private async exponentialBackoff(baseDelay: number): Promise<void> {
const delay = baseDelay * Math.pow(2, Math.random());
return new Promise(resolve => setTimeout(resolve, delay));
}
private async retryRequest(config: any): Promise<any> {
let attempt = 0;
while (attempt < this.rateLimiter.maxRetries) {
try {
await new Promise(resolve => setTimeout(resolve, this.rateLimiter.backoffMs * Math.pow(2, attempt)));
return await this.client.request(config);
} catch (error) {
attempt++;
console.log([HolySheep] Retry ${attempt}/${this.rateLimiter.maxRetries});
}
}
throw new Error(Échec après ${this.rateLimiter.maxRetries} tentatives);
}
public async completeRequest(prompt: string, model: string = 'gpt-4.1'): Promise<string> {
// Vérification du circuit breaker
if (this.circuitBreaker.state === 'OPEN') {
const timeSinceFailure = Date.now() - this.circuitBreaker.lastFailure;
if (timeSinceFailure < 60000) {
throw new Error('Circuit breaker ouvert - utilisez le fallback');
}
this.circuitBreaker.state = 'HALF_OPEN';
}
try {
const response = await this.client.post('/chat/completions', {
model: model,
messages: [{ role: 'user', content: prompt }],
temperature: 0.7,
max_tokens: 1000,
});
this.circuitBreaker.lastFailure = Date.now();
return response.data.choices[0].message.content;
} catch (error) {
this.circuitBreaker.lastFailure = Date.now();
throw error;
}
}
}
export const holySheepClient = new HolySheepAIClient();
console.log('[HolySheep] Client initialisé avec succès');
Étape 2 : Stratégie de Déploiement Canari
Pour minimiser les risques, j'ai recommandé un déploiement canari : 5% du trafic initialement, puis augmentation progressive.
// services/canary-controller.ts - Déploiement progressif
import { holySheepClient } from '../config/api-client';
interface CanaryConfig {
percentage: number;
targetLatencyMs: number;
targetErrorRate: number;
}
interface TrafficMetrics {
totalRequests: number;
successfulRequests: number;
failedRequests: number;
averageLatency: number;
totalCost: number;
}
class CanaryController {
private canaryPercentage: number = 5; // Départ à 5%
private metrics: TrafficMetrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageLatency: 0,
totalCost: 0,
};
// Rotation des clés API (fallback)
private apiKeys: string[] = [
process.env.HOLYSHEEP_API_KEY_1 || 'YOUR_HOLYSHEEP_API_KEY',
process.env.HOLYSHEEP_API_KEY_2 || 'YOUR_HOLYSHEEP_API_KEY',
];
private currentKeyIndex: number = 0;
public shouldUseNewProvider(): boolean {
return Math.random() * 100 < this.canaryPercentage;
}
public async processRequest(prompt: string, userId: string): Promise<string> {
const startTime = Date.now();
try {
// Logique canari
if (this.shouldUseNewProvider()) {
console.log([Canary] Requête ${userId} vers HolySheep AI);
const result = await this.callHolySheep(prompt);
this.recordSuccess(Date.now() - startTime, this.estimateCost(prompt));
return result;
} else {
console.log([Canary] Requête ${userId} vers provider existant);
const result = await this.callLegacyProvider(prompt);
this.recordSuccess(Date.now() - startTime, this.estimateCostLegacy(prompt));
return result;
}
} catch (error) {
this.recordFailure();
throw error;
}
}
private async callHolySheep(prompt: string): Promise<string> {
try {
// Utilisation du client HolySheep
return await holySheepClient.completeRequest(prompt, 'gpt-4.1');
} catch (error) {
console.log('[Canary] Échec HolySheep - basculement à la clé suivante');
this.rotateApiKey();
throw error;
}
}
private rotateApiKey(): void {
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length;
console.log([Canary] Rotation vers la clé API #${this.currentKeyIndex + 1});
// Logique de mise à jour de la clé active dans le client
}
private async callLegacyProvider(prompt: string): Promise<string> {
// Ancienne implémentation (à maintenir temporairement)
return Promise.resolve('Legacy response placeholder');
}
private recordSuccess(latencyMs: number, estimatedCost: number): void {
this.metrics.totalRequests++;
this.metrics.successfulRequests++;
// Calcul de la moyenne mobile
const n = this.metrics.totalRequests;
this.metrics.averageLatency =
((n - 1) * this.metrics.averageLatency + latencyMs) / n;
this.metrics.totalCost += estimatedCost;
this.evaluateCanaryHealth();
}
private recordFailure(): void {
this.metrics.totalRequests++;
this.metrics.failedRequests++;
}
private estimateCost(prompt: string): number {
// Prix HolySheep AI 2026 (en USD)
const pricePerMTok = 8.00; // GPT-4.1
const tokens = Math.ceil(prompt.length / 4); // Approximation
return (tokens / 1000000) * pricePerMTok;
}
private estimateCostLegacy(prompt: string): number {
// Prix ancien provider
const pricePerMTok = 30.00;
const tokens = Math.ceil(prompt.length / 4);
return (tokens / 1000000) * pricePerMTok;
}
private evaluateCanaryHealth(): void {
const errorRate = this.metrics.failedRequests / this.metrics.totalRequests;
if (this.metrics.averageLatency < 200 && errorRate < 0.01) {
this.increaseCanaryPercentage();
} else if (this.metrics.averageLatency > 500 || errorRate > 0.05) {
this.decreaseCanaryPercentage();
}
}
private increaseCanaryPercentage(): void {
const newPercentage = Math.min(this.canaryPercentage + 10, 100);
console.log([Canary] Augmentation progressive : ${this.canaryPercentage}% → ${newPercentage}%);
this.canaryPercentage = newPercentage;
}
private decreaseCanaryPercentage(): void {
const newPercentage = Math.max(this.canaryPercentage - 5, 1);
console.log([Canary] Réduction (santé dégradée) : ${this.canaryPercentage}% → ${newPercentage}%);
this.canaryPercentage = newPercentage;
}
public getMetrics(): TrafficMetrics {
return { ...this.metrics };
}
public resetMetrics(): void {
this.metrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
averageLatency: 0,
totalCost: 0,
};
}
}
export const canaryController = new CanaryController();
Étape 3 : Implémentation de la Dégradation Progressive
La stratégie de fallback multi-niveau est cruciale pour la résilience. Voici l'implémentation complète que j'ai déployée.
// services/fallback-strategy.ts - Dégradation gracieuse
import { holySheepClient } from '../config/api-client';
type ModelType = 'gpt-4.1' | 'claude-sonnet-4.5' | 'gemini-2.5-flash' | 'deepseek-v3.2';
interface ModelConfig {
name: string;
pricePerMTok: number; // USD
maxLatencyMs: number;
priority: number;
}
interface RequestContext {
userId: string;
urgency: 'high' | 'medium' | 'low';
maxCost: number;
maxLatency: number;
}
class FallbackStrategy {
private models: ModelConfig[] = [
{ name: 'gpt-4.1', pricePerMTok: 8.00, maxLatencyMs: 2000, priority: 1 },
{ name: 'claude-sonnet-4.5', pricePerMTok: 15.00, maxLatencyMs: 3000, priority: 2 },
{ name: 'gemini-2.5-flash', pricePerMTok: 2.50, maxLatencyMs: 800, priority: 3 },
{ name: 'deepseek-v3.2', pricePerMTok: 0.42, maxLatencyMs: 1500, priority: 4 },
];
private modelHealth: Map<string, { success: number; failures: number }> = new Map();
constructor() {
this.models.forEach(model => {
this.modelHealth.set(model.name, { success: 0, failures: 0 });
});
}
public async executeWithFallback(
prompt: string,
context: RequestContext
): Promise<{ response: string; model: string; latency: number }> {
const startTime = Date.now();
const sortedModels = this.getModelsByPriority(context.urgency);
for (const model of sortedModels) {
// Vérification de la santé du modèle
if (!this.isModelHealthy(model.name)) {
console.log([Fallback] Modèle ${model.name} non disponible (santé dégradée));
continue;
}
// Vérification des contraintes de coût
const estimatedCost = this.estimateCost(prompt, model.pricePerMTok);
if (estimatedCost > context.maxCost) {
console.log([Fallback] ${model.name} hors budget (${estimatedCost}>${context.maxCost}));
continue;
}
try {
console.log([Fallback] Tentative avec ${model.name});
const response = await this.executeWithTimeout(
() => holySheepClient.completeRequest(prompt, model.name),
Math.min(model.maxLatencyMs, context.maxLatency)
);
const latency = Date.now() - startTime;
this.recordSuccess(model.name);
return {
response,
model: model.name,
latency,
};
} catch (error) {
console.log([Fallback] Échec ${model.name}: ${error.message});
this.recordFailure(model.name);
continue;
}
}
// Dernier recours : réponse cachée
return {
response: 'Service temporairement indisponible. Veuillez réessayer.',
model: 'fallback',
latency: Date.now() - startTime,
};
}
private getModelsByPriority(urgency: 'high' | 'medium' | 'low'): ModelConfig[] {
const sorted = [...this.models].sort((a, b) => a.priority - b.priority);
if (urgency === 'high') {
return sorted.slice(0, 2); // Meilleurs modèles uniquement
} else if (urgency === 'low') {
return sorted.slice(2); // Modèles économiques d'abord
}
return sorted;
}
private async executeWithTimeout<T>(
fn: () => Promise<T>,
timeoutMs: number
): Promise<T> {
return Promise.race([
fn(),
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), timeoutMs)
),
]);
}
private estimateCost(prompt: string, pricePerMTok: number): number {
const tokens = Math.ceil(prompt.length / 4);
return (tokens / 1000000) * pricePerMTok;
}
private isModelHealthy(modelName: string): boolean {
const health = this.modelHealth.get(modelName);
if (!health) return false;
const total = health.success + health.failures;
if (total < 10) return true; // Pas assez de données
const successRate = health.success / total;
return successRate > 0.9; // 90% de succès minimum
}
private recordSuccess(modelName: string): void {
const health = this.modelHealth.get(modelName);
if (health) health.success++;
}
private recordFailure(modelName: string): void {
const health = this.modelHealth.get(modelName);
if (health) health.failures++;
}
public getRecommendations(context: RequestContext): string[] {
const sorted = this.getModelsByPriority(context.urgency);
return sorted
.filter(m => this.estimateCost('Sample prompt', m.pricePerMTok) <= context.maxCost)
.filter(m => this.isModelHealthy(m.name))
.map(m => m.name);
}
}
export const fallbackStrategy = new FallbackStrategy();
// Exemple d'utilisation
async function exampleUsage() {
const result = await fallbackStrategy.executeWithFallback(
'Analysez les tendances de vente du Q4 2025',
{
userId: 'user_12345',
urgency: 'medium',
maxCost: 0.10,
maxLatency: 2000,
}
);
console.log(Réponse: ${result.response});
console.log(Modèle utilisé: ${result.model});
console.log(Latence: ${result.latency}ms);
}
Implémentation du Rate Limiter Local
Pour éviter les erreurs 429 coûteuses, j'ai déployé un rate limiter local avec bucketing de tokens.
// services/rate-limiter.ts - Limitation locale intelligente
interface TokenBucket {
tokens: number;
maxTokens: number;
refillRate: number; // tokens par seconde
lastRefill: number;
}
class AdvancedRateLimiter {
private buckets: Map<string, TokenBucket> = new Map();
private globalBucket: TokenBucket;
// Configuration par endpoint
private limits: Record<string, { rpm: number; rps: number }> = {
'chat/completions': { rpm: 3000, rps: 50 },
'embeddings': { rpm: 5000, rps: 100 },
'images/generations': { rpm: 50, rps: 1 },
};
constructor() {
this.globalBucket = {
tokens: 3000,
maxTokens: 3000,
refillRate: 50, // 3000/minute
lastRefill: Date.now(),
};
}
public async acquire(
userId: string,
endpoint: string,
tokens: number = 1
): Promise<boolean> {
const limit = this.limits[endpoint] || { rpm: 1000, rps: 20 };
// Initialisation du bucket utilisateur
if (!this.buckets.has(userId)) {
this.buckets.set(userId, {
tokens: limit.rpm,
maxTokens: limit.rpm,
refillRate: limit.rpm / 60, // par seconde
lastRefill: Date.now(),
});
}
const userBucket = this.buckets.get(userId)!;
// Refill des tokens
this.refillBucket(userBucket);
this.refillBucket(this.globalBucket);
// Vérification des limites
if (userBucket.tokens < tokens) {
const waitTime = ((tokens - userBucket.tokens) / userBucket.refillRate) * 1000;
console.log([RateLimit] Utilisateur ${userId} - attente ${waitTime.toFixed(0)}ms);
await this.sleep(waitTime);
this.refillBucket(userBucket);
}
if (this.globalBucket.tokens < tokens) {
const waitTime = ((tokens - this.globalBucket.tokens) / this.globalBucket.refillRate) * 1000;
console.log([RateLimit] Limite globale - attente ${waitTime.toFixed(0)}ms);
await this.sleep(waitTime);
this.refillBucket(this.globalBucket);
}
// Consommation des tokens
userBucket.tokens -= tokens;
this.globalBucket.tokens -= tokens;
return true;
}
private refillBucket(bucket: TokenBucket): void {
const now = Date.now();
const elapsedSeconds = (now - bucket.lastRefill) / 1000;
const tokensToAdd = elapsedSeconds * bucket.refillRate;
bucket.tokens = Math.min(bucket.maxTokens, bucket.tokens + tokensToAdd);
bucket.lastRefill = now;
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
public getStatus(userId: string): { userTokens: number; globalTokens: number } {
const userBucket = this.buckets.get(userId);
return {
userTokens: userBucket?.tokens || 0,
globalTokens: this.globalBucket.tokens,
};
}
// Nettoyage périodique des buckets inactifs
public cleanup(): void {
const now = Date.now();
const inactiveThreshold = 3600000; // 1 heure
for (const [userId, bucket] of this.buckets.entries()) {
if (now - bucket.lastRefill > inactiveThreshold) {
this.buckets.delete(userId);
console.log([RateLimit] Bucket nettoyé pour ${userId});
}
}
}
}
export const rateLimiter = new AdvancedRateLimiter();
// Nettoyage toutes les heures
setInterval(() => rateLimiter.cleanup(), 3600000);
Métriques à 30 Jours
Après 30 jours de migration complète, les résultats ont dépassé toutes les attentes initiales :
| Métrique | Avant (Provider précédent) | Après (HolySheep AI) | Amélioration |
|---|---|---|---|
| Latence médiane | 420ms | 180ms | -57% |
| P99 Latence | 2,3 secondes | 650ms | -72% |
| Facture mensuelle | $4 200 | $680 | -84% |
| Erreurs 429/heure | 847 | 12 | -99% |
| Taux de succès | 94,2% | 99,7% | +5,5 pts |
Erreurs Courantes et Solutions
Au cours de mes nombreuses migrations, j'ai identifié les erreurs les plus fréquentes. Voici comment les éviter :
Erreur 1 : Rate Limit Non Géré - Boucle Infinie de Retries
Symptôme : Le nombre de requêtes explose, la latence augmente progressivement, la facture s'envole.
Cause : L'application continue de retenter immédiatement après une erreur 429, aggravant la situation.
// ❌ MAUVAIS - Retry immédiat sans backoff
async function badRetry(prompt: string) {
while (true) {
try {
return await holySheepClient.completeRequest(prompt);
} catch (error) {
if (error.response?.status === 429) {
console.log('Rate limit atteint, retry immédiat...');
// ERREUR: Retry sans délai!
}
}
}
}
// ✅ BONNE PRATIQUE - Backoff exponentiel avec jitter
async function goodRetry(prompt: string, maxRetries: number = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await holySheepClient.completeRequest(prompt);
} catch (error) {
if (error.response?.status === 429) {
// Backoff exponentiel avec jitter aléatoire
const baseDelay = 1000; // 1 seconde
const maxDelay = 32000; // 32 secondes max
const delay = Math.min(
baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
maxDelay
);
console.log([Retry] Attente ${delay.toFixed(0)}ms (tentative ${attempt + 1}/${maxRetries}));
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error; // Erreur non-récupérable
}
}
}
throw new Error(Échec après ${maxRetries} tentatives);
}
Erreur 2 : Circuit Breaker Mal Configuré
Symptôme : Le service reste en panne alors que le provider est redevenu disponible.
Cause : Le circuit breaker ne se referme jamais ou se referme trop vite.
// ❌ MAUVAIS - État persistant
const badBreaker = {
failures: 0,
state: 'CLOSED',
// Problème: ne réessaye jamais après OPEN
isOpen: function() {
return this.state === 'OPEN';
}
};
// ✅ BONNE PRATIQUE - Half-open state intelligent
class SmartCircuitBreaker {
private failures: number = 0;
private successes: number = 0;
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
private lastFailureTime: number = 0;
private readonly FAILURE_THRESHOLD = 5;
private readonly SUCCESS_THRESHOLD = 3; // 3 succès pour fermer
private readonly RESET_TIMEOUT_MS = 60000; // 60s avant demi-ouverture
private readonly RECOVERY_TIMEOUT_MS = 30000; // 30s de récupération
public async execute<T>(fn: () => Promise<T>): Promise<T> {
// Transition OPEN → HALF_OPEN
if (this.state === 'OPEN') {
const timeSinceFailure = Date.now() - this.lastFailureTime;
if (timeSinceFailure >= this.RESET_TIMEOUT_MS) {
console.log('[CircuitBreaker] Transition OPEN → HALF_OPEN');
this.state = 'HALF_OPEN';
this.successes = 0;
} else {
throw new Error('Circuit breaker ouvert - utilisez le fallback');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
this.failures = 0;
if (this.state === 'HALF_OPEN') {
this.successes++;
console.log([CircuitBreaker] Succès en demi-ouverture (${this.successes}/${this.SUCCESS_THRESHOLD}));
if (this.successes >= this.SUCCESS_THRESHOLD) {
console.log('[CircuitBreaker] Transition HALF_OPEN → CLOSED');
this.state = 'CLOSED';
this.successes = 0;
}
}
}
private onFailure(): void {
this.failures++;
this.lastFailureTime = Date.now();
if (this.state === 'HALF_OPEN') {
console.log('[CircuitBreaker] Échec en demi-ouverture → OPEN');
this.state = 'OPEN';
} else if (this.failures >= this.FAILURE_THRESHOLD) {
console.log([CircuitBreaker] Seuil atteint (${this.failures}) → OPEN);
this.state = 'OPEN';
}
}
public getState(): string {
return this.state;
}
}
export const circuitBreaker = new SmartCircuitBreaker();
Erreur 3 : Gestion des Coûts Absente
Symptôme : Facture mensuelle imprévisible, dépassements budgétaires.
Cause : Pas de tracking des dépenses, sélection du modèle uniquement basée sur la qualité.
// ✅ BONNE PRATIQUE - Budget control avec sélection intelligente
class CostAwareClient {
private dailyBudget: number = 50; // $50/jour
private todaySpend: number = 0;
private todayDate: string = new Date().toISOString().split('T')[0];
private readonly MODEL_PRICES: Record<string, number> = {
'gpt-4.1': 8.00,
'claude-sonnet-4.5': 15.00,
'gemini-2.5-flash': 2.50,
'deepseek-v3.2': 0.42,
};
public async completeRequest(
prompt: string,
preferredModel: string = 'gpt-4.1'
): Promise<string> {
this.checkDate();
// Vérification du budget
if (this.todaySpend >= this.dailyBudget) {
console.warn([CostControl] Budget journalier épuisé (${this.todaySpend}/${this.dailyBudget}));
// Basculement vers le modèle le moins cher
return this.completeWithModel(prompt, 'deepseek-v3.2');
}
// Calcul du coût estimé
const tokens = Math.ceil(prompt.length / 4);
const estimatedCost = (tokens / 1000000) * this.MODEL_PRICES[preferredModel];
// Vérification du coût restant
if (this.todaySpend + estimatedCost > this.dailyBudget) {
console.log([CostControl] Budget insuffisant pour ${preferredModel}, sélection du modèle économique);
const affordableModel = this.selectAffordableModel(estimatedCost);
return this.completeWithModel(prompt, affordableModel);
}
return this.completeWithModel(prompt, preferredModel);
}
private selectAffordableModel(budgetLeft: number): string {
// Trouver le modèle le moins cher dans le budget
const models = Object.entries(this.MODEL_PRICES)
.sort(([, a], [, b]) => a - b);
for (const [model, price] of models) {
if (price * 1000 <= budgetLeft) { // Estimation 1K tokens
return model;
}
}
return 'deepseek-v3.2'; // Fallback vers le moins cher
}
private async completeWithModel(prompt: string, model: string): Promise<string> {
const tokens = Math.ceil(prompt.length / 4);
const cost = (tokens / 1000000) * this.MODEL_PRICES[model];
try {
const response = await holySheepClient.completeRequest(prompt, model);
this.todaySpend += cost;
console.log([CostControl] ${model}: ${cost.toFixed(4)}$ | Total: ${this.todaySpend.toFixed(2)}$);
return response;