Il est 3h47 du matin quand mon téléphone vibre violemment. Je bondis du lit, les yeux encore embués de sommeil, pour découvrir un cauchemar financier : ma console d'administration affiche une consommation de 2 847 $ en seulement 6 heures. L'erreur était simple mais dévastatrice : une boucle infinie dans mon pipeline de traitement envoyait des requêtes continues à l'API sans le moindre mécanisme de limitation ou de surveillance. Ce n'était pas un hack, ni une fuite de credentials — c'était simplement l'absence d'un système d'alerte de、成本控制 adequado.
Cette expérience m'a coûté cher, mais elle m'a appris une leçon inestimable sur la conception de systèmes robustes pour les API de grands modèles de langage. Aujourd'hui, je partage avec vous l'architecture complète que j'ai développée pour éviter ce genre de catastrophe et optimiser mes dépenses Cloud.
Comprendre les Risques Financiers des API LLM
Les API de grands modèles de langage sont facturées au token — chaque requête génère des coûts qui s'accumulent silencieusement. Avec des prix pouvant atteindre 15 $ par million de tokens pour les modèles les plus performants comme Claude Sonnet 4.5, une simple erreur de programmation peut se transformer en facture de plusieurs milliers de dollars en quelques heures.
HolySheep AI offre des tarifs avantageux avec un taux de change de ¥1 pour 1 $, représentant une économie de plus de 85% par rapport aux providers traditionnels. Leurs modèles incluent DeepSeek V3.2 à seulement 0,42 $ par million de tokens, Gemini 2.5 Flash à 2,50 $, et pour les besoins avancés, GPT-4.1 à 8 $ et Claude Sonnet 4.5 à 15 $. La latence moyenne reste inférieure à 50 millisecondes, et le support pour WeChat et Alipay facilite les paiements pour les utilisateurs internationaux.
Architecture du Système d'Alerte
Le système que je vais vous présenter s'articule autour de quatre composants principaux : un proxy API intelligent, un module de surveillance en temps réel, un système d'alerte multicanal, et une couche de limitation automatique. Commençons par l'implémentation complète.
Implémentation du Proxy API avec Monitoring
#!/usr/bin/env python3
"""
Système d'Alerte de Surconsommation API LLM
Auteur: HolySheep AI Technical Blog
Version: 2.0
"""
import time
import json
import asyncio
import logging
from datetime import datetime, timedelta
from dataclasses import dataclass, field
from typing import Optional, Dict, List, Callable
from collections import defaultdict
from threading import Lock
import hashlib
Configuration HolySheep API
HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"
@dataclass
class TokenUsage:
"""Suivi détaillé de l'utilisation des tokens"""
model: str
prompt_tokens: int
completion_tokens: int
total_tokens: int
cost: float
timestamp: datetime = field(default_factory=datetime.now)
request_id: str = ""
@dataclass
class AlertThresholds:
"""Configuration des seuils d'alerte"""
hourly_cost: float = 50.0 # Alerte si > 50$/heure
daily_cost: float = 200.0 # Alerte si > 200$/jour
request_rate: int = 100 # Alerte si > 100 req/min
burst_requests: int = 20 # Alerte si > 20 req/seconde
error_rate: float = 0.1 # Alerte si > 10% d'erreurs
class CostTracker:
"""Tracker de coûts avec alertes intelligentes"""
# Prix par million de tokens (2026)
MODEL_PRICES = {
"gpt-4.1": 8.0,
"claude-sonnet-4.5": 15.0,
"gemini-2.5-flash": 2.50,
"deepseek-v3.2": 0.42,
"holy-deepseek": 0.42,
"holy-gpt4": 8.0,
"holy-claude": 15.0
}
def __init__(self, alert_callback: Optional[Callable] = None):
self.usage_history: List[TokenUsage] = []
self.lock = Lock()
self.alert_callback = alert_callback
self.daily_cost = 0.0
self.hourly_cost = 0.0
self.alert_cooldown = 300 # 5 minutes entre alertes
# Compteurs par modèle
self.model_costs: Dict[str, float] = defaultdict(float)
self.model_requests: Dict[str, int] = defaultdict(int)
# Surveillance du taux de requêtes
self.request_timestamps: List[float] = []
self.last_alert_time = 0
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
self.logger = logging.getLogger(__name__)
def calculate_cost(self, model: str, prompt_tokens: int,
completion_tokens: int) -> float:
"""Calcule le coût exact basé sur le modèle"""
price_per_million = self.MODEL_PRICES.get(model, 1.0)
total_tokens = prompt_tokens + completion_tokens
cost = (total_tokens / 1_000_000) * price_per_million
return round(cost, 6)
def record_usage(self, model: str, prompt_tokens: int,
completion_tokens: int, request_id: str = "") -> TokenUsage:
"""Enregistre l'utilisation et vérifie les seuils"""
cost = self.calculate_cost(model, prompt_tokens, completion_tokens)
usage = TokenUsage(
model=model,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
cost=cost,
request_id=request_id or self._generate_request_id()
)
with self.lock:
self.usage_history.append(usage)
self._update_costs()
self._update_rate_tracking()
# Vérification des alertes
self._check_thresholds()
self.logger.info(
f"Usage: {model} | Tokens: {usage.total_tokens} | "
f"Cost: ${cost:.6f} | Cumulative: ${self.daily_cost:.2f}"
)
return usage
def _generate_request_id(self) -> str:
"""Génère un ID unique pour traçabilité"""
timestamp = str(time.time()).encode()
return hashlib.md5(timestamp).hexdigest()[:12]
def _update_costs(self):
"""Met à jour les compteurs de coûts"""
now = datetime.now()
cutoff_hour = now - timedelta(hours=1)
cutoff_day = now - timedelta(days=1)
# Calcul du coût horaire
hourly_usages = [u for u in self.usage_history if u.timestamp > cutoff_hour]
self.hourly_cost = sum(u.cost for u in hourly_usages)
# Calcul du coût journalier
daily_usages = [u for u in self.usage_history if u.timestamp > cutoff_day]
self.daily_cost = sum(u.cost for u in daily_usages)
# Nettoyage des vieux enregistrements
self.usage_history = daily_usages.copy()
def _update_rate_tracking(self):
"""Surveille le taux de requêtes"""
now = time.time()
self.request_timestamps = [
t for t in self.request_timestamps if now - t < 60
]
self.request_timestamps.append(now)
def _check_thresholds(self):
"""Vérifie tous les seuils d'alerte"""
now = time.time()
if now - self.last_alert_time < self.alert_cooldown:
return
alerts_triggered = []
if self.hourly_cost > AlertThresholds.hourly_cost:
alerts_triggered.append(
f"🚨 ALERTE: Coût horaire ${self.hourly_cost:.2f} "
f"(seuil: ${AlertThresholds.hourly_cost})"
)
if self.daily_cost > AlertThresholds.daily_cost:
alerts_triggered.append(
f"🚨 ALERTE: Coût journalier ${self.daily_cost:.2f} "
f"(seuil: ${AlertThresholds.daily_cost})"
)
request_rate = len(self.request_timestamps)
if request_rate > AlertThresholds.request_rate:
alerts_triggered.append(
f"⚠️ ALERTE: Taux de requêtes {request_rate}/min "
f"(seuil: {AlertThresholds.request_rate})"
)
if alerts_triggered and self.alert_callback:
for alert in alerts_triggered:
self.logger.warning(alert)
self.alert_callback(alert)
self.last_alert_time = now
def get_cost_breakdown(self) -> Dict:
"""Retourne un rapport détaillé des coûts"""
with self.lock:
return {
"hourly_cost": round(self.hourly_cost, 4),
"daily_cost": round(self.daily_cost, 4),
"total_requests": len(self.usage_history),
"by_model": {
model: {
"cost": round(cost, 4),
"requests": self.model_requests[model]
}
for model, cost in self.model_costs.items()
},
"avg_cost_per_request": round(
self.daily_cost / len(self.usage_history)
if self.usage_history else 0, 6
)
}
def emergency_stop(self) -> bool:
"""Active le blocage d'urgence"""
self.logger.critical("🚨 ARRÊT D'URGENCE ACTIVÉ - Toutes les requêtes bloquées")
return True
def reset_limits(self):
"""Réinitialise les compteurs"""
with self.lock:
self.usage_history.clear()
self.model_costs.clear()
self.model_requests.clear()
self.hourly_cost = 0.0
self.daily_cost = 0.0
self.logger.info("Compteurs réinitialisés")
Démonstration
if __name__ == "__main__":
def handle_alert(message: str):
print(f"📱 ALERTE ENVOYÉE: {message}")
tracker = CostTracker(alert_callback=handle_alert)
# Simulation d'appels API
test_requests = [
("deepseek-v3.2", 1500, 300),
("gemini-2.5-flash", 2000, 450),
("deepseek-v3.2", 1800, 400),
("holy-gpt4", 2500, 600),
]
for model, prompt, completion in test_requests:
tracker.record_usage(model, prompt, completion)
print("\n📊 Rapport de coûts:")
print(json.dumps(tracker.get_cost_breakdown(), indent=2))
Client API avec Rate Limiting Intelligent
#!/usr/bin/env python3
"""
Client API LLM avec Rate Limiting et Retry Intelligent
Compatible HolySheep AI
"""
import os
import time
import asyncio
from typing import Dict, Any, Optional, List
from dataclasses import dataclass
from datetime import datetime, timedelta
import logging
import aiohttp
from abc import ABC, abstractmethod
@dataclass
class APIResponse:
"""Structure de réponse standardisée"""
success: bool
data: Optional[Dict] = None
error: Optional[str] = None
tokens_used: int = 0
cost: float = 0.0
latency_ms: float = 0.0
model: str = ""
class RateLimiter:
"""Rate limiter avec tokens bucket et burst control"""
def __init__(self, requests_per_minute: int = 60,
burst_limit: int = 10):
self.rpm = requests_per_minute
self.burst_limit = burst_limit
self.tokens = burst_limit
self.last_update = time.time()
self.retry_after: Optional[float] = None
self.request_times: List[float] = []
def acquire(self, blocking: bool = True) -> bool:
"""Acquiert un token, attend si nécessaire"""
now = time.time()
# Nettoyage des requêtes anciennes
self.request_times = [t for t in self.request_times if now - t < 60]
if len(self.request_times) >= self.rpm:
if not blocking:
return False
# Calcul du temps d'attente
oldest = min(self.request_times)
wait_time = 60 - (now - oldest)
if wait_time > 0:
time.sleep(wait_time)
self.request_times = [t for t in self.request_times if now - t < 60]
if self.tokens < 1:
if not blocking:
return False
# Attente pour un token
time.sleep(1.0 / self.rpm)
self.tokens = min(self.burst_limit, self.tokens + (time.time() - self.last_update) * (self.rpm / 60))
self.tokens -= 1
self.last_update = time.time()
self.request_times.append(now)
return True
def get_wait_time(self) -> float:
"""Retourne le temps d'attente estimé en secondes"""
now = time.time()
self.request_times = [t for t in self.request_times if now - t < 60]
if len(self.request_times) < self.rpm:
return 0.0
oldest = min(self.request_times)
return max(0.0, 60 - (now - oldest))
class LLMAPIClient:
"""Client complet pour HolySheep AI avec surveillance des coûts"""
def __init__(self, api_key: str, base_url: str = HOLYSHEEP_BASE_URL,
rate_limiter: Optional[RateLimiter] = None,
cost_tracker: Optional[Any] = None,
max_retries: int = 3,
timeout: int = 60):
self.api_key = api_key
self.base_url = base_url.rstrip('/')
self.rate_limiter = rate_limiter or RateLimiter()
self.cost_tracker = cost_tracker
self.max_retries = max_retries
self.timeout = timeout
self.emergency_stop = False
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO)
# Session aiohttp pour performances
self._session: Optional[aiohttp.ClientSession] = None
async def _get_session(self) -> aiohttp.ClientSession:
"""Obtient ou crée une session aiohttp"""
if self._session is None or self._session.closed:
timeout = aiohttp.ClientTimeout(total=self.timeout)
self._session = aiohttp.ClientSession(timeout=timeout)
return self._session
async def chat_completion(
self,
model: str,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 2048,
**kwargs
) -> APIResponse:
"""Appel principal avec gestion complète des erreurs"""
if self.emergency_stop:
return APIResponse(
success=False,
error="EMERGENCY_STOP: Toutes les requêtes sont bloquées"
)
# Acquisition du rate limit
if not self.rate_limiter.acquire(blocking=True):
return APIResponse(
success=False,
error="RATE_LIMIT: Impossible d'acquérir un token"
)
start_time = time.time()
last_error = None
for attempt in range(self.max_retries):
try:
session = await self._get_session()
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
**kwargs
}
url = f"{self.base_url}/chat/completions"
async with session.post(url, json=payload,
headers=headers) as response:
latency = (time.time() - start_time) * 1000
if response.status == 200:
data = await response.json()
# Enregistrement du coût
usage = data.get("usage", {})
prompt_tokens = usage.get("prompt_tokens", 0)
completion_tokens = usage.get("completion_tokens", 0)
if self.cost_tracker:
usage_record = self.cost_tracker.record_usage(
model=model,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens
)
cost = usage_record.cost
else:
cost = 0.0
self.logger.info(
f"✅ {model} | Latence: {latency:.0f}ms | "
f"Tokens: {prompt_tokens + completion_tokens} | "
f"Coût: ${cost:.6f}"
)
return APIResponse(
success=True,
data=data,
tokens_used=prompt_tokens + completion_tokens,
cost=cost,
latency_ms=latency,
model=model
)
elif response.status == 401:
return APIResponse(
success=False,
error="AUTH_ERROR: Clé API invalide ou expirée",
latency_ms=latency
)
elif response.status == 429:
retry_after = response.headers.get('Retry-After', '5')
wait_time = float(retry_after) * (2 ** attempt)
self.logger.warning(
f"Rate limit atteint, attente {wait_time:.1f}s"
)
await asyncio.sleep(min(wait_time, 30))
continue
elif response.status == 500:
last_error = f"SERVER_ERROR: {response.status}"
await asyncio.sleep(2 ** attempt)
continue
else:
error_text = await response.text()
last_error = f"HTTP_{response.status}: {error_text}"
break
except asyncio.TimeoutError:
last_error = f"TIMEOUT: Délai dépassé ({self.timeout}s)"
self.logger.error(f"Timeout lors de l'appel API (tentative {attempt + 1})")
except aiohttp.ClientError as e:
last_error = f"CONNECTION_ERROR: {str(e)}"
self.logger.error(f"Erreur de connexion: {e}")
except Exception as e:
last_error = f"UNEXPECTED_ERROR: {str(e)}"
self.logger.exception("Erreur inattendue")
break
return APIResponse(
success=False,
error=last_error or "Erreur inconnue",
latency_ms=(time.time() - start_time) * 1000
)
async def batch_completion(
self,
requests: List[Dict[str, Any]],
concurrency: int = 5
) -> List[APIResponse]:
"""Traitement par lots avec limitation de concurrence"""
semaphore = asyncio.Semaphore(concurrency)
async def process_single(req: Dict) -> APIResponse:
async with semaphore:
return await self.chat_completion(**req)
tasks = [process_single(req) for req in requests]
return await asyncio.gather(*tasks)
async def close(self):
"""Fermeture propre de la session"""
if self._session and not self._session.closed:
await self._session.close()
def enable_emergency_stop(self):
"""Active l'arrêt d'urgence"""
self.emergency_stop = True
self.logger.critical("🛑 Arrêt d'urgence activé")
def disable_emergency_stop(self):
"""Désactive l'arrêt d'urgence"""
self.emergency_stop = False
self.logger.info("✅ Arrêt d'urgence désactivé")
Exemple d'utilisation avec HolySheep
async def example_usage():
"""Exemple complet d'utilisation du client"""
api_key = os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
def alert_handler(message: str):
print(f"📱 NOTIFICATION: {message}")
cost_tracker = CostTracker(alert_callback=alert_handler)
rate_limiter = RateLimiter(requests_per_minute=30, burst_limit=5)
client = LLMAPIClient(
api_key=api_key,
rate_limiter=rate_limiter,
cost_tracker=cost_tracker
)
try:
messages = [
{"role": "system", "content": "Tu es un assistant expert en optimisation de coûts."},
{"role": "user", "content": "Explique comment réduire les coûts d'API LLM."}
]
response = await client.chat_completion(
model="deepseek-v3.2",
messages=messages,
temperature=0.7,
max_tokens=1000
)
if response.success:
print(f"✅ Réponse reçue en {response.latency_ms:.0f}ms")
print(f"💰 Coût: ${response.cost:.6f}")
print(f"📊 Contenu: {response.data['choices'][0]['message']['content'][:200]}...")
else:
print(f"❌ Erreur: {response.error}")
finally:
await client.close()
print("\n📊 Coûts totaux:", json.dumps(cost_tracker.get_cost_breakdown(), indent=2))
if __name__ == "__main__":
asyncio.run(example_usage())
Système d'Alerte Multi-Canal
#!/usr/bin/env python3
"""
Système d'Alerte Multi-Canal avec Actions Automatisées
Support: Email, Webhook, SMS, Telegram, WeChat
"""
import os
import smtplib
import asyncio
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional, Callable
from datetime import datetime
from abc import ABC, abstractmethod
import json
import logging
from enum import Enum
class AlertSeverity(Enum):
INFO = "info"
WARNING = "warning"
ERROR = "error"
CRITICAL = "critical"
@dataclass
class Alert:
"""Structure d'une alerte"""
severity: AlertSeverity
title: str
message: str
timestamp: datetime = field(default_factory=datetime.now)
metadata: Dict[str, Any] = field(default_factory=dict)
cost_impact: float = 0.0
resolved: bool = False
class AlertChannel(ABC):
"""Classe de base pour les canaux d'alerte"""
@abstractmethod
async def send(self, alert: Alert) -> bool:
"""Envoie l'alerte"""
pass
@abstractmethod
def is_configured(self) -> bool:
"""Vérifie si le canal est configuré"""
pass
class EmailAlertChannel(AlertChannel):
"""Alertes par email avec templates HTML"""
def __init__(self, smtp_host: str, smtp_port: int,
username: str, password: str,
from_addr: str, to_addrs: List[str]):
self.smtp_host = smtp_host
self.smtp_port = smtp_port
self.username = username
self.password = password
self.from_addr = from_addr
self.to_addrs = to_addrs
def is_configured(self) -> bool:
return bool(self.smtp_host and self.username and self.password)
def _get_html_template(self, alert: Alert) -> str:
"""Template HTML pour l'email"""
severity_colors = {
AlertSeverity.INFO: "#17a2b8",
AlertSeverity.WARNING: "#ffc107",
AlertSeverity.ERROR: "#dc3545",
AlertSeverity.CRITICAL: "#8b0000"
}
color = severity_colors.get(alert.severity, "#6c757d")
return f"""
🚨 {alert.title}
Severité: {alert.severity.value.upper()}
Heure: {alert.timestamp.strftime('%Y-%m-%d %H:%M:%S UTC')}
{alert.message}
{f'💰 Impact financier: ${alert.cost_impact:.2f}' if alert.cost_impact > 0 else ''}
{f'{json.dumps(alert.metadata, indent=2)}' if alert.metadata else ''}
"""
async def send(self, alert: Alert) -> bool:
if not self.is_configured():
return False
try:
msg = MIMEMultipart('alternative')
msg['Subject'] = f"[{alert.severity.value.upper()}] {alert.title}"
msg['From'] = self.from_addr
msg['To'] = ', '.join(self.to_addrs)
html_content = self._get_html_template(alert)
msg.attach(MIMEText(html_content, 'html'))
# Envoi synchrone dans un executor
loop = asyncio.get_event_loop()
await loop.run_in_executor(None, self._send_sync, msg)
logging.info(f"Email envoyé à {self.to_addrs}")
return True
except Exception as e:
logging.error(f"Erreur envoi email: {e}")
return False
def _send_sync(self, msg):
with smtplib.SMTP(self.smtp_host, self.smtp_port) as server:
server.starttls()
server.login(self.username, self.password)
server.send_message(msg)
class WebhookAlertChannel(AlertChannel):
"""Alertes via webhook HTTP (Slack, Discord, custom)"""
def __init__(self, url: str, headers: Optional[Dict] = None,
timeout: int = 10):
self.url = url
self.headers = headers or {"Content-Type": "application/json"}
self.timeout = timeout
def is_configured(self) -> bool:
return bool(self.url)
def _format_slack_message(self, alert: Alert) -> Dict:
"""Format Slack Block Kit"""
severity_emoji = {
AlertSeverity.INFO: "ℹ️",
AlertSeverity.WARNING: "⚠️",
AlertSeverity.ERROR: "❌",
AlertSeverity.CRITICAL: "🚨"
}
emoji = severity_emoji.get(alert.severity, "📢")
blocks = [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"{emoji} {alert.title}"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": f"*Severity:*\n{alert.severity.value}"},
{"type": "mrkdwn", "text": f"*Time:*\n{alert.timestamp.isoformat()}"}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": alert.message
}
}
]
if alert.cost_impact > 0:
blocks.append({
"type": "section",
"fields": [{
"type": "mrkdwn",
"text": f"*💰 Cost Impact:*\n${alert.cost_impact:.2f}"
}]
})
return {"blocks": blocks}
async def send(self, alert: Alert) -> bool:
if not self.is_configured():
return False
try:
import aiohttp
payload = self._format_slack_message(alert)
async with aiohttp.ClientSession() as session:
async with session.post(
self.url,
json=payload,
headers=self.headers,
timeout=aiohttp.ClientTimeout(total=self.timeout)
) as response:
if response.status < 300:
logging.info(f"Webhook envoyé: {self.url}")
return True
else:
logging.error(f"Webhook error: {response.status}")
return False
except Exception as e:
logging.error(f"Erreur webhook: {e}")
return False
class TelegramAlertChannel(AlertChannel):
"""Alertes via Telegram Bot"""
def __init__(self, bot_token: str, chat_id: str):
self.bot_token = bot_token
self.chat_id = chat_id
self.api_url = f"https://api.telegram.org/bot{bot_token}"
def is_configured(self) -> bool:
return bool(self.bot_token and self.chat_id)
async def send(self, alert: Alert) -> bool:
if not self.is_configured():
return False
try:
import aiohttp
emoji = "🔵" if alert.severity == AlertSeverity.INFO else \
"🟡" if alert.severity == AlertSeverity.WARNING else \
"🔴" if alert.severity == AlertSeverity.ERROR else \
"🚨"
message = f"""
{emoji} {alert.title}
Severity: {alert.severity.value.upper()}
Time: {alert.timestamp.strftime('%Y-%m-%d %H:%M:%S')}
{alert.message}
{f"💰 Cost Impact: ${alert.cost_impact:.2f}" if alert.cost_impact > 0 else ""}
"""
url = f"{self.api_url}/sendMessage"
payload = {
"chat_id": self.chat_id,
"text": message,
"parse_mode": "HTML"
}
async with aiohttp.ClientSession() as session:
async with session.post(url, json=payload) as response:
return response.status == 200
except Exception as e:
logging.error(f"Erreur Telegram: {e}")
return False
class AlertManager:
"""Gestionnaire centralisé des alertes"""
def __init__(self):
self.channels: List[AlertChannel] = []
self.alert_history: List[Alert] = []
self.auto_actions: Dict[AlertSeverity, List[Callable]] = {
severity: [] for severity in AlertSeverity
}
self.logger = logging.getLogger(__name__)
def add_channel(self, channel: AlertChannel):
"""Ajoute un canal d'alerte"""
if channel.is_configured():
self.channels.append(channel)
self.logger.info(f"Canal ajouté: {channel.__class__.__name__}")
def register_auto_action(self, severity: AlertSeverity,
action: Callable[[Alert], None]):
"""Enregistre une action automatique pour une sévérité"""
self.auto_actions[severity].append(action)
async def send_alert(self, severity: AlertSeverity, title: str,
message: str, metadata: Optional[Dict] = None,
cost_impact: float = 0.0):
"""Envoie une alerte sur tous les canaux"""
alert = Alert(
severity=severity,
title=title,
message=message,
metadata=metadata or {},
cost_impact=cost_impact
)
self.alert_history.append(alert)
# Exécution des actions automatiques
for action in self.auto_actions.get(severity, []):
try:
action(alert)
except Exception as e:
self.logger.error(f"Erreur action auto: {e}")
# Envoi sur tous les canaux
results = await asyncio.gather(
*[channel.send(alert) for channel in self.channels],
return_exceptions=True
)
success_count = sum(1 for r in results if r is True)
self.logger.info(
f"Alerte envoyée: {title} ({success_count}/{len(self.channels)} canaux)"
)
return alert
def get_alert_summary(self, hours: int = 24) -> Dict:
"""Résumé des alertes des dernières heures"""
cutoff = datetime.now() - timedelta(hours=hours)
recent = [a for a in self.alert_history if a.timestamp > cutoff]
return {
"total": len(recent),
"by_severity": {
s.value: len([a for a in recent if a.severity == s])
for s in AlertSeverity
},
"total_cost_impact": sum(a.cost_impact for a in recent)
}
Configuration exemple
async def setup_alert_system():
"""Configuration complète du système d'alertes"""
manager = AlertManager()
# Email
email_channel = EmailAlertChannel(
smtp_host=os.environ.get("SMTP_HOST", "smtp.gmail.com"),
smtp_port=int(os.environ.get("SMTP_PORT", "587")),
username=os.environ.get("SMTP_USER"),
password=os.environ.get("SMTP_PASS"),
from_addr="[email protected]",
to_addrs=["[email protected]", "[email protected]"]
)
manager.add_channel(email_channel)
# Slack/Discord Webhook
webhook_channel = WebhookAlertChannel(
url=os.environ.get("SLACK_WEBHOOK_URL", ""),
headers={"Content-Type": "application/json"}
)
manager.add_channel(webhook_channel)
# Telegram
telegram_channel = TelegramAlertChannel(
bot_token=os.environ.get("TELEGRAM_BOT_TOKEN", ""),
chat_id=os.environ.get("