Files
microdao-daarion/gateway-bot/rate_limiter.py
Apple ef3473db21 snapshot: NODE1 production state 2026-02-09
Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.

Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles

Excluded from snapshot: venv/, .env, data/, backups, .tgz archives

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 08:46:46 -08:00

118 lines
4.2 KiB
Python

"""
DAARION Platform - Rate Limiter
Protects agents from spam and abuse
"""
import time
from collections import defaultdict
from typing import Dict, Tuple, Optional
import structlog
logger = structlog.get_logger()
class RateLimiter:
"""Token bucket rate limiter per user/agent"""
def __init__(self, default_rpm: int = 30, default_burst: int = 5):
self.default_rpm = default_rpm
self.default_burst = default_burst
# {user_id: {agent_id: (tokens, last_update)}}
self._buckets: Dict[str, Dict[str, Tuple[float, float]]] = defaultdict(dict)
# {agent_id: rpm_limit}
self._agent_limits: Dict[str, int] = {}
# Blocked users (temporary)
self._blocked: Dict[str, float] = {} # user_id: unblock_time
def set_agent_limit(self, agent_id: str, rpm: int):
"""Set rate limit for specific agent"""
self._agent_limits[agent_id] = rpm
logger.info("rate_limit_set", agent_id=agent_id, rpm=rpm)
def _get_limit(self, agent_id: str) -> int:
"""Get RPM limit for agent"""
return self._agent_limits.get(agent_id, self.default_rpm)
def _refill_tokens(self, user_id: str, agent_id: str) -> float:
"""Refill tokens based on time elapsed"""
now = time.time()
if agent_id not in self._buckets[user_id]:
# New user - full bucket
self._buckets[user_id][agent_id] = (self.default_burst, now)
return self.default_burst
tokens, last_update = self._buckets[user_id][agent_id]
rpm = self._get_limit(agent_id)
# Calculate tokens to add (tokens per second = rpm / 60)
elapsed = now - last_update
tokens_to_add = elapsed * (rpm / 60.0)
# Cap at burst limit
new_tokens = min(self.default_burst, tokens + tokens_to_add)
self._buckets[user_id][agent_id] = (new_tokens, now)
return new_tokens
def is_blocked(self, user_id: str) -> bool:
"""Check if user is temporarily blocked"""
if user_id not in self._blocked:
return False
if time.time() > self._blocked[user_id]:
del self._blocked[user_id]
return False
return True
def block_user(self, user_id: str, seconds: int = 60):
"""Temporarily block user"""
self._blocked[user_id] = time.time() + seconds
logger.warning("user_blocked", user_id=user_id, seconds=seconds)
def check(self, user_id: str, agent_id: str) -> Tuple[bool, Optional[str]]:
"""
Check if request is allowed.
Returns:
(allowed, error_message)
"""
# Check block list
if self.is_blocked(user_id):
remaining = int(self._blocked[user_id] - time.time())
return False, f"Занадто багато запитів. Спробуйте через {remaining} сек."
# Refill and check tokens
tokens = self._refill_tokens(user_id, agent_id)
if tokens < 1:
# No tokens - calculate wait time
rpm = self._get_limit(agent_id)
wait_seconds = int(60 / rpm)
logger.warning("rate_limit_exceeded", user_id=user_id, agent_id=agent_id)
return False, f"Ліміт запитів. Спробуйте через {wait_seconds} сек."
# Consume token
self._buckets[user_id][agent_id] = (tokens - 1, time.time())
return True, None
def get_remaining(self, user_id: str, agent_id: str) -> int:
"""Get remaining requests for user"""
tokens = self._refill_tokens(user_id, agent_id)
return int(tokens)
# Singleton instance
_limiter = None
def get_rate_limiter() -> RateLimiter:
"""Get or create rate limiter instance"""
global _limiter
if _limiter is None:
_limiter = RateLimiter()
# Set agent-specific limits
_limiter.set_agent_limit("helion", 60)
_limiter.set_agent_limit("nutra", 30)
_limiter.set_agent_limit("greenfood", 20)
_limiter.set_agent_limit("druid", 20)
_limiter.set_agent_limit("daarwizz", 100)
return _limiter