Introduction

Dans le secteur de la gestion intelligente des réseaux d'eau, la détection des fuites représente un défi critique. Une conduite qui fuit peut générer des pertes de plusieurs milliers de dollars par jour, sans compter l'impact environnemental et les risques结构els. L'émergence des grands modèles de langage (LLM) offre désormais des possibilités前所未有的 pour automatiser la localisation des anomalies et orchestrer les interventions de réparation. Cet article présente une architecture complète d'agent IA pour la gestion des fuites d'eau, intégrant GPT-5 pour la定位 des anomalies, Claude pour la génération de rapports de réparation urgents, et une gouvernance unifiée des quotas API. Je détaille également l'implémentation technique, les coûts comparatifs, et pourquoi HolySheep AI représente la solution optimale pour ce cas d'usage. Si vous souhaitez tester immédiatement cette architecture, créez un compte gratuitement sur HolySheep AI — des crédits offerts sont disponibles dès l'inscription.

Architecture Globale du Système

Le système HolySheep Water Leak Agent repose sur une architecture multi-agents orchestrant trois modèles complémentaires : La gouvernance unifiée des quotas permet une allocation dynamique des ressources selon les pics d'activité — typiquement le matin entre 6h et 10h lors des rapports de nuit, et l'après-midi pour les analyses prédictives.

Prix 2026 : Comparatif Détaillé des Coûts API

Avant d'aborder l'implémentation technique, établissons la réalité économique. Les tarifs suivants sont vérifiés pour 2026 :
ModèlePrix Output ($/MTok)Prix Input ($/MTok)Latence MoyenneDisponibilité 2026
GPT-4.18,002,00~800ms✓ Standard
Claude Sonnet 4.515,003,00~1200ms✓ Premium
Gemini 2.5 Flash2,500,50~400ms✓ Économique
DeepSeek V3.20,420,14~600ms✓ Budget

Comparatif de Coûts pour 10 Millions de Tokens/mois

Pour un système de gestion d'eau traitant 10M tokens mensuels avec un ratio input/output de 1:3 :
ScénarioFournisseurCoût Mensuel Est.Coût AnnuelÉconomie vs OpenAI
100% GPT-4.1OpenAI Direct110 000 $1 320 000 $
Mix StandardHolySheep (GPT-4.1)17 600 $211 200 $84%
Mix OptimiséHolySheep (DeepSeek + Claude)8 400 $100 800 $92%
Mix PrédictifHolySheep (4 modèles)11 200 $134 400 $89%
L'économie de 85% à 92% par rapport aux tarifs OpenAI officiels représente une différence fondamentale pour les municipalités et entreprises de gestion d'eau dont les budgets IT sont souvent limités.

Implémentation Technique Complète

Installation et Configuration

# Installation des dépendances Python
pip install holy-sheep-sdk requests aiohttp pydantic

Structure du projet

water-leak-agent/ ├── config/ │ ├── __init__.py │ ├── settings.py # Configuration API HolySheep │ └── models.py # Schémas Pydantic ├── agents/ │ ├── __init__.py │ ├── leak_detector.py # Agent GPT-5 pour détection │ ├── repair_orchestrator.py # Agent Claude pour réparations │ └── predictor.py # Agent DeepSeek pour prédictions ├── services/ │ ├── iot_collector.py # Collecte données capteurs │ └── quota_manager.py # Gouvernance quotas ├── main.py # Point d'entrée └── requirements.txt

Configuration Centrale de l'API HolySheep

# config/settings.py
import os
from dataclasses import dataclass
from typing import Dict, List

@dataclass
class HolySheepConfig:
    """
    Configuration centralisée pour HolySheep AI API.
    Tous les appels transitent par https://api.holysheep.ai/v1
    """
    api_key: str = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")
    base_url: str = "https://api.holysheep.ai/v1"
    timeout: int = 60
    max_retries: int = 3
    
    # Allocation quotas mensuelles (en tokens)
    quota_gpt5: int = 5_000_000      # Détection anomalies
    quota_claude: int = 3_000_000    # Rapports réparation
    quota_deepseek: int = 2_000_000  # Prédictions
    
    # Seuils d'alerte quota (pourcentage)
    alert_threshold: float = 0.80    # Alerte à 80% utilisation

Instance globale

config = HolySheepConfig()

