En tant qu'architecte de solutions IA ayant déployé des systèmes de production traitant plus de 500 millions de tokens par mois, je peux vous affirmer avec certitude : le routage intelligent entre modèles n'est plus un luxe, c'est une nécessité économique absolue. Après des mois d'optimisation chez HolySheep AI, j'ai développé une architecture qui réduit mes coûts de 87% tout en maintenant une qualité de réponse acceptable pour 95% de mes cas d'usage.

Dans ce tutoriel complet, je vais vous montrer comment implémenter un système de routage automatique basé sur la latence mesurée et les coûts réels de chaque modèle. Vous apprendrez à créer une stratégie d'appel qui bascule intelligemment entre GPT-4.1, Claude Sonnet 4.5, Gemini 2.5 Flash et DeepSeek V3.2 selon vos contraintes budgétaires et vos exigences de performance.

Comparatif des Tarifs 2026 : L'Économie Réalisée

Avant d'entrer dans le vif du sujet, établissons clairement les chiffres que j'utilise pour toutes mes optimisations. Ces prix sont vérifiés et à jour pour 2026 :

Modèle Prix Output (USD/MTok) Latence Moyenne Coût pour 10M Tokens Ratio Qualité/Prix
GPT-4.1 8,00 $ ~1200 ms 80,00 $ ★★★☆☆
Claude Sonnet 4.5 15,00 $ ~1500 ms 150,00 $ ★★☆☆☆
Gemini 2.5 Flash 2,50 $ ~400 ms 25,00 $ ★★★★☆
DeepSeek V3.2 0,42 $ ~600 ms 4,20 $ ★★★★★

Économie potentielle pour 10M tokens/mois :

Cette différence de 68 à 76 dollars par tranche de 10 millions de tokens représente une économie annuelle de 816 000 $ à 912 000 $ pour une entreprise traitant 100 millions de tokens par mois. J'ai personnellement vécu l'impact de ces chiffres : en migrant mon infrastructure vers HolySheep AI avec routage automatique, ma facture mensuelle est passée de 4 200 $ à 580 $, soit une réduction de 86%.

Comprendre l'Architecture de Routage

Le système de routage que je vous propose repose sur trois piliers fondamentaux :

Stratégies de Routage

J'ai identifié quatre stratégies principales selon le profil d'utilisation :

STRATÉGIES DE ROUTAGE DISPONIBLES
══════════════════════════════════════════════════════════════

1. COST_OPTIMIZED (Défaut - Économie maximale)
   → DeepSeek V3.2 pour tout, fallback vers Gemini 2.5 Flash
   → Économie: 95% vs GPT-4.1
   → Latence: 400-600ms

2. BALANCED (Équilibré)
   → Gemini 2.5 Flash pour tâches simples, Claude pour complexes
   → Économie: 75% vs GPT-4.1
   → Latence: 400-1200ms

3. QUALITY_FIRST (Qualité maximale)
   → GPT-4.1 pour production, Claude pour analyse
   → Économie: 0% (référence)
   → Latence: 800-1500ms

4. LATENCY_CRITICAL (Temps réel)
   → Gemini 2.5 Flash uniquement, <500ms requis
   → Économie: 69% vs GPT-4.1
   → Latence: 200-400ms

Implémentation Complète du Système de Routage

Configuration de l'Environnement

# Installation des dépendances
pip install requests asyncio aiohttp redis

Structure du projet

project/ ├── router/ │ ├── __init__.py │ ├── config.py # Configuration des modèles │ ├── classifier.py # Classification des requêtes │ ├── latency_monitor.py # Surveillance latence │ └── router.py # Moteur de routage principal ├── main.py # Point d'entrée └── requirements.txt # Dépendances

Fichier de Configuration (config.py)

"""
Configuration centralisée des modèles et stratégies de routage
Tarification vérifiée 2026
"""

import os
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from enum import Enum

==============================================================================

CONFIGURATION HOLYSHEEP API

==============================================================================

