""" Cerebras API Rotation - Multi-account management Rotação automática entre múltiplas contas Cerebras para evitar rate limits Contas disponíveis: 1. ann_cerebras_api 2. isaac_cerebras_api 3. netflux_cerebras_api 4. gitakira_cerebras_api """ import os import logging from typing import Optional, Dict, Any from datetime import datetime, timedelta logger = logging.getLogger(__name__) class CerebrasRotation: """Gerencia rotação de múltiplas contas Cerebras""" def __init__(self): def _eci(n: str) -> str: v = os.getenv(n, "").strip() or os.getenv(n.lower(), "").strip() or os.getenv(n.upper(), "").strip() if len(v) >= 2 and ((v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'"))): v = v[1:-1].strip() return v self.accounts = { 'ann': _eci('ANN_CEREBRAS_API_KEY'), 'isaac': _eci('ISAAC_CEREBRAS_API_KEY'), 'netflux': _eci('NETFLUX_CEREBRAS_API_KEY'), 'gitakira': _eci('GITAKIRA_CEREBRAS_API_KEY'), } # Filtrar contas vazias self.accounts = {k: v for k, v in self.accounts.items() if v} self.current_index = 0 self.account_names = list(self.accounts.keys()) self.rate_limit_cache: Dict[str, dict] = {} if self.account_names: logger.info(f"✅ Cerebras Rotation inicializado com {len(self.account_names)} conta(s)") for i, name in enumerate(self.account_names, 1): status = "✅ ATIVA" if self.accounts[name] else "❌ VAZIA" logger.info(f" [{i}] {name.upper():12} {status}") else: logger.warning("⚠️ Nenhuma conta Cerebras encontrada nos secrets") def get_current_account_name(self) -> str: """Retorna o nome da conta atual""" if not self.account_names: return "nenhuma" return self.account_names[self.current_index] def get_current_api_key(self) -> Optional[str]: """Retorna a chave API da conta atual""" if not self.account_names: return None current_name = self.account_names[self.current_index] return self.accounts.get(current_name) def rotate_to_next(self): """Rotaciona para próxima conta""" if not self.account_names: return old_index = self.current_index self.current_index = (self.current_index + 1) % len(self.account_names) old_name = self.account_names[old_index] new_name = self.account_names[self.current_index] logger.info(f"🔄 Rotacionando Cerebras: '{old_name}' → '{new_name}'") def handle_rate_limit_error(self) -> bool: """ Trata erro 429 (rate limit) Retorna True se conseguiu rotacionar, False se todas contas limitadas """ if len(self.account_names) <= 1: logger.error("❌ [429] Apenas 1 conta Cerebras disponível e limitada") return False current_name = self.account_names[self.current_index] logger.warning(f"⚠️ [429 RATE LIMIT] Conta '{current_name}' esgotada") # Marca conta como limitada self.rate_limit_cache[current_name] = { 'limited': True, 'until': datetime.now() + timedelta(minutes=10) } # Rotaciona para próxima self.rotate_to_next() new_name = self.account_names[self.current_index] logger.info(f"✅ [429 RECOVERY] Mudando para '{new_name}'") return True def get_all_api_keys(self) -> Dict[str, str]: """Retorna dict {name: api_key} de todas as contas""" return self.accounts.copy() def is_account_limited(self, account_name: str) -> bool: """Verifica se conta está limitada""" if account_name not in self.rate_limit_cache: return False cache = self.rate_limit_cache[account_name] if datetime.now() > cache.get('until', datetime.now()): del self.rate_limit_cache[account_name] return False return cache.get('limited', False) # Singleton instance _cerebras_rotation_instance: Optional[CerebrasRotation] = None def get_cerebras_rotation() -> CerebrasRotation: """Factory para Cerebras Rotation (singleton)""" global _cerebras_rotation_instance if _cerebras_rotation_instance is None: _cerebras_rotation_instance = CerebrasRotation() return _cerebras_rotation_instance def reset_cerebras_rotation(): """Reset para testes""" global _cerebras_rotation_instance _cerebras_rotation_instance = None