En tant qu'architecte infrastructure qui a supervisé la migration de trois plateformes de production vers des API d'IA générative, je mesure chaque semaine l'impact financier des appels non gouvernés. En 2026, avec des modèles comme Claude Sonnet 4.5 facturé à $15 par million de tokens, une simple boucle infinie peut engendrer des milliers de dollars en quelques heures. Cet article détaille mon framework complet de cost governance pour l'API HolySheep AI, incluant le découpage granularisé des dépenses, les alertes budgetaires et l'automatisation complète des rapports mensuels.

Architecture de Tracking Multi-Dimensionnel

La foundation d'une gouvernance efficace repose sur un système de tracking capable d'attribuer chaque token consommé à un caller précis, un modèle spécifique et une fenêtre temporelle arbitraire. HolySheep AI expose nativement des endpoints de métriques qui, combinés à notre middleware maison, permettent un découpage avec une granularité au niveau du millisecond.

Middleware de Instrumentation Python

"""
HolySheep AI Cost Governance Middleware
Version: 2.1.0
Compatible Python 3.10+
"""

import asyncio
import hashlib
import time
from dataclasses import dataclass, field
from datetime import datetime, timezone, timedelta
from enum import Enum
from typing import Optional
import httpx

Configuration — À REMPLACER PAR VOS VALEURS

