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 typing import Optional, Any
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import asyncio
|
import asyncio
|
||||||
import time
|
|
||||||
from collections import deque
|
try:
|
||||||
|
from ..utils import RateLimiter
|
||||||
|
except ImportError:
|
||||||
|
from utils import RateLimiter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -31,29 +34,6 @@ class ProviderResponse:
|
|||||||
return self.success
|
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):
|
class BaseProvider(ABC):
|
||||||
"""
|
"""
|
||||||
Clase base abstracta para todos los providers de modelos.
|
Clase base abstracta para todos los providers de modelos.
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ from pathlib import Path
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, Any, Callable
|
from typing import Optional, Any, Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections import deque
|
|
||||||
|
try:
|
||||||
|
from ..utils import RateLimiter
|
||||||
|
except ImportError:
|
||||||
|
from utils import RateLimiter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -35,31 +39,6 @@ class ToolResult:
|
|||||||
retries: int = 0
|
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:
|
class SecurityValidator:
|
||||||
"""Validador de seguridad para herramientas."""
|
"""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