""" HuggingFace Inference API Rotation Manager Manages rotation between multiple HF accounts to maximize rate limits and avoid 429 rate limit errors during inference calls. Accounts supported: - ann_hf_api (ANN_HF_TOKEN) - isaac_hf_api (ISAAC_HF_TOKEN) - gitbelmira_hf_api (GITBELMIRA_HF_TOKEN) - netflix_hf_api (NETFLIX_HF_TOKEN) - fugakusayku_hf_api (FUGAKUSAYKU_HF_TOKEN) Free tier capacity per account: 500 requests/day Combined capacity: 2,500 requests/day """ import os import logging from datetime import datetime, timedelta from typing import Dict, Optional logger = logging.getLogger(__name__) def _env_ci(name: str) -> str: v = os.getenv(name, "").strip() or os.getenv(name.lower(), "").strip() or os.getenv(name.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 class HFInferenceRotation: """ Singleton class for managing HuggingFace Inference API token rotation. Features: - Round-robin rotation between 5 HF accounts - Automatic rate limit handling (429 errors) - 10-minute rate limit caching per account - Per-account quota tracking """ _instance: Optional['HFInferenceRotation'] = None def __init__(self): self.accounts = { 'ann': 'ANN_HF_TOKEN', 'isaac': 'ISAAC_HF_TOKEN', 'gitbelmira': 'GITBELMIRA_HF_TOKEN', 'netflix': 'NETFLIX_HF_TOKEN', 'fugakusayku': 'FUGAKUSAYKU_HF_TOKEN', 'hf_token': 'HF_TOKEN', } self.current_account_idx = 0 self.account_order = list(self.accounts.keys()) # Track rate limit status: {account_name: (limited_until_timestamp, error_msg)} self.rate_limited: Dict[str, tuple] = {} # Verify at least one account is configured self._verify_accounts() logger.info( "🤗 [HF INFERENCE] Rotation initialized with accounts: " f"{', '.join([acc for acc in self.account_order if _env_ci(self.accounts[acc])])}" ) def _verify_accounts(self): """Verify that at least one HF token is configured.""" configured = [ acc for acc in self.account_order if _env_ci(self.accounts[acc]) ] if not configured: logger.warning( "⚠️ [HF INFERENCE] No HF tokens configured. " "Set ANN_HF_TOKEN, ISAAC_HF_TOKEN, GITBELMIRA_HF_TOKEN, " "NETFLIX_HF_TOKEN, or FUGAKUSAYKU_HF_TOKEN" ) logger.info(f"✅ [HF INFERENCE] {len(configured)} accounts configured") def get_current_account_name(self) -> str: """Get current account name in rotation.""" return self.account_order[self.current_account_idx] def get_current_api_token(self) -> Optional[str]: """ Get current API token for HF Inference. Skips rate-limited accounts automatically. Returns None if all accounts are rate-limited. """ # Check if current account is rate-limited attempts = 0 max_attempts = len(self.account_order) while attempts < max_attempts: current_account = self.get_current_account_name() # Check if account is temporarily limited if not self.is_account_limited(current_account): token = _env_ci(self.accounts[current_account]) if token: logger.debug( f"🤗 [HF TOKEN] Using account: {current_account}" ) return token # Token not found — rotate to next account self.rotate_to_next() attempts += 1 else: logger.debug( f"⏭️ [HF RATE LIMIT] Account {current_account} limited, " f"rotating..." ) self.rotate_to_next() attempts += 1 logger.error( "❌ [HF INFERENCE] All accounts are rate-limited or unconfigured" ) return None def rotate_to_next(self) -> str: """Rotate to next account in round-robin.""" self.current_account_idx = ( (self.current_account_idx + 1) % len(self.account_order) ) new_account = self.get_current_account_name() logger.info(f"🔄 [HF ROTATION] Switched to account: {new_account}") return new_account def handle_rate_limit_error(self, error_msg: str = None) -> str: """ Handle 429 rate limit error by rotating to next account and caching the current account as limited for 10 minutes. Returns: Next account name to use """ current_account = self.get_current_account_name() limited_until = datetime.now() + timedelta(minutes=10) self.rate_limited[current_account] = (limited_until, error_msg or "429 Rate Limit") logger.warning( f"⚠️ [HF 429] Account {current_account} rate-limited. " f"Will retry in 10 min. Error: {error_msg}" ) # Rotate to next account next_account = self.rotate_to_next() return next_account def is_account_limited(self, account_name: str) -> bool: """Check if account is currently rate-limited.""" if account_name not in self.rate_limited: return False limited_until, _ = self.rate_limited[account_name] if datetime.now() < limited_until: remaining = (limited_until - datetime.now()).total_seconds() logger.debug( f"⏱️ [HF LIMIT] {account_name} limited for " f"{remaining:.0f}s more" ) return True else: # Limit expired, remove from cache del self.rate_limited[account_name] logger.info( f"✅ [HF LIMIT EXPIRED] {account_name} is available again" ) return False def get_all_api_tokens(self) -> Dict[str, Optional[str]]: """Get dict of all configured account tokens.""" return { acc: (_env_ci(self.accounts[acc]) or None) for acc in self.account_order } def get_hf_inference_rotation() -> HFInferenceRotation: """Factory function to get singleton HFInferenceRotation instance.""" if HFInferenceRotation._instance is None: HFInferenceRotation._instance = HFInferenceRotation() return HFInferenceRotation._instance