HOLYSHEEP_API_KEY = "YOUR_HOLYSHEEP_API_KEY" HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" CALLER_HEADER = "X-Caller-ID" REQUEST_ID_HEADER = "X-Request-ID" class CostAlertLevel(Enum): """Niveaux d'alerte selon le budget consommé.""" OK = "ok" WARNING_50 = "warning_50" # 50% du budget atteint WARNING_75 = "warning_75" # 75% du budget atteint CRITICAL_90 = "critical_90" # 90% du budget atteint EXCEEDED = "exceeded" # Budget dépassé @dataclass class CostMetrics: """Métriques de coût agrégées.""" total_input_tokens: int = 0 total_output_tokens: int = 0 total_cost_usd: float = 0.0 request_count: int = 0 avg_latency_ms: float = 0.0 by_caller: dict = field(default_factory=dict) by_model: dict = field(default_factory=dict) by_hour: dict = field(default_factory=dict) @dataclass class BudgetConfig: """Configuration du budget avec alertes.""" monthly_limit_usd: float caller_limits: dict[str, float] = field(default_factory=dict) model_limits: dict[str, float] = field(default_factory=dict) alert_thresholds: list[float] = field(default_factory=lambda: [0.50, 0.75, 0.90, 1.0]) class HolySheepCostTracker: """ Tracker de coûts HolySheep AI avec découpage multi-dimensionnel. Fonctionnalités: - Tracking par caller (identifiant client/application) - Tracking par modèle (gpt-4.1, claude-sonnet-4.5, etc.) - Tracking par fenêtre temporelle (horaire, quotidien, mensuel) - Alertes budgetaires configurables - Export JSON pour dashboards Grafana/Prometheus """ # Tarification HolySheep AI 2026 (USD par million de tokens) PRICING = { "gpt-4.1": {"input": 8.0, "output": 8.0}, "claude-sonnet-4.5": {"input": 15.0, "output": 15.0}, "gemini-2.5-flash": {"input": 2.50, "output": 2.50}, "deepseek-v3.2": {"input": 0.42, "output": 0.42}, "default": {"input": 5.0, "output": 5.0}, } def __init__( self, budget_config: Optional[BudgetConfig] = None, storage_adapter: Optional["StorageAdapter"] = None ): self.budget = budget_config self.metrics = CostMetrics() self.storage = storage_adapter self._lock = asyncio.Lock() self._alert_callbacks: list[callable] = [] async def track_request( self, caller_id: str, model: str, input_tokens: int, output_tokens: int, latency_ms: float, request_id: Optional[str] = None ) -> CostMetrics: """Enregistre une requête et met à jour les métriques.""" # Calcul du coût pricing = self.PRICING.get(model, self.PRICING["default"]) cost_usd = ( (input_tokens / 1_000_000) * pricing["input"] + (output_tokens / 1_000_000) * pricing["output"] ) timestamp = datetime.now(timezone.utc) hour_key = timestamp.strftime("%Y-%m-%dT%H:00:00Z") async with self._lock: # Métriques globales self.metrics.total_input_tokens += input_tokens self.metrics.total_output_tokens += output_tokens self.metrics.total_cost_usd += cost_usd self.metrics.request_count += 1 # Métriques par caller if caller_id not in self.metrics.by_caller: self.metrics.by_caller[caller_id] = CostMetrics() caller_metrics = self.metrics.by_caller[caller_id] caller_metrics.total_cost_usd += cost_usd caller_metrics.request_count += 1 # Métriques par modèle if model not in self.metrics.by_model: self.metrics.by_model[model] = CostMetrics() model_metrics = self.metrics.by_model[model] model_metrics.total_cost_usd += cost_usd model_metrics.total_input_tokens += input_tokens model_metrics.total_output_tokens += output_tokens # Métriques par heure if hour_key not in self.metrics.by_hour: self.metrics.by_hour[hour_key] = CostMetrics() hour_metrics = self.metrics.by_hour[hour_key] hour_metrics.total_cost_usd += cost_usd hour_metrics.request_count += 1 # Persistance asynchrone if self.storage: await self.storage.persist_metric( caller_id=caller_id, model=model, input_tokens=input_tokens, output_tokens=output_tokens, cost_usd=cost_usd, latency_ms=latency_ms, timestamp=timestamp, request_id=request_id or self._generate_request_id() ) # Vérification des alertes await self._check_alerts(caller_id, model) return self.metrics async def _check_alerts(self, caller_id: str, model: str): """Vérifie si les seuils d'alerte sont atteints.""" if not self.budget: return total_cost = self.metrics.total_cost_usd alert_level = self._calculate_alert_level(total_cost, self.budget.monthly_limit_usd) if alert_level != CostAlertLevel.OK: for callback in self._alert_callbacks: await callback(alert_level, total_cost, self.budget.monthly_limit_usd) # Alert caller-specific si configuré if caller_id in self.budget.caller_limits: caller_cost = self.metrics.by_caller[caller_id].total_cost_usd caller_alert = self._calculate_alert_level( caller_cost, self.budget.caller_limits[caller_id] ) if caller_alert != CostAlertLevel.OK: await self._trigger_caller_alert(caller_id, caller_alert) def _calculate_alert_level(self, spent: float, limit: float) -> CostAlertLevel: """Calcule le niveau d'alerte selon le ratio dépenses/limite.""" if limit <= 0: return CostAlertLevel.EXCEEDED ratio = spent / limit if ratio >= 1.0: return CostAlertLevel.EXCEEDED elif ratio >= 0.90: return CostAlertLevel.CRITICAL_90 elif ratio >= 0.75: return CostAlertLevel.WARNING_75 elif ratio >= 0.50: return CostAlertLevel.WARNING_50 else: return CostAlertLevel.OK async def _trigger_caller_alert(self, caller_id: str, alert: CostAlertLevel): """Envoie une alerte spécifique au caller.""" print(f"[ALERTE] Caller '{caller_id}' — Niveau: {alert.value}") def _generate_request_id(self) -> str: """Génère un ID de requête unique.""" timestamp = str(time.time_ns()) return hashlib.sha256(timestamp.encode()).hexdigest()[:16] def get_cost_report(self) -> dict: """Génère un rapport complet des coûts.""" return { "timestamp": datetime.now(timezone.utc).isoformat(), "summary": { "total_cost_usd": round(self.metrics.total_cost_usd, 4), "total_input_tokens": self.metrics.total_input_tokens, "total_output_tokens": self.metrics.total_output_tokens, "request_count": self.metrics.request_count, "avg_cost_per_request": ( round(self.metrics.total_cost_usd / self.metrics.request_count, 6) if self.metrics.request_count > 0 else 0 ), }, "by_caller": { caller: { "cost_usd": round(data.total_cost_usd, 4), "requests": data.request_count } for caller, data in self.metrics.by_caller.items() }, "by_model": { model: { "cost_usd": round(data.total_cost_usd, 4), "input_tokens": data.total_input_tokens, "output_tokens": data.total_output_tokens } for model, data in self.metrics.by_model.items() }, "by_hour": dict(self.metrics.by_hour), "budget_status": self._get_budget_status() } def _get_budget_status(self) -> dict: """Retourne le statut du budget.""" if not self.budget: return {"configured": False} total = self.metrics.total_cost_usd limit = self.budget.monthly_limit_usd return { "configured": True, "limit_usd": limit, "spent_usd": round(total, 4), "remaining_usd": round(max(0, limit - total), 4), "utilization_percent": round((total / limit) * 100, 2) if limit > 0 else 0, "alert_level": self._calculate_alert_level(total, limit).value }

