Ідея за 30 секунд
Rate limiting — це runtime-контроль, який обмежує частоту зовнішніх викликів агента, щоб не допустити сплесків і retry-шторму в production.
Коли це потрібно: коли агент часто викликає model/tool API, має retry-логіку і працює під піковим навантаженням.
Проблема
Без rate limiting один нестабільний сервіс швидко розганяє ланцюг: retry → виклик → ще retry.
У demo це майже не видно. У production така поведінка створює лавину 429/5xx, черги і затримки.
Найгірше, що інцидент масштабується сам:
- один надто активний користувач забирає квоти
- один tenant "душить" інших
- глобальний пік ламає залежності одразу для всіх
І кожна хвилина без обмеження частоти додає нові ретраї, черги і затримки, і система сама себе DDoS-ить.
Аналогія: це як рампа на в'їзді на автомагістраль. Якщо не дозувати потік, навіть добра дорога стає затором за хвилини.
Рішення
Рішення — додати централізований rate-limit policy layer у runtime і tool gateway.
Кожен зовнішній виклик агента перевіряється за лімітами per_user, per_tenant, global і burst_tokens.
Policy повертає технічне рішення: allow або stop з явною причиною:
rate_limited_userrate_limited_tenantrate_limited_globalburst_limited
Коли повертається stop, runtime повертає retry_after_ms клієнту і не виконує виклик.
Це окремий рівень системи, а не частина промпта чи логіки моделі.
Rate limiting ≠ step limits
Це різні рівні контролю:
- Rate limiting обмежує частоту зовнішніх викликів.
- Step limits обмежують довжину і поведінку runtime loop.
Одне без іншого не працює:
- без rate limiting зовнішні API падають від піків і retry-шторму
- без step limits run може довго крутитися навіть із помірною частотою викликів
Приклад:
- rate limiting: не більше
per_user=6викликів за 10 секунд - step limits:
max_steps=18,max_repeat_action=3
Компоненти rate-limiting контролю
Ці компоненти працюють разом на кожному зовнішньому виклику агента.
| Компонент | Що контролює | Ключові механіки | Навіщо |
|---|---|---|---|
| Per-user limit | Поведінку одного користувача | per_user quotasliding window | Не дає одному користувачу "з'їсти" весь ресурс |
| Per-tenant limit | Навантаження одного tenant | per_tenant quotatenant-scoped keys | Ізолює піки між клієнтами |
| Global limit | Загальне навантаження системи | global capshared limiter | Захищає зовнішні залежності від масового сплеску |
| Burst control | Короткі пікові "спайки" | token bucket refill rate | Гасить миттєві стрибки без повного стопу системи |
| Rate-limit observability | Видимість policy-рішень | audit logs alerts на stop spikes | Не обмежує виклики напряму, але дозволяє швидко знайти джерело піку |
Приклад alert:
Slack: 🛑 Support-Agent hit rate_limited_tenant. retry_after=1200ms, tenant=t_42.
Як це виглядає в архітектурі
Rate-limit policy layer стоїть між runtime і зовнішніми model/tool API.
Кожне рішення (allow або stop) фіксується в audit log.
Кожен зовнішній виклик агента проходить через цей flow перед виконанням: runtime не виконує виклик напряму, а передає рішення policy layer.
Коротко по flow:
- Runtime формує зовнішній виклик агента
- Policy перевіряє
per_user,per_tenant,globalіburst_tokens allow-> виклик виконуєтьсяstop-> повертаєтьсяretry_after_msі часткова відповідь- обидва рішення пишуться в audit log
Приклад
Агент підтримки одночасно обробляє багато запитів і кілька разів ретраїть crm.search.
З rate limiting:
per_user = 6 / 10sper_tenant = 120 / minglobal = 50 / sburst_tokens = 5
-> сплеск зупиняється на policy-рівні до падіння залежностей і черг.
Rate limiting зупиняє інцидент прямо перед зовнішнім викликом, а не після хвилі 429.
У коді це виглядає так
У спрощеній схемі вище показано основний flow.
Критично: rate-limit check має бути O(1) і атомарним (зазвичай через Redis/Lua або еквівалент), інакше під піком він сам стає вузьким місцем.
Після stop(...) runtime зазвичай формує часткову відповідь для клієнта з явною причиною і retry_after_ms.
Приклад rate-limit конфігурації:
rate_limits:
per_user_10s: 6
per_tenant_min: 120
global_rps: 50
burst_tokens: 5
refill_per_second: 2
action = planner.next(state)
action_key = make_action_key(action.name, action.args)
if not action.is_external_call():
# execute_local — умовний helper для локальних дій без зовнішнього API
# Decision.allow — умовний helper, щоб зберегти єдину модель outcome/reason.
local_result = execute_local(action)
local_decision = Decision.allow(reason=None)
audit.log(
run_id,
decision=local_decision.outcome,
reason=local_decision.reason,
scope="local",
action=action.name,
action_key=action_key,
result=local_result.status,
)
return local_result
decision = rate_limit.check(
user_id=state.user_id,
tenant_id=state.tenant_id,
action=action.name,
now_ms=clock.now_ms(),
)
if decision.outcome == "stop":
audit.log(
run_id,
decision=decision.outcome,
reason=decision.reason,
scope=decision.scope,
retry_after_ms=decision.retry_after_ms,
action=action.name,
action_key=action_key,
)
alerts.notify_if_needed(run_id, decision.reason, scope=decision.scope)
return stop(decision.reason, retry_after_ms=decision.retry_after_ms)
result = executor.execute(action)
audit.log(
run_id,
decision=decision.outcome,
reason=decision.reason,
scope=decision.scope,
action=action.name,
action_key=action_key,
result=result.status,
)
return result
Як це виглядає під час виконання
Сценарій 1: зупинка по tenant-ліміту
- Runtime формує зовнішній виклик
crm.search. - Policy бачить перевищення
per_tenantквоти. - Рішення:
stop (reason=rate_limited_tenant). - Runtime повертає
retry_after_ms. - Виклик не виконується, подія пишеться в audit log.
Сценарій 2: burst-сплеск
- Кілька run-ів одночасно роблять короткий сплеск викликів.
- Policy вичерпує
burst_tokens. - Рішення:
stop (reason=burst_limited). - Частина викликів відхиляється з
retry_after_ms. - Система лишається стабільною без cascade-failure.
Сценарій 3: нормальне виконання
- Runtime формує зовнішній виклик.
- Policy перевіряє ліміти: все в межах.
- Рішення:
allow. - Виклик виконується.
- Рішення і результат пишуться в audit log.
Типові помилки
- ставити тільки global limit без
per_user/per_tenantізоляції - не віддавати
retry_afterприstop - робити retry без backoff і jitter
- перевіряти rate limit лише в одному шарі (тільки runtime або тільки gateway)
- не логувати stop-рішення (
reason,scope,retry_after_ms) - не ставити alerting на сплеск
rate_limited_*
У результаті система виглядає контрольованою, але при реальному піку швидко деградує.
Самоперевірка
Швидка перевірка rate limiting перед запуском у production:
Прогрес: 0/8
⚠ Бракує базового governance-контролю
Перед production потрібні мінімум: контроль доступу, ліміти, audit logs і аварійна зупинка.
FAQ
Q: З яких лімітів почати?
A: Мінімум: per_user, per_tenant, global + невеликий burst-контроль. Далі підлаштовуй за реальними stop-подіями.
Q: Якщо зовнішній API вже має 429, чи потрібен наш rate limiting?
A: Так. Власний rate limiting захищає runtime до зовнішнього 429 і дає контрольовані stop reasons, retry_after та аудит.
Q: Що краще при ліміті: стоп чи черга?
A: Для sync-run зазвичай краще stop + retry_after. Для async-пайплайнів можна додавати чергу, але все одно з явними лімітами і timeout.
Q: Rate limiting замінює budget controls?
A: Ні. Rate limiting контролює частоту викликів, а budget controls — сумарні витрати run.
Q: Де зберігати лічильники?
A: У спільному low-latency сховищі з атомарними операціями (часто Redis). Без цього ліміти між інстансами будуть неконсистентними.
Де Rate Limiting у загальній системі
Rate limiting — це один із рівнів Agent Governance. Разом із RBAC, бюджетами, step limits, approval і audit він формує єдину систему контролю виконання.
Пов'язані сторінки
Далі за темою:
- Огляд Agent Governance — загальна модель контролю агентів у production.
- Контроль бюджетів — як обмежувати сумарні витрати run.
- Step limits — як зупиняти loop на рівні runtime.
- Kill switch — як аварійно зупиняти дії без релізу.
- Audit logs для агентів — як аналізувати причини stop і сплески навантаження.