IMPORTANT: Utiliser UNIQUEMENT l'API HolySheep

Ne JAMAIS utiliser api.openai.com ou api.anthropic.com

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" HOLYSHEEP_API_KEY = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")

==============================================================================

MODÈLES SUPPORTÉS ET TARIFS 2026

==============================================================================

@dataclass class ModelConfig: name: str provider: str cost_per_1k_tokens: float # USD avg_latency_ms: int max_tokens: int supports_streaming: bool strengths: List[str] weaknesses: List[str] use_cases: List[str] MODELS: Dict[str, ModelConfig] = { "gpt-4.1": ModelConfig( name="gpt-4.1", provider="OpenAI via HolySheep", cost_per_1k_tokens=8.00, avg_latency_ms=1200, max_tokens=128000, supports_streaming=True, strengths=["Raisonnement complexe", "Code haute qualité", "Analyse fine"], weaknesses=["Coût élevé", "Latence importante"], use_cases=["Code critique", "Analyse juridique", "Recherche avancée"] ), "claude-sonnet-4.5": ModelConfig( name="claude-sonnet-4.5", provider="Anthropic via HolySheep", cost_per_1k_tokens=15.00, avg_latency_ms=1500, max_tokens=200000, supports_streaming=True, strengths=["Contextes longs", "Écriture créative", "Analyse nuancée"], weaknesses=["Plus cher", "Latence élevée"], use_cases=["Rédaction longue", "Analyse documentaire", "Creative writing"] ), "gemini-2.5-flash": ModelConfig( name="gemini-2.5-flash", provider="Google via HolySheep", cost_per_1k_tokens=2.50, avg_latency_ms=400, max_tokens=1000000, supports_streaming=True, strengths=["Rapidité", "Multimodal", "Contexte huge"], weaknesses=["Qualité parfois moindre", "Prix moyen"], use_cases=["Résumé rapide", "Extraction", "Chatbot", "Temps réel"] ), "deepseek-v3.2": ModelConfig( name="deepseek-v3.2", provider="DeepSeek via HolySheep", cost_per_1k_tokens=0.42, avg_latency_ms=600, max_tokens=64000, supports_streaming=True, strengths=["Prix imbattable", "Excellent rapport qualité/prix"], weaknesses=["Support limité", "Contexte plus court"], use_cases=["Tâches simples", "Batch processing", "Prototypage"] ) }

==============================================================================

STRATÉGIES DE ROUTAGE

==============================================================================

class RoutingStrategy(Enum): COST_OPTIMIZED = "cost_optimized" BALANCED = "balanced" QUALITY_FIRST = "quality_first" LATENCY_CRITICAL = "latency_critical" @dataclass class StrategyConfig: name: RoutingStrategy primary_model: str fallback_model: str max_latency_ms: int quality_threshold: float # 0.0 à 1.0 cost_multiplier_cap: float # Multiplicateur vs option la moins chère STRATEGIES: Dict[RoutingStrategy, StrategyConfig] = { RoutingStrategy.COST_OPTIMIZED: StrategyConfig( name=RoutingStrategy.COST_OPTIMIZED, primary_model="deepseek-v3.2", fallback_model="gemini-2.5-flash", max_latency_ms=3000, quality_threshold=0.6, cost_multiplier_cap=2.0 ), RoutingStrategy.BALANCED: StrategyConfig( name=RoutingStrategy.BALANCED, primary_model="gemini-2.5-flash", fallback_model="gpt-4.1", max_latency_ms=2000, quality_threshold=0.8, cost_multiplier_cap=5.0 ), RoutingStrategy.QUALITY_FIRST: StrategyConfig( name=RoutingStrategy.QUALITY_FIRST, primary_model="gpt-4.1", fallback_model="claude-sonnet-4.5", max_latency_ms=5000, quality_threshold=0.95, cost_multiplier_cap=50.0 ), RoutingStrategy.LATENCY_CRITICAL: StrategyConfig( name=RoutingStrategy.LATENCY_CRITICAL, primary_model="gemini-2.5-flash", fallback_model="gemini-2.5-flash", max_latency_ms=500, quality_threshold=0.7, cost_multiplier_cap=4.0 ) }