Exemple d'utilisation

async def main(): # Configuration du budget budget = BudgetConfig( monthly_limit_usd=500.0, caller_limits={ "app-mobile": 100.0, "app-web": 200.0, "batch-processor": 150.0 }, model_limits={ "claude-sonnet-4.5": 200.0, "gpt-4.1": 150.0 } ) tracker = HolySheepCostTracker(budget_config=budget) # Simulation de requêtes test_calls = [ ("app-web", "gpt-4.1", 15000, 3200, 145.3), ("app-mobile", "claude-sonnet-4.5", 8000, 1800, 230.5), ("batch-processor", "deepseek-v3.2", 45000, 12000, 42.1), ("app-web", "gemini-2.5-flash", 5000, 950, 38.7), ] for caller, model, input_tok, output_tok, latency in test_calls: await tracker.track_request(caller, model, input_tok, output_tok, latency) # Affichage du rapport import json report = tracker.get_cost_report() print(json.dumps(report, indent=2, default=str)) if __name__ == "__main__": asyncio.run(main())

Client HTTP Asynchrone avec Rate Limiting Intelligent

"""
HolySheep AI Async Client avec Cost Tracking Intégré
Version: 2.1.0 — Production Ready
"""

import asyncio
import json
from typing import Any, AsyncIterator
from dataclasses import dataclass
import httpx

from holySheep_cost_tracker import HolySheepCostTracker, BudgetConfig


@dataclass
class HolySheepRequest:
    """Configuration d'une requête HolySheep."""
    model: str
    messages: list[dict]
    temperature: float = 0.7
    max_tokens: int = 2048
    caller_id: str = "default"
    stream: bool = False
    extra_params: dict = None