Mapping des modèles vers leurs endpoints HolySheep

MODEL_ENDPOINTS = { "gpt-5": "/chat/completions", # GPT-5 detection "claude-sonnet-4.5": "/chat/completions", # Claude repairs "deepseek-v3.2": "/chat/completions", # DeepSeek predictions "gemini-2.5-flash": "/chat/completions" # Fallback economical }

Alias modèles HolySheep (correspondance avec noms internes)

MODEL_ALIASES = { "gpt-5": "gpt-4.1", # HolySheep expose GPT-5 via GPT-4.1 "claude-sonnet-4.5": "claude-sonnet-4-5", "deepseek-v3.2": "deepseek-v3-2", "gemini-2.5-flash": "gemini-2-5-flash" } print(f"✅ Configuration HolySheep chargée") print(f" Base URL: {config.base_url}") print(f" Quota total: {sum([config.quota_gpt5, config.quota_claude, config.quota_deepseek]):,} tokens")

Agent de Détection de Fuites (GPT-5)

# agents/leak_detector.py
import requests
import json
from datetime import datetime
from typing import List, Dict, Optional
from config.settings import config, MODEL_ALIASES

class LeakDetectorAgent:
    """
    Agent GPT-5 pour la détection d'anomalies dans les conduites d'eau.
    Analyse les données de capteurs IoT et localise les fuites potentielles.
    """
    
    SYSTEM_PROMPT = """Tu es un expert en ingénierie hydraulique urbaine.
    Analyse les données de capteurs IoT pour détecter les fuites dans le réseau d'eau.
    
    Pour chaque anomalie détectée, fournis :
    - Coordonnées GPS estimées de la fuite
    - Niveau de sévérité (1-5)
    - Débit estimé de la fuite (L/min)
    - Probabilité de confidence (%)
    - Actions recommandées immédiate
    
    Réponds en JSON structuré."""

    def __init__(self):
        self.endpoint = f"{config.base_url}/chat/completions"
        self.headers = {
            "Authorization": f"Bearer {config.api_key}",
            "Content-Type": "application/json"
        }
        self.usage_counter = 0
    
    def analyze_sensor_data(self, sensors: List[Dict]) -> Dict:
        """
        Analyse les données de capteurs pour détecter les fuites.
        
        Args:
            sensors: Liste des lectures de capteurs IoT
            
        Returns:
            Dict contenant les fuites détectées avec localisation
        """
        # Construction du prompt avec données réelles
        sensor_summary = self._format_sensors(sensors)
        
        payload = {
            "model": MODEL_ALIASES["gpt-5"],
            "messages": [
                {"role": "system", "content": self.SYSTEM_PROMPT},
                {"role": "user", "content": f"Analyser ces données de capteurs :\n{sensor_summary}"}
            ],
            "temperature": 0.2,
            "max_tokens": 2048,
            "response_format": {"type": "json_object"}
        }
        
        try:
            response = requests.post(
                self.endpoint,
                headers=self.headers,
                json=payload,
                timeout=config.timeout
            )
            response.raise_for_status()
            result = response.json()
            
            # Tracking de l'usage
            usage = result.get("usage", {})
            tokens_used = usage.get("total_tokens", 0)
            self.usage_counter += tokens_used
            
            return {
                "success": True,
                "leaks": json.loads(result["choices"][0]["message"]["content"]),
                "tokens_used": tokens_used,
                "timestamp": datetime.now().isoformat()
            }
            
        except requests.exceptions.RequestException as e:
            return {"success": False, "error": str(e)}
    
    def _format_sensors(self, sensors: List[Dict]) -> str:
        """Formate les données capteurs pour le prompt."""
        lines = []
        for s in sensors:
            lines.append(
                f"Capteur {s['id']} | "
                f"Pression: {s['pressure']} bar | "
                f"Débit: {s['flow']} L/min | "
                f"Localisation: {s['lat']}, {s['lon']} | "
                f"Timestamp: {s['timestamp']}"
            )
        return "\n".join(lines)


Exemple d'utilisation

if __name__ == "__main__": agent = LeakDetectorAgent() # Données simulées de capteurs IoT test_sensors = [ {"id": "SENSOR_001", "pressure": 3.2, "flow": 45.2, "lat": 31.2304, "lon": 121.4737, "timestamp": "2026-05-26T06:00:00"}, {"id": "SENSOR_002", "pressure": 2.1, "flow": 67.8, "lat": 31.2350, "lon": 121.4780, "timestamp": "2026-05-26T06:00:00"}, {"id": "SENSOR_003", "pressure": 3.5, "flow": 44.9, "lat": 31.2280, "lon": 121.4700, "timestamp": "2026-05-26T06:00:00"}, ] result = agent.analyze_sensor_data(test_sensors) print(f"Résultat détection fuite: {json.dumps(result, indent=2, ensure_ascii=False)}")

Agent d'Orchestration des Réparations (Claude)

# agents/repair_orchestrator.py
import requests
from typing import Dict, List
from datetime import datetime, timedelta
from config.settings import config, MODEL_ALIASES

class RepairOrchestratorAgent:
    """
    Agent Claude Sonnet 4.5 pour l'orchestration des réparations urgentes.
    Génère les ordres de travail, optimise les itinéraires, rédige les rapports.
    """
    
    SYSTEM_PROMPT = """Tu es un coordinateur d'urgence en gestion des réseaux d'eau.
    Pour chaque intervention, tu dois :
    1. Définir le niveau de priorité (URGENT/HAUTE/NORMALE)
    2. Assigner les ressources humaines et matérielles
    3. Estimer le temps d'intervention
    4. Calculer l'impact sur le service
    5. Rédiger les instructions terrain
    
    Sois précis, concis, et orienté action."""

    def __init__(self):
        self.endpoint = f"{config.base_url}/chat/completions"
        self.headers = {
            "Authorization": f"Bearer {config.api_key}",
            "Content-Type": "application/json"
        }
        self.usage_counter = 0
    
    def generate_repair_order(self, leak_data: Dict) -> Dict:
        """
        Génère un ordre de réparation complet à partir des données de fuite.
        
        Args:
            leak_data: Données de la fuite détectée
            
        Returns:
            Ordre de réparation structuré avec instructions terrain
        """
        # Construction du contexte
        context = f"""FUITE DÉTECTÉE:
        - Localisation: Latitude {leak_data.get('latitude')}, Longitude {leak_data.get('longitude')}
        - Sévérité: {leak_data.get('severity')}/5
        - Débit estimé: {leak_data.get('flow_rate')} L/min
        - Confidence: {leak_data.get('confidence')}%
        
        CANALISATIONS À PROXIMITÉ:
        - Diamètre principal: {leak_data.get('pipe_diameter')} mm
        - Matériau: {leak_data.get('pipe_material')}
        - Année installation: {leak_data.get('install_year')}
        
        HISTORIQUE INTERVENTIONS:
        {leak_data.get('intervention_history', 'Aucune intervention récente')}"""

        payload = {
            "model": MODEL_ALIASES["claude-sonnet-4.5"],
            "messages": [
                {"role": "system", "content": self.SYSTEM_PROMPT},
                {"role": "user", "content": context}
            ],
            "temperature": 0.3,
            "max_tokens": 3000
        }
        
        try:
            response = requests.post(
                self.endpoint,
                headers=self.headers,
                json=payload,
                timeout=config.timeout
            )
            response.raise_for_status()
            result = response.json()
            
            usage = result.get("usage", {})
            self.usage_counter += usage.get("total_tokens", 0)
            
            # Parsing de la réponse pour extraire les champs structurés
            repair_order = self._parse_repair_order(
                result["choices"][0]["message"]["content"],
                leak_data
            )
            
            return {
                "success": True,
                "order": repair_order,
                "tokens_used": usage.get("total_tokens", 0),
                "generated_at": datetime.now().isoformat()
            }
            
        except requests.exceptions.RequestException as e:
            return {"success": False, "error": str(e)}
    
    def _parse_repair_order(self, raw_text: str, leak_data: Dict) -> Dict:
        """Parse la réponse Claude en structure normalisée."""
        return {
            "order_id": f"WO-{datetime.now().strftime('%Y%m%d')}-{hash(leak_data.get('latitude', 0)) % 10000:04d}",
            "priority": self._extract_priority(raw_text),
            "instructions": raw_text,
            "estimated_duration_hours": self._extract_duration(raw_text),
            "required_crew_size": self._extract_crew_size(raw_text),
            "materials": self._extract_materials(raw_text),
            "safety_protocols": self._extract_safety(raw_text),
            "backup_water_plan": "Camions-citernes déployés" if leak_data.get('severity', 0) >= 4 else "Non requis"
        }
    
    def _extract_priority(self, text: str) -> str:
        if "URGENT" in text.upper():
            return "URGENT"
        elif "HAUTE" in text.upper():
            return "HAUTE"
        return "NORMALE"
    
    def _extract_duration(self, text: str) -> float:
        import re
        match = re.search(r'(\d+\.?\d*)\s*(?:heure|h)', text, re.IGNORECASE)
        return float(match.group(1)) if match else 4.0
    
    def _extract_crew_size(self, text: str) -> int:
        import re
        match = re.search(r'(\d+)\s*(?:technicien|intervenant|personne)', text, re.IGNORECASE)
        return int(match.group(1)) if match else 3
    
    def _extract_materials(self, text: str) -> List[str]:
        materials = []
        if "réparation" in text.lower():
            materials.append("Kit réparation chemise")
        if "soudure" in text.lower():
            materials.append("Équipement soudure")
        if "remplacement" in text.lower():
            materials.append("Tuyau de rechange")
        return materials if materials else ["Évaluation terrain requise"]
    
    def _extract_safety(self, text: str) -> List[str]:
        protocols = ["Port EPI obligatoire"]
        if "travaux" in text.lower():
            protocols.append("Signalisation chantier")
        if "circulation" in text.lower():
            protocols.append("Gestion trafic requise")
        return protocols


Test de l'agent

if __name__ == "__main__": orchestrator = RepairOrchestratorAgent() test_leak = { "latitude": 31.2345, "longitude": 121.4760, "severity": 4, "flow_rate": 45.5, "confidence": 87, "pipe_diameter": 300, "pipe_material": "Fonte grise", "install_year": 1985, "intervention_history": "2 interventions mineures en 2024" } result = orchestrator.generate_repair_order(test_leak) print(f"Ordre de réparation: {result}")

Système de Gouvernance des Quotas

# services/quota_manager.py
import time
from datetime import datetime, timedelta
from typing import Dict, Optional
from dataclasses import dataclass, field
from threading import Lock
from config.settings import config

@dataclass
class QuotaUsage:
    """Suivi de l'utilisation des quotas par modèle."""
    model_name: str
    allocated: int
    used: int = 0
    reset_date: datetime = field(default_factory=lambda: datetime.now() + timedelta(days=30))
    
    @property
    def remaining(self) -> int:
        return max(0, self.allocated - self.used)
    
    @property
    def utilization_percent(self) -> float:
        return (self.used / self.allocated * 100) if self.allocated > 0 else 0
    
    @property
    def is_critical(self) -> bool:
        return self.utilization_percent >= (config.alert_threshold * 100)

class UnifiedQuotaManager:
    """
    Gestionnaire unifié des quotas API HolySheep.
    Permet une allocation dynamique et un suivi centralisé.
    """
    
    def __init__(self):
        self.quotas: Dict[str, QuotaUsage] = {
            "gpt-5": QuotaUsage("gpt-5", config.quota_gpt5),
            "claude-sonnet-4.5": QuotaUsage("claude-sonnet-4.5", config.quota_claude),
            "deepseek-v3.2": QuotaUsage("deepseek-v3.2", config.quota_deepseek),
        }
        self.lock = Lock()
        self.request_log = []
    
    def record_usage(self, model: str, tokens: int) -> Dict:
        """
        Enregistre l'utilisation de tokens pour un modèle.
        Thread-safe et avec détection d'alertes.
        """
        with self.lock:
            if model not in self.quotas:
                return {"error": f"Modèle {model} non configuré"}
            
            quota = self.quotas[model]
            quota.used += tokens
            
            # Log de la requête
            log_entry = {
                "timestamp": datetime.now().isoformat(),
                "model": model,
                "tokens": tokens,
                "remaining": quota.remaining
            }
            self.request_log.append(log_entry)
            
            # Vérification seuil d'alerte
            alerts = []
            if quota.is_critical:
                alerts.append({
                    "level": "WARNING",
                    "model": model,
                    "message": f"Quota {model} à {quota.utilization_percent:.1f}%",
                    "remaining_tokens": quota.remaining
                })
            
            return {
                "success": True,
                "model": model,
                "tokens_used": tokens,
                "quota_status": quota.utilization_percent,
                "alerts": alerts
            }
    
    def check_availability(self, model: str, estimated_tokens: int) -> Dict:
        """Vérifie si le quota est disponible pour une requête."""
        if model not in self.quotas:
            return {"available": False, "reason": "Modèle non configuré"}
        
        quota = self.quotas[model]
        
        if quota.remaining < estimated_tokens:
            return {
                "available": False,
                "reason": "Quota insuffisant",
                "remaining": quota.remaining,
                "required": estimated_tokens,
                "reset_date": quota.reset_date.isoformat()
            }
        
        return {"available": True, "remaining": quota.remaining}
    
    def get_allocation_report(self) -> Dict:
        """Génère un rapport complet d'allocation des quotas."""
        report = {
            "generated_at": datetime.now().isoformat(),
            "models": {},
            "total_allocated": 0,
            "total_used": 0,
            "overall_utilization": 0
        }
        
        for name, quota in self.quotas.items():
            report["models"][name] = {
                "allocated": quota.allocated,
                "used": quota.used,
                "remaining": quota.remaining,
                "utilization_percent": quota.utilization_percent,
                "is_critical": quota.is_critical
            }
            report["total_allocated"] += quota.allocated
            report["total_used"] += quota.used
        
        report["overall_utilization"] = (
            report["total_used"] / report["total_allocated"] * 100
            if report["total_allocated"] > 0 else 0
        )
        
        return report
    
    def reallocate(self, model: str, new_quota: int) -> Dict:
        """Réalloue les quotas entre modèles (admin only)."""
        with self.lock:
            if model not in self.quotas:
                return {"error": "Modèle non trouvé"}
            
            old_quota = self.quotas[model].allocated
            self.quotas[model].allocated = new_quota
            
            return {
                "success": True,
                "model": model,
                "old_quota": old_quota,
                "new_quota": new_quota,
                "change_percent": ((new_quota - old_quota) / old_quota * 100)
            }


Instance singleton

quota_manager = UnifiedQuotaManager() if __name__ == "__main__": # Test du gestionnaire de quotas print("=== Test Quota Manager ===") # Vérifier disponibilité check = quota_manager.check_availability("gpt-5", 50000) print(f"Disponibilité GPT-5: {check}") # Simuler des requêtes for i in range(3): result = quota_manager.record_usage("gpt-5", 15000) print(f"Requête {i+1}: {result['quota_status']:.1f}% utilisé") # Rapport complet report = quota_manager.get_allocation_report() print(f"\nRapport d'allocation:") print(f" Total utilisé: {report['total_used']:,} tokens") print(f" Utilisation globale: {report['overall_utilization']:.1f}%")

Intégration Complete avec HolySheep API

# main.py - Orchestrateur principal
import asyncio
from datetime import datetime
from agents.leak_detector import LeakDetectorAgent
from agents.repair_orchestrator import RepairOrchestratorAgent
from services.iot_collector import IoTDataCollector
from services.quota_manager import quota_manager

class WaterLeakAgentOrchestrator:
    """
    Orchestrateur principal du système HolySheep Water Leak Agent.
    Coordonne la détection, l'analyse et la réparation des fuites.
    """
    
    def __init__(self):
        self.leak_detector = LeakDetectorAgent()
        self.repair_orchestrator = RepairOrchestratorAgent()
        self.iot_collector = IoTDataCollector()
        self.quota_manager = quota_manager
        
        # Seuils de décision
        self.min_confidence = 75.0
        self.min_severity = 3
    
    async def run_daily_analysis(self):
        """Analyse quotidienne complète du réseau."""
        print(f"[{datetime.now()}] === DÉMARRAGE ANALYSE QUOTIDIENNE ===")
        
        # Étape 1: Collecte des données IoT
        print("📡 Étape 1/4: Collecte données capteurs...")
        sensors = self.iot_collector.fetch_current_readings()
        
        # Étape 2: Détection des fuites
        print("🔍 Étape 2/4: Analyse GPT-5 pour détection anomalies...")
        
        # Vérification quota avant appel
        quota_check = self.quota_manager.check_availability("gpt-5", 80000)
        if not quota_check["available"]:
            print(f"⚠️ Quota GPT-5 épuisé: {quota_check['reason']}")
            return {"status": "quota_exceeded", "details": quota_check}
        
        detection_result = self.leak_detector.analyze_sensor_data(sensors)
        
        if not detection_result["success"]:
            print(f"❌ Erreur détection: {detection_result['error']}")
            return detection_result
        
        # Enregistrement usage
        self.quota_manager.record_usage("gpt-5", detection_result["tokens_used"])
        
        leaks = detection_result.get("leaks", {}).get("detected_leaks", [])
        print(f"   → {len(leaks)} fuite(s) potentielle(s) détectée(s)")
        
        # Étape 3: Génération ordres de réparation
        print("📋 Étape 3/4: Génération ordres de réparation Claude...")
        repair_orders = []
        
        for leak in leaks:
            confidence = leak.get("confidence", 0)
            severity = leak.get("severity", 0)
            
            # Filtres de décision
            if confidence < self.min_confidence:
                print(f"   ⏭️ Fuite exclue (confidence {confidence}% < {self.min_confidence}%)")
                continue
            
            if severity < self.min_severity:
                print(f"   ⏭️ Fuite exclue (sévérité {severity} < {self.min_severity})")
                continue
            
            # Vérification quota Claude
            quota_check = self.quota_manager.check_availability("claude-sonnet-4.5", 15000)
            if not quota_check["available"]:
                print(f"   ⚠️ Quota Claude épuisé")
                break
            
            order_result = self.repair_orchestrator.generate_repair_order(leak)
            
            if order_result["success"]:
                repair_orders.append(order_result["order"])
                self.quota_manager.record_usage(
                    "claude-sonnet-4.5",
                    order_result["tokens_used"]
                )
        
        # Étape 4: Rapport final
        print("📊 Étape 4/4: Génération rapport...")
        quota_report = self.quota_manager.get_allocation_report()
        
        return {
            "status": "completed",
            "detection": {
                "sensors_analyzed": len(sensors),
                "leaks_found": len(leaks),
                "leaks_processed": len(repair_orders)
            },
            "repair_orders": repair_orders,
            "quota_report": quota_report,
            "executed_at": datetime.now().isoformat()
        }

async def main():
    """Point d'entrée principal."""
    orchestrator = WaterLeakAgentOrchestrator()
    
    print("🏭 HolySheep Water Leak Agent - Système Intelligent")
    print("=" * 50)
    
    # Exécution de l'analyse
    result = await orchestrator.run_daily_analysis()
    
    print("\n" + "=" * 50)
    print("📋 RÉSULTAT FINAL")
    print(f"   Statut: {result['status']}")
    
    if result['status'] == 'completed':
        print(f"   Fuites détectées: {result['detection']['leaks_found']}")
        print(f"   Ordres générés: {result['detection']['leaks_processed']}")
        print(f"   Quota total utilisé: {result['quota_report']['total_used']:,} tokens")
    
    return result

if __name__ == "__main__":
    asyncio.run(main())

Pour qui / pour qui ce n'est pas fait

✓ Ce système est fait pour :

✗ Ce système n'est pas fait pour :

Tarification et ROI

Structure des Coûts HolySheep 2026

NiveauPrix MensuelCrédits InclusSupportCas d'Usage
StarterGratuit1M tokensCommunautéTests, POCs
Professional299 $/mois50M tokensEmail + SlackPME, Départements
Enterprise999 $/mois200M tokensDédié + SLA 99.9%Municipalités, Grands comptes
CustomSur devisIllimitéAccount ManagerOpérateurs nationaux

Calculateur de ROI pour une Municipalité Type

PosteSans Agent IAAvec HolySheepÉconomie
Coût API mensuel (10M tokens)110 000 $17 600 $92 400 $ (-84%)
Interventions non planifiées45/an12/an33 interventions (-73%)
Coût moyen intervention urgente8 500 $8 500 $280 500 $ économisé
Pertes d'eau non détectées12% du volume3% du volumeValeur: ~500 000 $/an
Économie annuelle totale~973 000 $/an
Investissement développement~150 000 $

🔥 Essayez HolySheep AI

Passerelle API IA directe. Claude, GPT-5, Gemini, DeepSeek — une clé, sans VPN.

👉 S'inscrire gratuitement →