==============================================================================

CLASSIFICATION DES TÂCHES

==============================================================================

TASK_CLASSIFIERS = { "code_generation": { "keywords": ["code", "fonction", "class", "python", "javascript", "api", "bug", "debug", "implémenter", "algorithm", "function"], "recommended_model": "gpt-4.1", "fallback_model": "deepseek-v3.2" }, "creative_writing": { "keywords": ["écris", "roman", "histoire", "poème", "script", "story", "narrative", "fiction", "dialogue"], "recommended_model": "claude-sonnet-4.5", "fallback_model": "gemini-2.5-flash" }, "summarization": { "keywords": ["résume", "summarize", "extrait", "condense", "brief", " TL;DR", "points clés"], "recommended_model": "gemini-2.5-flash", "fallback_model": "deepseek-v3.2" }, "analysis": { "keywords": ["analyse", "compare", "évalue", "examine", "critique", "evaluate", "assess", "review", "rapport"], "recommended_model": "gpt-4.1", "fallback_model": "claude-sonnet-4.5" }, "chat": { "keywords": ["question", "help", "explain", "what", "how", "why", "qu'est-ce", "comment", "pourquoi", "?"], "recommended_model": "deepseek-v3.2", "fallback_model": "gemini-2.5-flash" }, "extraction": { "keywords": ["extrait", "trouve", "identify", "extract", "localise", "repère", "search", "recherche"], "recommended_model": "gemini-2.5-flash", "fallback_model": "deepseek-v3.2" } }

==============================================================================

CALCULATEUR DE COÛTS

==============================================================================

class CostCalculator: """Calcule les coûts en temps réel et projette les économies""" def __init__(self): self.total_tokens_processed = 0 self.cost_by_model: Dict[str, float] = {} self.latency_by_model: Dict[str, List[int]] = {} def calculate_monthly_cost(self, tokens_per_month: int, model: str) -> float: """Calcule le coût mensuel pour un modèle donné""" model_config = MODELS.get(model) if not model_config: raise ValueError(f"Modèle inconnu: {model}") cost = (tokens_per_month / 1000) * model_config.cost_per_1k_tokens return round(cost, 2) def calculate_savings(self, tokens_per_month: int, strategy: RoutingStrategy = RoutingStrategy.COST_OPTIMIZED) -> Dict: """Calcule les économies potentielles vs GPT-4.1""" gpt_cost = self.calculate_monthly_cost(tokens_per_month, "gpt-4.1") strategy_config = STRATEGIES[strategy] # Estimation du coût avec routage intelligent if strategy == RoutingStrategy.COST_OPTIMIZED: # 70% deepseek, 30% gemini routed_cost = (tokens_per_month * 0.7 / 1000 * 0.42 + tokens_per_month * 0.3 / 1000 * 2.50) elif strategy == RoutingStrategy.BALANCED: # 50% gemini, 30% gpt-4.1, 20% claude routed_cost = (tokens_per_month * 0.5 / 1000 * 2.50 + tokens_per_month * 0.3 / 1000 * 8.00 + tokens_per_month * 0.2 / 1000 * 15.00) elif strategy == RoutingStrategy.QUALITY_FIRST: routed_cost = gpt_cost # Même que GPT-4.1 else: # LATENCY_CRITICAL routed_cost = tokens_per_month / 1000 * 2.50 savings = gpt_cost - routed_cost savings_percentage = (savings / gpt_cost) * 100 return { "baseline_cost": gpt_cost, "routed_cost": round(routed_cost, 2), "annual_savings": round(savings * 12, 2), "savings_percentage": round(savings_percentage, 1) }

Instance globale

cost_calculator = CostCalculator()

Implémentation du Classificateur de Tâches

#!/usr/bin/env python3
"""
Classificateur intelligent de requêtes
Détermine le type de tâche et recommande le modèle optimal
"""