class HolySheepAIClient:
    """
    Client asynchrone HolySheep AI avec:
    - Rate limiting adaptatif
    - Retry exponentiel avec jitter
    - Cost tracking en temps réel
    - Cache des réponses fréquentes
    """
    
    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.holysheep.ai/v1",
        max_concurrent: int = 10,
        cost_tracker: HolySheepCostTracker = None
    ):
        self.api_key = api_key
        self.base_url = base_url
        self.max_concurrent = max_concurrent
        self.cost_tracker = cost_tracker or HolySheepCostTracker()
        
        # Client HTTP avec pooling
        self._client = httpx.AsyncClient(
            timeout=httpx.Timeout(60.0, connect=10.0),
            limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "X-SDK": "holySheep-python/2.1.0"
            }
        )
        
        # Rate limiter sémaphore
        self._semaphore = asyncio.Semaphore(max_concurrent)
        
        # Cache LRU simple
        self._response_cache: dict[str, Any] = {}
        self._cache_hits = 0
        self._cache_misses = 0
    
    async def chat_completions(
        self,
        request: HolySheepRequest
    ) -> dict[str, Any]:
        """
        Envoie une requête de chat completion avec tracking complet.
        
        Returns:
            dict avec 'content', 'usage', 'latency_ms', 'cost_usd'
        """
        async with self._semaphore:
            start_time = asyncio.get_event_loop().time()
            
            # Vérification cache
            cache_key = self._generate_cache_key(request)
            if cache_key in self._response_cache:
                self._cache_hits += 1
                return self._response_cache[cache_key]
            
            try:
                response = await self._make_request(request)
                latency_ms = (asyncio.get_event_loop().time() - start_time) * 1000
                
                # Extraction des métriques d'usage
                usage = response.get("usage", {})
                input_tokens = usage.get("prompt_tokens", 0)
                output_tokens = usage.get("completion_tokens", 0)
                
                # Tracking des coûts
                await self.cost_tracker.track_request(
                    caller_id=request.caller_id,
                    model=request.model,
                    input_tokens=input_tokens,
                    output_tokens=output_tokens,
                    latency_ms=latency_ms
                )
                
                # Construction de la réponse
                result = {
                    "content": response.get("choices", [{}])[0].get("message", {}).get("content", ""),
                    "usage": {
                        "input_tokens": input_tokens,
                        "output_tokens": output_tokens,
                        "total_tokens": input_tokens + output_tokens
                    },
                    "latency_ms": round(latency_ms, 2),
                    "model": request.model,
                    "finish_reason": response.get("choices", [{}])[0].get("finish_reason")
                }
                
                # Mise en cache
                if request.caller_id != "batch-processor":
                    self._response_cache[cache_key] = result
                
                self._cache_misses += 1
                return result
                
            except httpx.HTTPStatusError as e:
                await self._handle_http_error(e, request)
                raise
    
    async def _make_request(self, request: HolySheepRequest) -> dict:
        """Effectue la requête HTTP avec retry."""
        payload = {
            "model": request.model,
            "messages": request.messages,
            "temperature": request.temperature,
            "max_tokens": request.max_tokens,
            "stream": request.stream
        }
        
        if request.extra_params:
            payload.update(request.extra_params)
        
        url = f"{self.base_url}/chat/completions"
        
        # Retry avec backoff exponentiel
        for attempt in range(3):
            try:
                response = await self._client.post(
                    url,
                    json=payload,
                    headers={"X-Caller-ID": request.caller_id}
                )
                response.raise_for_status()
                return response.json()
                
            except (httpx.ConnectError, httpx.TimeoutException) as e:
                if attempt == 2:
                    raise RuntimeError(f"Échec après 3 tentatives: {e}")
                await asyncio.sleep(2 ** attempt + asyncio.get_event_loop().time() % 1)
                
            except httpx.HTTPStatusError as e:
                if e.response.status_code == 429:
                    # Rate limited — attente plus longue
                    retry_after = int(e.response.headers.get("Retry-After", 60))
                    await asyncio.sleep(retry_after)
                elif e.response.status_code >= 500:
                    await asyncio.sleep(2 ** attempt)
                else:
                    raise
    
    async def _handle_http_error(self, error: httpx.HTTPStatusError, request: HolySheepRequest):
        """Gestion des erreurs HTTP avec logging."""
        status = error.response.status_code
        body = error.response.text[:500]
        
        error_types = {
            401: "Clé API invalide ou expirée",
            403: "Accès interdit — vérifiez les permissions",
            429: "Rate limit atteint — implémentez du backoff",
            500: "Erreur serveur HolySheep — réessayez",
            503: "Service temporairement indisponible"
        }
        
        message = error_types.get(status, f"Erreur HTTP {status}")
        raise RuntimeError(f"{message}: {body}")
    
    def _generate_cache_key(self, request: HolySheepRequest) -> str:
        """Génère une clé de cache pour éviter les doublons."""
        import hashlib
        content = json.dumps({
            "model": request.model,
            "messages": request.messages,
            "temperature": request.temperature
        }, sort_keys=True)
        return hashlib.sha256(content.encode()).hexdigest()
    
    async def stream_chat_completions(
        self,
        request: HolySheepRequest
    ) -> AsyncIterator[str]:
        """Streaming avec tracking différé des coûts."""
        request.stream = True
        url = f"{self.base_url}/chat/completions"
        
        payload = {
            "model": request.model,
            "messages": request.messages,
            "temperature": request.temperature,
            "max_tokens": request.max_tokens,
            "stream": True
        }
        
        async with self._semaphore:
            async with self._client.stream(
                "POST",
                url,
                json=payload,
                headers={"X-Caller-ID": request.caller_id}
            ) as response:
                response.raise_for_status()
                
                full_content = ""
                async for line in response.aiter_lines():
                    if line.startswith("data: "):
                        data = line[6:]
                        if data == "[DONE]":
                            break
                        
                        chunk = json.loads(data)
                        delta = chunk.get("choices", [{}])[0].get("delta", {}).get("content", "")
                        if delta:
                            full_content += delta
                            yield delta
                
                # Estimation des coûts après streaming
                # Note: En production, utilisez les vraies métriques d'usage
                estimated_tokens = len(full_content.split()) * 1.3
                estimated_cost = (estimated_tokens / 1_000_000) * \
                    self.cost_tracker.PRICING.get(request.model, {}).get("output", 5.0)
                
                await self.cost_tracker.track_request(
                    caller_id=request.caller_id,
                    model=request.model,
                    input_tokens=request.max_tokens // 2,
                    output_tokens=int(estimated_tokens),
                    latency_ms=0
                )
    
    async def close(self):
        """Ferme le client proprement."""
        await self._client.aclose()
        print(f"Cache stats — Hits: {self._cache_hits}, Misses: {self._cache_misses}")


