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

View File

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