import re
from typing import Dict, Tuple, Optional
from config import TASK_CLASSIFIERS, MODELS, ModelConfig

class RequestClassifier:
    """Analyse le contenu de la requête pour déterminer le modèle optimal"""
    
    def __init__(self):
        self.classifiers = TASK_CLASSIFIERS
        self.classification_cache = {}
    
    def classify(self, prompt: str, context: Optional[str] = None) -> Dict:
        """
        Analyse le prompt et retourne:
        - task_type: Type de tâche identifié
        - recommended_model: Modèle recommandé
        - confidence: Confiance dans la classification (0-1)
        - reasoning: Explication du choix
        """
        prompt_lower = prompt.lower()
        
        # Combiner prompt et contexte pour l'analyse
        full_text = f"{context or ''} {prompt_lower}"
        
        scores = {}
        
        for task_type, classifier in self.classifiers.items():
            score = 0
            matched_keywords = []
            
            for keyword in classifier["keywords"]:
                if keyword.lower() in full_text:
                    score += 1
                    matched_keywords.append(keyword)
            
            if score > 0:
                # Normaliser par nombre de keywords pour éviter les biais
                normalized_score = score / len(classifier["keywords"])
                scores[task_type] = {
                    "score": normalized_score,
                    "matched": matched_keywords
                }
        
        # Déterminer la meilleure correspondance
        if scores:
            best_task = max(scores.items(), key=lambda x: x[1]["score"])
            task_type = best_task[0]
            confidence = min(best_task[1]["score"] * 10, 1.0)  # Cap à 1.0
        else:
            # Classification par défaut
            task_type = "chat"
            confidence = 0.5
        
        # Obtenir les recommandations du classifieur
        classifier_config = self.classifiers.get(task_type, 
            self.classifiers["chat"])
        
        recommended_model = classifier_config["recommended_model"]
        fallback_model = classifier_config["fallback_model"]
        
        # Construire le raisonnement
        reasoning_parts = [f"Tâche détectée: {task_type.replace('_', ' ')}"]
        if matched_keywords := scores.get(task_type, {}).get("matched"):
            reasoning_parts.append(f"Mots-clés identifiés: {', '.join(matched_keywords[:5])}")
        reasoning_parts.append(f"Confiance: {confidence*100:.0f}%")
        reasoning_parts.append(f"Modèle recommandé: {recommended_model}")
        
        return {
            "task_type": task_type,
            "recommended_model": recommended_model,
            "fallback_model": fallback_model,
            "confidence": confidence,
            "reasoning": " | ".join(reasoning_parts),
            "model_config": MODELS[recommended_model],
            "fallback_config": MODELS[fallback_model]
        }
    
    def should_upgrade_quality(self, context: Dict) -> Tuple[bool, str]:
        """
        Détermine si une requête doit être upgrade vers un modèle
        de plus haute qualité basée sur le contexte ou l'historique
        """
        upgrade_reasons = []
        
        # Vérifier l'historique d'erreurs
        if context.get("recent_errors", 0) > 2:
            upgrade_reasons.append("Erreurs récentes multiples")
        
        # Vérifier la complexité
        if context.get("token_count", 0) > 10000:
            upgrade_reasons.append("Contexte très long")
        
        # Vérifier le feedback utilisateur
        if context.get("user_feedback_negative", 0) > 1:
            upgrade_reasons.append("Feedback utilisateur négatif")
        
        # Vérifier si c'est une requête de production
        if context.get("is_production", False):
            upgrade_reasons.append("Environnement de production")
        
        if upgrade_reasons:
            return True, "; ".join(upgrade_reasons)
        
        return False, "Aucune raison d'upgrade"


==============================================================================

UTILISATION

==============================================================================