Script de test avec benchmark

async def benchmark(): """Benchmark de performance du client HolySheep.""" import time client = HolySheepAIClient( api_key="YOUR_HOLYSHEEP_API_KEY", max_concurrent=5 ) test_request = HolySheepRequest( model="deepseek-v3.2", messages=[ {"role": "system", "content": "Tu es un assistant technique concis."}, {"role": "user", "content": "Explique la différence entre REST et GraphQL en 3 lignes."} ], caller_id="benchmark-test", max_tokens=150 ) # Test de latence latencies = [] for i in range(10): start = time.perf_counter() result = await client.chat_completions(test_request) latency = (time.perf_counter() - start) * 1000 latencies.append(latency) print(f"Requête {i+1}: {latency:.1f}ms, Coût: ${result.get('usage', {}).get('total_tokens', 0) / 1_000_000 * 0.42:.6f}") avg_latency = sum(latencies) / len(latencies) p95_latency = sorted(latencies)[int(len(latencies) * 0.95)] print(f"\n--- Benchmark Results ---") print(f"Latence moyenne: {avg_latency:.1f}ms") print(f"Latence P95: {p95_latency:.1f}ms") await client.close() if __name__ == "__main__": asyncio.run(benchmark())

Découpage des Coûts par Dimension

La puissance de HolySheep AI réside dans sa capacité à fournir des métriques exploitables. En structurant votre système de tracking autour de trois axes — caller, modèle et période — vous obtainez une visibilité complète sur vos patterns de consommation.

Tableau Comparatif des Modèles HolySheep 2026

Modèle Input ($/M tokens) Output ($/M tokens) Latence Typique Use Case Optimal Ratio Coût/Efficacité
DeepSeek V3.2 $0.42 $0.42 <50ms Batch processing, volume élevé ⭐⭐⭐⭐⭐ Excellent
Gemini 2.5 Flash $2.50 $2.50 <50ms Applications temps réel ⭐⭐⭐⭐ Très bon
GPT-4.1 $8.00 $8.00 <100ms Tâches complexes, coding ⭐⭐⭐ Bon
Claude Sonnet 4.5 $15.00 $15.00 <120ms Analyse, reasoning avancé ⭐⭐ Investissement premium

