Пакет расшифровок звонков ушёл в обработку, первые задачи завершились, а потом воркеры начали получать HTTP 429. Самая опасная реакция — немедленно повторить всё. Если причина в лимите расходов, повтор бесполезен. Если причина во всплеске запросов, синхронный retry делает всплеск ещё выше.
Первое, что я бы проверил, — не «работает ли AI вообще», а полный ответ одного неудачного запроса. Нужны тело ошибки, error.code, x-request-id и заголовки x-ratelimit-*. Без них 429 остаётся гаданием.
Разделите временный лимит и проблему квоты
| Что видно | Что это обычно означает | Действие |
|---|---|---|
| Rate limit для requests или tokens | Короткий всплеск превысил доступный темп | Поставить очередь и backoff |
credit_balance_exhausted | Закончился предоплаченный баланс | Остановить повторы, проверить Billing |
project_spend_limit_exceeded | Достигнут лимит расходов проекта | Проверить лимит проекта и владельца настроек |
| Нет тела и заголовков в логе | Диагностика теряется в HTTP-клиенте | Исправить логирование до нового запуска |
Не надо увеличивать лимиты до этой развилки. На практике часть «проблем AI» оказывается двумя одинаковыми обработчиками одного webhook или очередью без ограничения параллельности.
Алгоритм диагностики
- Возьмите одну задачу и сохраните её внутренний ID. Не пишите в лог текст разговора или API-ключ.
- Запишите HTTP-статус,
error.code,x-request-id, оставшиеся requests и tokens, а также время reset. - Если ошибка связана с балансом или spend limit, переведите задачу в состояние «ожидает вмешательства». Автоповтор выключите.
- Если исчерпан временный rate limit, уменьшите число параллельных запросов и примените задержку из
Retry-After. При отсутствии корректного значения используйте exponential backoff с jitter. - После паузы отправьте один контрольный запрос. Только после PASS возвращайте очередь в рабочий режим.
Минимальный retry без бесконечного цикла
async function requestWithBackoff(send, maxAttempts = 4) {
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const response = await send();
if (response.status !== 429) return response;
const body = await response.clone().json().catch(() => ({}));
const code = body?.error?.code || body?.error?.type || 'unknown';
if (/balance|spend_limit|usage_limit/.test(code)) {
throw new Error('429 без retry: ' + code);
}
if (attempt === maxAttempts) throw new Error('429: лимит попыток исчерпан');
const retryAfter = Number(response.headers.get('retry-after'));
const baseMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 500 * 2 ** (attempt - 1);
const jitterMs = Math.floor(Math.random() * 250);
await new Promise(resolve => setTimeout(resolve, baseMs + jitterMs));
}
}
Этот helper не знает лимиты конкретного аккаунта и не заменяет очередь. Его задача уже: не повторять ошибку квоты, ограничить число попыток и развести воркеры по времени.
PASS и FAIL после исправления
- PASS: один бизнес-ID создаёт одну задачу; 429 классифицирован; повторы ограничены; очередь после reset постепенно разгребается.
- PASS: в логе есть request ID и номер попытки, но нет API-ключа и текста клиента.
- FAIL: все воркеры просыпаются одновременно и снова получают 429.
- FAIL: ошибка расходов повторяется до истечения общего timeout.
Типичные ошибки
Двойной retry. Официальный SDK уже может повторять подходящие ошибки, а внешний worker добавляет ещё четыре попытки. Посчитайте фактическое число вызовов на одну задачу.
Нет idempotency на своей стороне. Повтор завершился успешно, но исходный worker не получил ответ и создал вторую запись в CRM. Фиксируйте статус по внутреннему ID операции.
Одинаковая задержка. Десять воркеров ждут две секунды и снова атакуют API вместе. Jitter нужен именно против этого.
Логируется только сообщение исключения. Тогда исчезают заголовки и request ID, а поддержке нечего сопоставлять.
Ограничьте параллельность до retry
Backoff регулирует повтор одной задачи, но не останавливает сотню новых задач. У очереди должен быть отдельный предел активных AI-запросов. Начните с малого числа воркеров, измерьте остаток requests и tokens, затем повышайте параллельность ступенями. Если 429 появляется сразу после увеличения, верните предыдущее значение.
Проверяйте не только число запросов. Длинная расшифровка может упереться в token limit при спокойном RPM. Записывайте размер входа и установленный максимум ответа рядом с бизнес-ID, но не сохраняйте конфиденциальный текст. Это покажет, какие задачи создают пик.
Чек-лист очереди
- У одной задачи есть стабильный ID и конечный deadline.
- Число одновременно выполняемых запросов задано конфигурацией.
- Повтор планируется в очереди, а не держит HTTP-процесс открытым.
- После исчерпания попыток задача уходит в отдельный статус, а не исчезает.
- Метрика backlog растёт тревожно, а не маскируется успешными retry.
Контрольный тест делайте на небольшой партии. Запустите десять обезличенных задач, дождитесь завершения и сравните: входных ID десять, финальных статусов десять, повторно записанных результатов нет. Только после этого возвращайте обычный поток.
Что делать, если не помогло
Остановите конкретную очередь, снимите обезличенный полный ответ и сравните количество бизнес-задач с количеством API-вызовов. Если вызовов больше, ищите повторную доставку webhook, второй worker или retry SDK. Если числа совпадают, проверьте Limits и Billing проекта. Для процессов анализа звонков мы можем разобрать AI-контур, а устройство безопасного ответа продолжает материал про валидацию JSON до записи в CRM. Границы автономных действий разобраны в статье про AI-агентов в продажах.