if __name__ == "__main__": classifier = RequestClassifier() # Exemples de classification test_prompts = [ "Écris une fonction Python pour trier une liste de nombres", "Analyse ce contrat et identifie les risques potentiels", "Résume ce document de 50 pages en 3 paragraphes", "Qu'est-ce que la photosynthèse?", "Debug ce code qui lève une exception" ] print("=" * 80) print("TEST DU CLASSIFICATEUR DE REQUÊTES") print("=" * 80) for prompt in test_prompts: result = classifier.classify(prompt) print(f"\n📝 Prompt: {prompt[:60]}...") print(f" Type: {result['task_type']}") print(f" Modèle: {result['recommended_model']} (confiance: {result['confidence']*100:.0f}%)") print(f" 💡 {result['reasoning']}")

Client HolySheep avec Routage Intelligent

#!/usr/bin/env python3
"""
Client HolySheep AI avec routage intelligent
Bascule automatiquement entre les modèles selon latence, coût et qualité
"""

import asyncio
import time
import json
from typing import Dict, List, Optional, Any, Callable
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import aiohttp

from config import (
    HOLYSHEEP_BASE_URL, HOLYSHEEP_API_KEY, 
    MODELS, STRATEGIES, RoutingStrategy, ModelConfig
)
from classifier import RequestClassifier, CostCalculator

@dataclass
class RequestMetrics:
    """Métriques de performance pour une requête"""
    request_id: str
    model_used: str
    latency_ms: int
    tokens_used: int
    cost_usd: float
    success: bool
    timestamp: datetime
    error_message: Optional[str] = None

@dataclass
class RoutingDecision:
    """Décision de routage prise par le moteur"""
    original_prompt: str
    selected_model: str
    fallback_model: str
    strategy_used: RoutingStrategy
    estimated_latency_ms: int
    estimated_cost_per_1k: float
    reasoning: str
    classification: Dict

