Consolidar RateLimiter en utils.py
- Crear orchestrator/utils.py con RateLimiter consolidado - Eliminar duplicados de providers/base.py y tools/executor.py - Agregar métodos reset() y available_calls al RateLimiter - Import compatible con ambos modos de ejecución 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,11 @@ from dataclasses import dataclass, field
|
||||
from typing import Optional, Any
|
||||
from datetime import datetime
|
||||
import asyncio
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
try:
|
||||
from ..utils import RateLimiter
|
||||
except ImportError:
|
||||
from utils import RateLimiter
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -31,29 +34,6 @@ class ProviderResponse:
|
||||
return self.success
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
"""Rate limiter para llamadas a APIs."""
|
||||
|
||||
def __init__(self, max_calls: int = 60, period: float = 60.0):
|
||||
self.max_calls = max_calls
|
||||
self.period = period
|
||||
self.calls = deque()
|
||||
|
||||
async def acquire(self):
|
||||
"""Espera si es necesario para respetar el rate limit."""
|
||||
now = time.time()
|
||||
|
||||
while self.calls and self.calls[0] < now - self.period:
|
||||
self.calls.popleft()
|
||||
|
||||
if len(self.calls) >= self.max_calls:
|
||||
wait_time = self.calls[0] + self.period - now
|
||||
if wait_time > 0:
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
self.calls.append(time.time())
|
||||
|
||||
|
||||
class BaseProvider(ABC):
|
||||
"""
|
||||
Clase base abstracta para todos los providers de modelos.
|
||||
|
||||
@@ -20,7 +20,11 @@ from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Any, Callable
|
||||
from datetime import datetime
|
||||
from collections import deque
|
||||
|
||||
try:
|
||||
from ..utils import RateLimiter
|
||||
except ImportError:
|
||||
from utils import RateLimiter
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -35,31 +39,6 @@ class ToolResult:
|
||||
retries: int = 0
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
"""Rate limiter simple basado en ventana deslizante."""
|
||||
|
||||
def __init__(self, max_calls: int, period: float = 60.0):
|
||||
self.max_calls = max_calls
|
||||
self.period = period
|
||||
self.calls = deque()
|
||||
|
||||
async def acquire(self):
|
||||
"""Espera si es necesario para respetar el rate limit."""
|
||||
now = time.time()
|
||||
|
||||
# Limpiar llamadas antiguas
|
||||
while self.calls and self.calls[0] < now - self.period:
|
||||
self.calls.popleft()
|
||||
|
||||
# Si llegamos al límite, esperar
|
||||
if len(self.calls) >= self.max_calls:
|
||||
wait_time = self.calls[0] + self.period - now
|
||||
if wait_time > 0:
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
self.calls.append(time.time())
|
||||
|
||||
|
||||
class SecurityValidator:
|
||||
"""Validador de seguridad para herramientas."""
|
||||
|
||||
|
||||
68
orchestrator/utils.py
Normal file
68
orchestrator/utils.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# orchestrator/utils.py
|
||||
"""
|
||||
Utilidades compartidas del orquestador.
|
||||
|
||||
Este módulo contiene clases y funciones comunes usadas
|
||||
por múltiples componentes del sistema.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
|
||||
class RateLimiter:
|
||||
"""
|
||||
Rate limiter basado en ventana deslizante.
|
||||
|
||||
Controla la frecuencia de llamadas para respetar límites de APIs.
|
||||
Thread-safe para uso con asyncio.
|
||||
|
||||
Ejemplo:
|
||||
limiter = RateLimiter(max_calls=60, period=60.0)
|
||||
await limiter.acquire() # Espera si es necesario
|
||||
# ... hacer la llamada
|
||||
"""
|
||||
|
||||
def __init__(self, max_calls: int = 60, period: float = 60.0):
|
||||
"""
|
||||
Args:
|
||||
max_calls: Número máximo de llamadas permitidas en el período
|
||||
period: Duración del período en segundos (default: 60s)
|
||||
"""
|
||||
self.max_calls = max_calls
|
||||
self.period = period
|
||||
self.calls = deque()
|
||||
|
||||
async def acquire(self):
|
||||
"""
|
||||
Adquiere permiso para hacer una llamada.
|
||||
|
||||
Espera si es necesario para respetar el rate limit.
|
||||
"""
|
||||
now = time.time()
|
||||
|
||||
# Limpiar llamadas antiguas fuera de la ventana
|
||||
while self.calls and self.calls[0] < now - self.period:
|
||||
self.calls.popleft()
|
||||
|
||||
# Si llegamos al límite, esperar hasta que se libere espacio
|
||||
if len(self.calls) >= self.max_calls:
|
||||
wait_time = self.calls[0] + self.period - now
|
||||
if wait_time > 0:
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
# Registrar esta llamada
|
||||
self.calls.append(time.time())
|
||||
|
||||
def reset(self):
|
||||
"""Resetea el contador de llamadas."""
|
||||
self.calls.clear()
|
||||
|
||||
@property
|
||||
def available_calls(self) -> int:
|
||||
"""Retorna el número de llamadas disponibles en la ventana actual."""
|
||||
now = time.time()
|
||||
# Contar llamadas activas
|
||||
active = sum(1 for t in self.calls if t >= now - self.period)
|
||||
return max(0, self.max_calls - active)
|
||||
Reference in New Issue
Block a user