Files
microdao-daarion/services/agents-service/quotas.py
Apple 3de3c8cb36 feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint
- usePresenceHeartbeat hook with activity tracking
- Auto away after 5 min inactivity
- Offline on page close/visibility change
- Integrated in MatrixChatRoom component
2025-11-27 00:19:40 -08:00

279 lines
9.3 KiB
Python

"""
Quotas & Rate Limits — Обмеження використання агентів
"""
import time
from typing import Dict, Optional
from datetime import datetime, timedelta
from collections import defaultdict
# ============================================================================
# Quota Configuration
# ============================================================================
class QuotaConfig:
"""Конфігурація квот"""
def __init__(
self,
tokens_per_minute: int = 1000,
runs_per_day: int = 100,
users_per_day: int = 50,
max_concurrent_runs: int = 5
):
self.tokens_per_minute = tokens_per_minute
self.runs_per_day = runs_per_day
self.users_per_day = users_per_day
self.max_concurrent_runs = max_concurrent_runs
# Default quota configurations
DEFAULT_QUOTAS = {
"free": QuotaConfig(
tokens_per_minute=500,
runs_per_day=50,
users_per_day=20,
max_concurrent_runs=2
),
"pro": QuotaConfig(
tokens_per_minute=2000,
runs_per_day=500,
users_per_day=200,
max_concurrent_runs=10
),
"enterprise": QuotaConfig(
tokens_per_minute=10000,
runs_per_day=5000,
users_per_day=1000,
max_concurrent_runs=50
)
}
# ============================================================================
# Quota Tracker
# ============================================================================
class QuotaTracker:
"""
Трекер використання ресурсів агентами
Тримає в пам'яті (in-memory) лічильники для:
- Токенів за хвилину
- Запусків за день
- Унікальних користувачів за день
- Паралельних запусків
"""
def __init__(self):
# Agent ID → tokens used in current minute
self._tokens_minute: Dict[str, list[tuple[float, int]]] = defaultdict(list)
# Agent ID → runs count today
self._runs_today: Dict[str, int] = defaultdict(int)
self._runs_today_date: Optional[str] = None
# Agent ID → unique users today
self._users_today: Dict[str, set[str]] = defaultdict(set)
# Agent ID → concurrent runs count
self._concurrent_runs: Dict[str, int] = defaultdict(int)
def check_tokens_quota(self, agent_id: str, tokens: int, quota: QuotaConfig) -> bool:
"""
Перевірити, чи не перевищено квоту токенів за хвилину
Args:
agent_id: ID агента
tokens: Кількість токенів для використання
quota: Конфігурація квот
Returns:
True, якщо квота дозволяє використання
"""
now = time.time()
one_minute_ago = now - 60
# Видалити старі записи (старше 1 хвилини)
self._tokens_minute[agent_id] = [
(ts, count) for ts, count in self._tokens_minute[agent_id]
if ts > one_minute_ago
]
# Підрахувати використані токени за останню хвилину
used_tokens = sum(count for _, count in self._tokens_minute[agent_id])
# Перевірити квоту
if used_tokens + tokens > quota.tokens_per_minute:
return False
return True
def record_tokens(self, agent_id: str, tokens: int) -> None:
"""
Записати використані токени
Args:
agent_id: ID агента
tokens: Кількість використаних токенів
"""
now = time.time()
self._tokens_minute[agent_id].append((now, tokens))
def check_runs_quota(self, agent_id: str, quota: QuotaConfig) -> bool:
"""
Перевірити, чи не перевищено квоту запусків за день
Args:
agent_id: ID агента
quota: Конфігурація квот
Returns:
True, якщо квота дозволяє запуск
"""
today = datetime.utcnow().strftime("%Y-%m-%d")
# Скинути лічильники, якщо новий день
if self._runs_today_date != today:
self._runs_today.clear()
self._users_today.clear()
self._runs_today_date = today
# Перевірити квоту
if self._runs_today[agent_id] >= quota.runs_per_day:
return False
return True
def record_run(self, agent_id: str, user_id: Optional[str] = None) -> None:
"""
Записати запуск
Args:
agent_id: ID агента
user_id: ID користувача (опційно)
"""
today = datetime.utcnow().strftime("%Y-%m-%d")
# Скинути лічильники, якщо новий день
if self._runs_today_date != today:
self._runs_today.clear()
self._users_today.clear()
self._runs_today_date = today
self._runs_today[agent_id] += 1
if user_id:
self._users_today[agent_id].add(user_id)
def check_users_quota(self, agent_id: str, user_id: str, quota: QuotaConfig) -> bool:
"""
Перевірити, чи не перевищено квоту унікальних користувачів за день
Args:
agent_id: ID агента
user_id: ID користувача
quota: Конфігурація квот
Returns:
True, якщо квота дозволяє користувача
"""
today = datetime.utcnow().strftime("%Y-%m-%d")
# Скинути лічильники, якщо новий день
if self._runs_today_date != today:
self._runs_today.clear()
self._users_today.clear()
self._runs_today_date = today
# Якщо користувач вже був — завжди дозволяємо
if user_id in self._users_today[agent_id]:
return True
# Перевірити квоту нових користувачів
if len(self._users_today[agent_id]) >= quota.users_per_day:
return False
return True
def check_concurrent_runs(self, agent_id: str, quota: QuotaConfig) -> bool:
"""
Перевірити, чи не перевищено квоту паралельних запусків
Args:
agent_id: ID агента
quota: Конфігурація квот
Returns:
True, якщо квота дозволяє запуск
"""
if self._concurrent_runs[agent_id] >= quota.max_concurrent_runs:
return False
return True
def start_run(self, agent_id: str) -> None:
"""
Почати запуск (збільшити лічильник паралельних запусків)
Args:
agent_id: ID агента
"""
self._concurrent_runs[agent_id] += 1
def finish_run(self, agent_id: str) -> None:
"""
Завершити запуск (зменшити лічильник паралельних запусків)
Args:
agent_id: ID агента
"""
if self._concurrent_runs[agent_id] > 0:
self._concurrent_runs[agent_id] -= 1
def get_usage_stats(self, agent_id: str) -> Dict:
"""
Отримати статистику використання агента
Args:
agent_id: ID агента
Returns:
Dict зі статистикою
"""
now = time.time()
one_minute_ago = now - 60
# Токени за останню хвилину
tokens_minute = sum(
count for ts, count in self._tokens_minute[agent_id]
if ts > one_minute_ago
)
today = datetime.utcnow().strftime("%Y-%m-%d")
if self._runs_today_date != today:
runs_today = 0
users_today = 0
else:
runs_today = self._runs_today[agent_id]
users_today = len(self._users_today[agent_id])
return {
"tokens_minute": tokens_minute,
"runs_today": runs_today,
"users_today": users_today,
"concurrent_runs": self._concurrent_runs[agent_id]
}
# ============================================================================
# Global Quota Tracker Instance
# ============================================================================
_global_tracker = QuotaTracker()
def get_quota_tracker() -> QuotaTracker:
"""Отримати глобальний екземпляр QuotaTracker"""
return _global_tracker