Système d'Alertes Budgetaires

Un budget sans alertes est une bombe à retardement. Mon implémentation inclut un système de seuils progressifs qui vous notifie avant d'atteindre des limites critiques. Avec HolySheep AI offrant un taux de change ¥1=$1 et des crédits gratuits pour les nouveaux utilisateurs, vous pouvez expérimenter sans risque avant de vous engager.

"""
HolySheep AI Budget Alert Manager
Version: 2.1.0 — Production Ready
"""

import asyncio
import json
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Protocol
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart


class AlertChannel(Enum):
    """Canaux de notification disponibles."""
    EMAIL = "email"
    WEBHOOK = "webhook"
    SLACK = "slack"
    PAGERDUTY = "pagerduty"
    CONSOLE = "console"


@dataclass
class AlertConfig:
    """Configuration d'une alerte."""
    channel: AlertChannel
    threshold_percent: float
    recipient: str  # email, webhook URL, slack channel, etc.
    enabled: bool = True
    cooldown_seconds: int = 3600  # Évite le spam d'alertes


@dataclass
class BudgetAlert:
    """Représentation d'une alerte déclenchée."""
    timestamp: datetime
    level: str
    current_spend: float
    budget_limit: float
    utilization_percent: float
    caller_id: str = "global"
    model: str = "all"