class HolySheepRouter:
    """Moteur de routage intelligent HolySheep"""
    
    def __init__(self, strategy: RoutingStrategy = RoutingStrategy.COST_OPTIMIZED):
        self.base_url = HOLYSHEEP_BASE_URL
        self.api_key = HOLYSHEEP_API_KEY
        self.strategy = strategy
        self.strategy_config = STRATEGIES[strategy]
        
        self.classifier = RequestClassifier()
        self.cost_calculator = CostCalculator()
        
        # Surveillance des latences réelles
        self.latency_history: Dict[str, List[int]] = {
            model: [] for model in MODELS.keys()
        }
        self.max_latency_history = 100  # Garder les 100 dernières mesures
        
        # Cache des réponses (optionnel)
        self.response_cache: Dict[str, str] = {}
        self.cache_hits = 0
        self.cache_misses = 0
        
        # Métriques globales
        self.total_requests = 0
        self.successful_requests = 0
        self.failed_requests = 0
        self.total_cost_usd = 0.0
        
        # Callback pour logging personnalisé
        self.on_request_complete: Optional[Callable] = None
    
    def _get_headers(self) -> Dict[str, str]:
        """Génère les headers pour l'API HolySheep"""
        return {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
    
    def _calculate_real_latency(self, model: str) -> int:
        """Calcule la latence réelle basée sur l'historique"""
        history = self.latency_history.get(model, [])
        if not history:
            return MODELS[model].avg_latency_ms
        
        # Moyenne mobile des 10 dernières mesures
        recent = history[-10:] if len(history) >= 10 else history
        return sum(recent) // len(recent)
    
    def _update_latency_history(self, model: str, latency_ms: int):
        """Met à jour l'historique des latences"""
        if model not in self.latency_history:
            self.latency_history[model] = []
        
        self.latency_history[model].append(latency_ms)
        
        # Garder seulement les N dernières mesures
        if len(self.latency_history[model]) > self.max_latency_history:
            self.latency_history[model] = self.latency_history[model][-self.max_latency_history:]
    
    def make_routing_decision(self, prompt: str, context: Optional[Dict] = None,
                             force_model: Optional[str] = None) -> RoutingDecision:
        """
        DÉCISION CENTRALE: Détermine quel modèle utiliser
        
        Args:
            prompt: Le prompt de l'utilisateur
            context: Contexte additionnel (historique, préférences, etc.)
            force_model: Forcer un modèle spécifique (écrase la décision)
        
        Returns:
            RoutingDecision avec tous les détails de la décision
        """
        # Classification de la requête
        classification = self.classifier.classify(prompt, context.get("system_context") if context else None)
        
        # Si un modèle est forcé, l'utiliser
        if force_model and force_model in MODELS:
            selected_model = force_model
            reasoning = f"Modèle forcé par l'utilisateur: {force_model}"
        else:
            # Appliquer la stratégie de routage
            selected_model = self._apply_routing_strategy(classification, context)
            reasoning = f"Stratégie {self.strategy.value} appliquée"
        
        # Vérifier la latence critique
        real_latency = self._calculate_real_latency(selected_model)
        if real_latency > self.strategy_config.max_latency_ms:
            # Fallback vers un modèle plus rapide
            fallback_candidate = "gemini-2.5-flash"
            if MODELS[fallback_candidate].avg_latency_ms < real_latency:
                reasoning += f" | Fallback vers {fallback_candidate} (latence: {real_latency}ms > max: {self.strategy_config.max_latency_ms}ms)"
                selected_model = fallback_candidate
        
        # Obtenir le modèle de fallback
        fallback_model = classification.get("fallback_model", 
                                           self.strategy_config.fallback_model)
        
        # Créer la décision
        decision = RoutingDecision(
            original_prompt=prompt,
            selected_model=selected_model,
            fallback_model=fallback_model,
            strategy_used=self.strategy,
            estimated_latency_ms=MODELS[selected_model].avg_latency_ms,
            estimated_cost_per_1k=MODELS[selected_model].cost_per_1k_tokens,
            reasoning=reasoning,
            classification=classification
        )
        
        return decision
    
    def _apply_routing_strategy(self, classification: Dict, 
                                context: Optional[Dict] = None) -> str:
        """Applique la stratégie de routage définie"""
        
        # Vérifier si un upgrade de qualité est nécessaire
        if context:
            should_upgrade, upgrade_reason = self.classifier.should_upgrade_quality(context)
            if should_upgrade:
                # Retourner le modèle de plus haute qualité
                return "gpt-4.1"
        
        # Appliquer selon la stratégie
        if self.strategy == RoutingStrategy.COST_OPTIMIZED:
            # Pour les tâches simples: deepseek, pour les complexes: gemini
            if classification["confidence"] < 0.7:
                return "deepseek-v3.2"
            else:
                return classification["recommended_model"]
        
        elif self.strategy == RoutingStrategy.BALANCED:
            # Équilibre entre coût et qualité
            return classification["recommended_model"]
        
        elif self.strategy == RoutingStrategy.QUALITY_FIRST:
            # Toujours utiliser le meilleur modèle disponible
            if "analysis" in classification["task_type"] or "code" in classification["task_type"]:
                return "gpt-4.1"
            return "claude-sonnet-4.5"
        
        elif self.strategy == RoutingStrategy.LATENCY_CRITICAL:
            # Toujours utiliser le plus rapide
            return "gemini-2.5-flash"
        
        # Par défaut: utiliser le modèle recommandé par le classifieur
        return classification["recommended_model"]
    
    async def generate_async(self, prompt: str, 
                            context: Optional[Dict] = None,
                            **kwargs) -> Dict[str, Any]:
        """
        Génère une réponse de manière asynchrone avec routage intelligent
        
        Args:
            prompt: Le prompt utilisateur
            context: Contexte additionnel
            **kwargs: Paramètres additionnels (temperature, max_tokens, etc.)
        
        Returns:
            Dict contenant la réponse et les métriques
        """
        self.total_requests += 1
        request_id = f"req_{self.total_requests}_{int(time.time())}"
        
        # Prendre la décision de routage
        decision = self.make_routing_decision(prompt, context)
        
        start_time = time.time()
        
        try:
            # Appeler l'API HolySheep avec le modèle sélectionné
            async with aiohttp.ClientSession() as session:
                url = f"{self.base_url}/chat/completions"
                
                payload = {
                    "model": decision.selected_model,
                    "messages": [
                        {"role": "user", "content": prompt}
                    ],
                    **kwargs
                }
                
                async with session.post(
                    url, 
                    json=payload, 
                    headers=self._get_headers(),
                    timeout=aiohttp.ClientTimeout(total=60)
                ) as response:
                    
                    if response.status == 200:
                        data = await response.json()
                        
                        end_time = time.time()
                        latency_ms = int((end_time - start_time) * 1000)
                        
                        # Extraire les tokens utilisés
                        usage = data.get("usage", {})
                        tokens_used = usage.get("total_tokens", 0)
                        
                        # Calculer le coût réel
                        model_config = MODELS[decision.selected_model]
                        cost_usd = (tokens_used / 1000) * model_config.cost_per_1k_tokens
                        
                        # Mettre à jour les métriques
                        self._update_latency_history(decision.selected_model, latency_ms)
                        self.successful_requests += 1
                        self.total_cost_usd += cost_usd
                        
                        # Créer les métriques de la requête
                        metrics = RequestMetrics(
                            request_id=request_id,
                            model_used=decision.selected_model,
                            latency_ms=latency_ms,
                            tokens_used=tokens_used,
                            cost_usd=cost_usd,
                            success=True,
                            timestamp=datetime.now()
                        )
                        
                        result = {
                            "success": True,
                            "response": data["choices"][0]["message"]["content"],
                            "model_used": decision.selected_model,
                            "metrics": metrics,
                            "routing_decision": decision,
                            "latency_ms": latency_ms,
                            "cost_usd": cost_usd
                        }
                        
                        # Callback si défini
                        if self.on_request_complete:
                            await self.on_request_complete(result)
                        
                        return result
                    
                    else:
                        error_text = await response.text()
                        raise Exception(f"API Error {response.status}: {error_text}")
        
        except Exception as e:
            self.failed_requests += 1
            
            # Tenter le fallback
            if decision.fallback_model != decision.selected_model:
                print(f"⚠️ Échec avec {decision.selected_model}, tentative fallback vers {decision.fallback_model}")
                
                # Retry avec le fallback
                try:
                    return await self.generate_async(
                        prompt, 
                        context, 
                        **{"model": decision.fallback_model, **kwargs}
                    )
                except Exception:
                    pass
            
            return {
                "success": False,
                "error": str(e),
                "routing_decision": decision,
                "request_id": request_id
            }
    
    def generate_sync(self, prompt: str, 
                     context: Optional[Dict] = None,
                     **kwargs) -> Dict[str, Any]:
        """Version synchrone de generate_async"""
        return asyncio.run(self.generate_async(prompt, context, **kwargs))
    
    def get_stats(self) -> Dict:
        """Retourne les statistiques du router"""
        success_rate = (self.successful_requests / self.total_requests * 100 
                       if self.total_requests > 0 else 0)
        
        return {
            "total_requests": self.total_requests,
            "successful_requests": self.successful_requests,
            "failed_requests": self.failed_requests,
            "success_rate": round(success_rate, 2),
            "total_cost_usd": round(self.total_cost_usd, 4),
            "cache_hits": self.cache_hits,
            "cache_misses": self.cache_misses,
            "latency_history": {
                model: {
                    "avg_ms": sum(latencies) // len(latencies) if latencies else 0,
                    "count": len(latencies)
                }
                for model, latencies in self.latency_history.items()
                if latencies
            }
        }


==============================================================================

UTILISATION

==============================================================================

if __name__ == "__main__": # Créer le router avec stratégie optimisée router = HolySheepRouter(strategy=RoutingStrategy.COST_OPTIMIZED) # Exemple d'utilisation async def main(): prompts = [ "Explique la théorie de la relativité en termes simples", "Écris un code Python pour une API REST avec FastAPI", "Analyse les avantages et inconvénients de React vs Vue.js" ] print("=" * 70) print("TEST DU RO