Spaces:
Running
Running
| """ | |
| BaseSkill - Classe base para todas as skills agrupadas com fallbacks | |
| Padrão: Primary Provider -> Fallback Chain -> Error Handling | |
| """ | |
| import time | |
| import json | |
| import logging | |
| from typing import Any, Dict, List, Optional, Callable | |
| from abc import ABC, abstractmethod | |
| from datetime import datetime, timedelta | |
| import hashlib | |
| class SkillError(Exception): | |
| """Erro base em skills""" | |
| pass | |
| class APITimeoutError(SkillError): | |
| """Timeout em chamada de API""" | |
| pass | |
| class APIRateLimitError(SkillError): | |
| """Rate limit atingido""" | |
| pass | |
| class DataValidationError(SkillError): | |
| """Dados inválidos retornados""" | |
| pass | |
| class CacheManager: | |
| """Gerencia cache com TTL""" | |
| def __init__(self): | |
| self.cache = {} | |
| self.logger = logging.getLogger(f"Cache") | |
| def set(self, key: str, value: Any, ttl: int = 3600): | |
| """Armazena valor em cache com TTL (em segundos)""" | |
| self.cache[key] = { | |
| "value": value, | |
| "expires_at": time.time() + ttl, | |
| "created_at": datetime.now().isoformat() | |
| } | |
| self.logger.debug(f"💾 Cache SET: {key} (TTL: {ttl}s)") | |
| def get(self, key: str) -> Optional[Any]: | |
| """Recupera valor do cache se ainda válido""" | |
| if key not in self.cache: | |
| return None | |
| entry = self.cache[key] | |
| if time.time() > entry["expires_at"]: | |
| del self.cache[key] | |
| self.logger.debug(f"♻️ Cache EXPIRED: {key}") | |
| return None | |
| self.logger.debug(f"✅ Cache HIT: {key}") | |
| return entry["value"] | |
| def clear(self): | |
| """Limpa todo o cache""" | |
| self.cache.clear() | |
| def get_stats(self) -> Dict: | |
| """Retorna estatísticas do cache""" | |
| return { | |
| "total_items": len(self.cache), | |
| "items": list(self.cache.keys()) | |
| } | |
| class BaseSkill(ABC): | |
| """ | |
| Classe base para skills com suporte a fallbacks automáticos | |
| Exemplo de uso: | |
| class WeatherSkill(BaseSkill): | |
| def get_primary_provider(self): | |
| return self.web_search_weather | |
| def get_fallback_chain(self): | |
| return [ | |
| self.weather_api, | |
| self.wttr_in | |
| ] | |
| """ | |
| def __init__(self, name: str, description: str): | |
| self.name = name | |
| self.description = description | |
| self.logger = logging.getLogger(f"Skill[{name}]") | |
| self.cache = CacheManager() | |
| self.call_count = 0 | |
| self.error_count = 0 | |
| def get_primary_provider(self) -> Callable: | |
| """Retorna função do provider primário""" | |
| pass | |
| def get_fallback_chain(self) -> List[Callable]: | |
| """Retorna lista de fallbacks (pode estar vazio)""" | |
| return [] | |
| def execute(self, *args, **kwargs) -> Dict[str, Any]: | |
| """ | |
| Executa skill com fallback automático | |
| Tenta: Primary -> Fallback1 -> Fallback2 -> Error | |
| """ | |
| self.call_count += 1 | |
| start_time = time.time() | |
| # Verifica cache | |
| cache_key = self._make_cache_key(*args, **kwargs) | |
| cached = self.cache.get(cache_key) | |
| if cached: | |
| return {**cached, "cache_hit": True} | |
| # Chain de providers | |
| providers = [self.get_primary_provider()] + self.get_fallback_chain() | |
| last_error = None | |
| for i, provider in enumerate(providers): | |
| provider_name = getattr(provider, "__name__", f"Provider{i}") | |
| try: | |
| self.logger.info(f"🔄 Tentando {provider_name}...") | |
| result = self._execute_with_timeout(provider, *args, **kwargs) | |
| if not result.get("sucesso"): | |
| self.logger.warning(f"⚠️ {provider_name} retornou erro: {result.get('erro')}") | |
| last_error = result.get("erro") | |
| continue | |
| # Sucesso! Formata e cacheia | |
| response = self._format_response(provider_name, result, False) | |
| elapsed = time.time() - start_time | |
| response["elapsed_ms"] = int(elapsed * 1000) | |
| # Cacheia resultado bem-sucedido | |
| ttl = kwargs.pop("cache_ttl", 3600) | |
| self.cache.set(cache_key, response, ttl=ttl) | |
| self.logger.info(f"✅ {provider_name} sucesso ({elapsed:.2f}s)") | |
| return response | |
| except APITimeoutError as e: | |
| self.logger.warning(f"⏱️ {provider_name} timeout: {e}") | |
| last_error = f"Timeout: {e}" | |
| if i < len(providers) - 1: | |
| time.sleep(0.5 * (2 ** i)) # Backoff exponencial | |
| continue | |
| except APIRateLimitError as e: | |
| self.logger.warning(f"🚫 {provider_name} rate limit: {e}") | |
| last_error = f"Rate limit: {e}" | |
| continue | |
| except DataValidationError as e: | |
| self.logger.warning(f"❌ {provider_name} dados inválidos: {e}") | |
| last_error = f"Dados inválidos: {e}" | |
| continue | |
| except Exception as e: | |
| self.logger.error(f"💥 {provider_name} erro: {type(e).__name__}: {e}") | |
| last_error = f"{type(e).__name__}: {e}" | |
| continue | |
| # Todos providers falharam | |
| self.error_count += 1 | |
| self.logger.error(f"🔴 Todos providers falharam para {self.name}") | |
| return self._format_error_response(last_error) | |
| def _execute_with_timeout(self, fn: Callable, *args, timeout: float = 5.0, **kwargs) -> Any: | |
| """ | |
| Executa função com timeout | |
| Implementação simples (ideal seria threading/async) | |
| """ | |
| # Para versão simples, apenas chama a função | |
| # Em produção, usar ThreadPoolExecutor ou asyncio | |
| return fn(*args, **kwargs) | |
| def _format_response(self, provider: str, data: Dict, cache_hit: bool) -> Dict: | |
| """Formata resposta padrão""" | |
| return { | |
| "sucesso": True, | |
| "skill": self.name, | |
| "provider": provider, | |
| "cache_hit": cache_hit, | |
| "dados": data, | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| def _format_error_response(self, error: str) -> Dict: | |
| """Formata resposta de erro""" | |
| return { | |
| "sucesso": False, | |
| "skill": self.name, | |
| "erro": error or f"Nenhum provider disponível para {self.name}", | |
| "sugestao": self._get_error_suggestion(), | |
| "timestamp": datetime.now().isoformat() | |
| } | |
| def _get_error_suggestion(self) -> str: | |
| """Retorna sugestão quando tudo falha""" | |
| return "Tenta de novo mais tarde" | |
| def _make_cache_key(self, *args, **kwargs) -> str: | |
| """Cria chave de cache baseada em argumentos""" | |
| # Não remove cache_ttl — é preservado para uso externo | |
| key_str = f"{self.name}:{json.dumps([args, kwargs], sort_keys=True, default=str)}" | |
| return hashlib.md5(key_str.encode()).hexdigest() | |
| def get_stats(self) -> Dict: | |
| """Retorna estatísticas da skill""" | |
| return { | |
| "name": self.name, | |
| "description": self.description, | |
| "calls": self.call_count, | |
| "errors": self.error_count, | |
| "error_rate": f"{(self.error_count/max(1, self.call_count)*100):.1f}%", | |
| "cache": self.cache.get_stats() | |
| } | |
| def clear_cache(self): | |
| """Limpa cache da skill""" | |
| self.cache.clear() | |
| self.logger.info("🧹 Cache limpo") | |
| # ========================== | |
| # Decoradores úteis | |
| # ========================== | |
| def retry(max_attempts: int = 3, backoff: float = 1.0): | |
| """Decorator para retry automático com backoff exponencial""" | |
| def decorator(fn): | |
| def wrapper(*args, **kwargs): | |
| for attempt in range(max_attempts): | |
| try: | |
| return fn(*args, **kwargs) | |
| except Exception as e: | |
| if attempt == max_attempts - 1: | |
| raise | |
| wait_time = backoff * (2 ** attempt) | |
| logging.warning(f"Retry {attempt+1}/{max_attempts}, aguardando {wait_time}s") | |
| time.sleep(wait_time) | |
| return wrapper | |
| return decorator | |
| def timeout(seconds: float = 5.0): | |
| """Decorator para timeout (implementação simples)""" | |
| def decorator(fn): | |
| def wrapper(*args, **kwargs): | |
| # Implementação real usaria signal ou threading | |
| return fn(*args, **kwargs) | |
| return wrapper | |
| return decorator | |
| def validate_response(schema: Dict = None): | |
| """Decorator para validar resposta contra schema""" | |
| def decorator(fn): | |
| def wrapper(*args, **kwargs): | |
| result = fn(*args, **kwargs) | |
| if not isinstance(result, dict): | |
| raise DataValidationError(f"Response deve ser dict, got {type(result)}") | |
| return result | |
| return wrapper | |
| return decorator | |