Sistema de Rate Limiting¶
Visão Geral¶
O sistema de rate limiting controla o uso de APIs externas para evitar exceção de limites e custos desnecessários.
RateLimiter Class¶
Configuração¶
gemini_limiter = RateLimiter(
requests_per_minute=20, # 60 no free tier, usando 20 para folga
requests_per_day=15000, # 60000 no free tier, usando 15000 para folga
name="gemini"
)
Funcionamento¶
Controles Implementados¶
- Por minuto: Máximo de requisições em janela deslizante de 60s
- Por dia: Máximo de requisições do início ao fim do dia
- Thread-safe: Uso de
threading.RLock()
Estruturas de Dados¶
self.minute_requests = [] # Timestamps das últimas requisições
self.day_requests = [] # Todas as requisições do dia
self.total_requests_today = 0 # Contador total
self.day_start = datetime.now().replace(hour=0, minute=0, second=0)
Métodos Principais¶
can_make_request()
¶
Verifica se uma nova requisição pode ser feita.
Retorno¶
can_request, wait_time = limiter.can_make_request()
# can_request: bool - Se pode fazer requisição
# wait_time: int - Segundos para esperar se não pode
Lógica¶
- Remove requisições antigas (> 1 minuto)
- Verifica limite por minuto
- Verifica limite diário
- Calcula tempo de espera necessário
wait_if_needed()
¶
Método principal usado pelo sistema.
Comportamento¶
can_proceed = limiter.wait_if_needed()
if can_proceed:
# Fazer requisição para API
result = api_call()
else:
# Usar fallback ou rejeitar
result = fallback_method()
Estratégias de Espera¶
- < 5 minutos: Espera o tempo necessário
- > 5 minutos: Rejeita e usa fallback
- Novo dia: Reset automático dos contadores
get_stats()
¶
Retorna estatísticas de uso atual.
Retorno¶
{
"name": "gemini",
"minute_usage": 5,
"minute_limit": 20,
"minute_percent": 25.0,
"day_usage": 150,
"day_limit": 15000,
"day_percent": 1.0,
"total_today": 150
}
Integração com AI Service¶
Verificação Antes da Chamada¶
def classify_email(subject, content):
if not GEMINI_API_KEY:
return _heuristic_classification(subject, content)
# Verificar rate limiting
if not gemini_limiter.wait_if_needed():
logger.warning("Rate limit excedido, usando classificação heurística.")
return _heuristic_classification(subject, content)
# Prosseguir com API Gemini
model = genai.GenerativeModel(text_model)
response = model.generate_content(prompt)
# Log de estatísticas
stats = gemini_limiter.get_stats()
logger.info(f"Uso da API: {stats['day_usage']}/{stats['day_limit']} ({stats['day_percent']:.1f}%)")
Logging e Monitoramento¶
# Alerta quando próximo do limite
if self.total_requests_today > self.requests_per_day * 0.8:
logger.warning(f"Aproximando-se do limite diário: {self.total_requests_today}/{self.requests_per_day}")
# Log de cada requisição
logger.debug(f"Requisição registrada: {self.total_requests_minute}/{self.requests_per_minute} req/min")
Limites da API Gemini¶
Free Tier (Padrão)¶
- Por minuto: 60 requisições
- Por dia: 60.000 requisições
Configuração Conservadora (Aplicada)¶
- Por minuto: 20 requisições (33% do limite)
- Por dia: 15.000 requisições (25% do limite)
Sistema de Fallback¶
Quando Rate Limit é Atingido¶
- Log warning sobre limite atingido
- Usar classificação heurística baseada em palavras-chave
- Manter funcionalidade do sistema
- Continuar monitoramento para quando limite resetar
Classificação Heurística¶
def _heuristic_classification(subject, content):
"""Fallback quando API não disponível"""
text_lower = (subject + " " + content).lower()
# Palavras-chave para cada categoria
improdutivo_count = sum(1 for k in improdutivo_keywords if k in text_lower)
produtivo_count = sum(1 for k in produtivo_keywords if k in text_lower)
# Regras especiais
if "reunião" in text_lower and "confirmar" in text_lower:
return 'productive'
# Decisão baseada em contagem
return 'unproductive' if improdutivo_count > produtivo_count else 'productive'
Monitoramento via API¶
Endpoint /api/usage/
¶
Retorna estatísticas em tempo real:
{
"gemini_api": {
"minute_usage": 5,
"minute_limit": 20,
"minute_percent": 25.0,
"day_usage": 150,
"day_limit": 15000,
"day_percent": 1.0,
"total_today": 150
},
"timestamp": "2023-12-01T10:30:00Z"
}
Reset Automático¶
Diário (Meia-noite)¶
def _is_new_day(self):
now = datetime.now()
if now >= self.day_end:
logger.info("Novo dia iniciado, resetando contadores")
self.day_requests = []
self.reset_day_stats()
return True
return False