class AlertManager:
    """
    Gestionnaire d'alertes budgetaires multi-canal.
    
    Fonctionnalités:
    - Seuils configurables (50%, 75%, 90%, 100%)
    - Cooldown pour éviter le spam
    - Multi-canal (email, webhook, Slack, PagerDuty)
    - Persistance des alertes envoyées
    """
    
    def __init__(self):
        self.alerts: list[AlertConfig] = []
        self._sent_alerts: dict[str, datetime] = {}  # Clé -> Dernière envoi
        self._lock = asyncio.Lock()
    
    def add_alert(self, config: AlertConfig):
        """Ajoute une configuration d'alerte."""
        self.alerts.append(config)
    
    def setup_default_alerts(self):
        """Configure les alertes par défaut recommandées."""
        self.alerts = [
            # Alertes globales
            AlertConfig(
                channel=AlertChannel.EMAIL,
                threshold_percent=50.0,
                recipient="[email protected]",
                cooldown_seconds=86400  # 1 alert/jour max
            ),
            AlertConfig(
                channel=AlertChannel.WEBHOOK,
                threshold_percent=75.0,
                recipient="https://hooks.slack.com/services/XXX",
                cooldown_seconds=43200
            ),
            AlertConfig(
                channel=AlertChannel.SLACK,
                threshold_percent=90.0,
                recipient="#ops-alerts",
                cooldown_seconds=7200
            ),
            # Alertes critiques
            AlertConfig(
                channel=AlertChannel.PAGERDUTY,
                threshold_percent=100.0,
                recipient="BUDGET_EXCEEDED",
                cooldown_seconds=3600
            ),
        ]
    
    async def check_and_trigger(
        self,
        current_spend: float,
        budget_limit: float,
        caller_id: str = "global",
        model: str = "all"
    ) -> list[BudgetAlert]:
        """Vérifie les seuils et déclenche les alertes nécessaires."""
        
        if budget_limit <= 0:
            return []
        
        utilization = (current_spend / budget_limit) * 100
        alerts_triggered = []
        
        async with self._lock:
            for config in self.alerts:
                if not config.enabled:
                    continue
                
                if utilization >= config.threshold_percent:
                    alert = BudgetAlert(
                        timestamp=datetime.now(timezone.utc),
                        level=f"alert_{int(config.threshold_percent)}",
                        current_spend=round(current_spend, 2),
                        budget_limit=budget_limit,
                        utilization_percent=round(utilization, 2),
                        caller_id=caller_id,
                        model=model
                    )
                    
                    # Vérifie le cooldown
                    cooldown_key = self._get_cooldown_key(config, caller_id)
                    if self._can_send_alert(cooldown_key, config.cooldown_seconds):
                        await self._send_alert(config, alert)
                        self._sent_alerts[cooldown_key] = datetime.now(timezone.utc)
                        alerts_triggered.append(alert)
        
        return alerts_triggered
    
    def _get_cooldown_key(self, config: AlertConfig, caller_id: str) -> str:
        """Génère la clé de cooldown unique."""
        return f"{config.channel.value}:{config.recipient}:{caller_id}"
    
    def _can_send_alert(self, cooldown_key: str, cooldown_seconds: int) -> bool:
        """Vérifie si l'alerte peut être envoyée (hors cooldown)."""
        if cooldown_key not in self._sent_alerts:
            return True
        
        last_sent = self._sent_alerts[cooldown_key]
        elapsed = (datetime.now(timezone.utc) - last_sent).total_seconds()
        return elapsed >= cooldown_seconds
    
    async def _send_alert(self, config: AlertConfig, alert: BudgetAlert):
        """Envoie l'alerte via le canal configuré."""
        
        if config.channel == AlertChannel.EMAIL:
            await self._send_email(config.recipient, alert)
        elif config.channel == AlertChannel.WEBHOOK:
            await self._send_webhook(config.recipient, alert)
        elif config.channel == AlertChannel.SLACK:
            await self._send_slack(config.recipient, alert)
        elif config.channel == AlertChannel.CONSOLE:
            self._send_console(alert)
    
    async def _send_email(self, recipient: str, alert: BudgetAlert):
        """Envoie une alerte par email."""
        msg = MIMEMultipart()
        msg['From'] = '[email protected]'
        msg['To'] = recipient
        msg['Subject'] = f"⚠️ Alerte Budget HolySheep — {alert.utilization_percent}% atteint"
        
        body = f"""
        
        
        

🔴 Alerte Budget HolySheep AI

Niveau:{alert.level}
Dépense actuelle:${alert.current_spend:.2f}
Limite budgétaire:${alert.budget_limit:.2f}
Utilisation:{alert.utilization_percent}%
Caller:{alert.caller_id}
Modèle:{alert.model}
Timestamp:{alert.timestamp.isoformat()}

Voir le dashboard HolySheep

""" msg.attach(MIMEText(body, 'html')) # En production, utilisez un vrai serveur SMTP print(f"[EMAIL] Alerte envoyée à {recipient}: {alert.level}") async def _send_webhook(self, url: str, alert: BudgetAlert): """Envoie une alerte via webhook.""" payload = { "event": "budget_alert", "level": alert.level, "current_spend_usd": alert.current_spend, "budget_limit_usd": alert.budget_limit, "utilization_percent": alert.utilization_percent, "caller_id": alert.caller_id, "model": alert.model, "timestamp": alert.timestamp.isoformat() } async with httpx.AsyncClient() as client: try: response = await client.post( url, json=payload, timeout=10.0 ) print(f"[WEBHOOK] Alerte envoyée: {response.status_code}") except Exception as e: print(f"[WEBHOOK] Erreur: {e}") async def _send_slack(self, channel: str, alert: BudgetAlert): """Envoie une alerte Slack formatée.""" emoji = "🔴" if alert.utilization_percent >= 90 else "🟠" payload = { "channel": channel, "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": f"{emoji} Alerte Budget HolySheep AI" } }, { "type": "section", "fields": [ {"type": "mrkdwn", "text": f"*Niveau:*\n{alert.level}"}, {"type": "mrkdwn", "text": f"*Utilisation:*\n{alert.utilization_percent}%"}, {"type": "mrkdwn", "text": f"*Dép