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:
ARCHITECT
2025-12-24 10:26:04 +00:00
parent d7f1254625
commit a99e58e809
3 changed files with 78 additions and 51 deletions

View File

@@ -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.

View File

@@ -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
View 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)