ЕГОР ШУКИС | ЗАМЕТКА | ← ВСЕ ЗАМЕТКИ
ЗАМЕТКА / · 9 минут чтения

Ваш ИИ-агент из туториала умрёт на 100 пользователях. Показываю где

Шесть мест, где сборка «за 20 минут» рассыпается, когда к ней приходят живые люди: лимиты провайдера, синхронный хендлер, растущий контекст, ретраи, отсутствие потолка расходов и состояние в памяти процесса.

Демо ИИ-агента и работающая система — две разные инженерные задачи. Первая делается за вечер, и в интернете её показывают тысячу раз в день. Вторая занимает недели, и её почти никто не показывает: смотреть там не на что, а рассказывать про очереди и лимиты скучнее, чем про «собрал агента за 20 минут».

Проблема в том, что разница вылезает не постепенно. Она вылезает сразу, в тот день, когда вы пускаете на сборку живых людей.

Ниже — шесть мест, где ломается конкретно. Не «нужно думать о масштабировании», а что именно отваливается, в каком порядке и что с этим делать.

Провайдер считает не пользователей, а токены в минуту

В туториале вы один. Ваш агент делает три-четыре вызова модели на один ответ, и это незаметно.

Лимиты у провайдеров устроены не по пользователям. Они считаются в запросах и токенах в минуту на ваш ключ целиком. Сто человек, пришедших одновременно, — это не «сто раз то, что было в демо». Это одна общая очередь, в которую ваши сто человек ломятся с четырьмя вызовами каждый.

Что происходит дальше: часть запросов возвращает 429. Агент, который делает цепочку из нескольких шагов, падает не в начале, а на пятом шаге из семи — когда пользователь уже прождал сорок секунд. Он видит «что-то пошло не так», а вы видите в логах ошибку, которую невозможно повторить у себя локально: у вас-то один запрос в минуту.

Лечится это не строчкой retry. Лечится очередью на своей стороне: собственный ограничитель параллелизма, который держит нагрузку на ключ ниже лимита, приоритеты (интерактивный запрос вперёд фоновой обработки) и честный ответ пользователю, что он в очереди, вместо молчания на сорок секунд.

Агент думает сорок секунд, и это кладёт весь сервис

Самая частая ошибка в сборках из туториалов — вызов модели прямо в HTTP-хендлере.

Обычный веб-запрос живёт десятки миллисекунд. Вызов LLM — секунды, цепочка вызовов — десятки секунд. Всё это время воркер занят: он не считает, он ждёт. Пул воркеров конечен. Когда все воркеры стоят в ожидании ответа от модели, ложится не только ИИ-функция. Ложится весь сервис, включая страницы, которые к ИИ отношения не имеют вообще.

Отсюда самый обидный класс аварий: у вас «упал сайт», хотя сайт в порядке. У вас кончились воркеры.

Правильная схема простая и старая: запрос принимается, ставится в очередь, клиент сразу получает идентификатор задачи, обработка идёт в фоне, результат приезжает через подписку или поллинг. Это не архитектурное излишество, это разница между «медленно» и «лежит».

История диалога дорожает быстрее, чем растёт продукт

Модель не помнит предыдущие сообщения. Каждый следующий запрос везёт с собой всю переписку заново. Двадцатая реплика — это двадцать реплик в промпте.

Значит, стоимость одного ответа растёт линейно по длине диалога, а стоимость диалога целиком — квадратично. В демо диалог из трёх реплик, и это незаметно. В проде люди пишут по сорок, а самые ценные пользователи — те, кто не уходит, — стоят вам дороже всех остальных.

То же самое с задержкой: чем длиннее промпт, тем дольше ответ. Пользователь, который пользуется продуктом активнее всех, получает самый медленный сервис. Это ровно противоположно тому, что вам нужно.

Что с этим делают: окно последних сообщений вместо всей истории, суммаризация старого хвоста, вынос фактов о пользователе в структурированное состояние (там им и место — это данные, а не текст), кеш стабильного префикса промпта на стороне провайдера. Сессии на сотню с лишним сообщений живут нормально, но только если в модель уезжает не вся сотня.

Ретраи без головы превращают сбой провайдера в аварию

Провайдер отвечает пятисоткой или отваливается по таймауту — это нормальная часть жизни, а не исключительная ситуация.

Наивный код повторяет запрос сразу. Когда провайдер деградирует, «сразу» делают все ваши пользователи одновременно, и вы добавляете к чужому сбою свой собственный шторм. Дальше — по классике: очередь растёт, таймауты растут, ретраи растут, всё это кормит само себя.

Минимум, который должен быть: экспоненциальный бэкофф со случайным разбросом, потолок числа попыток, разные стратегии на разные ошибки (429 — ждать, 500 — повторить, невалидный ответ модели — не повторять вслепую, а чинить парсинг), и идемпотентность там, где вызов что-то меняет. Отдельно — таймаут на всю цепочку целиком, а не только на каждый вызов: иначе агент из семи шагов законно ждёт семь таймаутов подряд.

У расходов нет потолка, пока вы его не поставили

Агент с инструментами и циклом «подумай — вызови — подумай» умеет уходить в петлю. В демо это невозможно заметить: там один запуск, вы смотрите на него глазами.

В проде петля стоит денег и работает ночью, когда на неё никто не смотрит. Один пользователь, у которого агент зациклился на неудачном инструменте, способен за ночь сжечь бюджет месяца.

Что ставится до запуска, а не после первого счёта: жёсткий лимит числа шагов на задачу, лимит токенов на пользователя в сутки, алерт по расходам за час (не за месяц — за час), и рубильник, который отключает ИИ-функции, оставляя продукт работать. Последнее особенно важно: если ваш продукт без ИИ-части превращается в кирпич, у вас не продукт с ИИ, а ИИ с интерфейсом.

нужна цифра: если помните конкретный случай с петлёй агента или неожиданным счётом — здесь ему идеальное место, один абзац с суммой и сроком делает всю статью

Состояние в памяти процесса

Последнее, самое дешёвое в починке и самое обидное. В демо история диалога лежит в словаре в памяти процесса. Это работает ровно до момента, когда инстансов становится два.

Дальше балансировщик отправляет второе сообщение пользователя на вторую реплику, которая про диалог ничего не знает. Пользователь получает «бот забыл, о чём мы только что говорили». Перезапуск сервиса — то же самое, только для всех сразу.

Состояние диалога — это данные. Им место в базе, а не в памяти воркера.

Что из этого чинится за вечер

Что ломается Минимальное решение Порядок работ
429 от провайдера свой ограничитель параллелизма перед ключом вечер
Сервис ложится целиком очередь + фоновая обработка + статус задачи 2–3 дня
Счёт растёт с длиной диалога окно сообщений, суммаризация хвоста, кеш префикса день
Шторм ретраев бэкофф с разбросом, потолок попыток, таймаут на цепочку вечер
Нет потолка расходов лимиты на задачу и на пользователя, алерт за час, рубильник день
«Бот забыл» состояние диалога в базу вечер

Ни одна из этих задач не требует переписывать систему. Все они требуют, чтобы кто-то один раз посмотрел на сборку не как на демо, а как на систему, к которой придут люди.

Сколько это стоит в деньгах

Самый дорогой пункт в списке — не отказоустойчивость, а контекст. Покажу на расчёте.

Бот отвечает на вопросы по каталогу из 500 товаров. Наивная реализация кладёт весь каталог в промпт на каждый вопрос — так делают все туториалы, потому что так короче код.

Допущения открытые, это расчёт, а не замер с чужого прода:

  • весь каталог в промпте — порядка 100 000 токенов на вопрос;
  • поиск по каталогу и подстановка только релевантных позиций — порядка 2 000 токенов на вопрос;
  • 10 000 вопросов в месяц;
  • цена входа — $2,5 за миллион токенов.

Наивно: 10 000 × 100 000 = 1 миллиард входных токенов, около $2 500 в месяц. Через поиск: 10 000 × 2 000 = 20 миллионов токенов, около $50 в месяц.

Разница в пятьдесят раз, и это при одинаковом качестве ответов — релевантные позиции модель видит в обоих случаях, просто во втором она не читает заодно все остальные 498 товаров.

Сюда же добавляется задержка: сто тысяч токенов на входе — это секунды только на чтение промпта, каждый раз.

нужна цифра: ваш реальный счёт до и после на проекте с каталогом — даже порядок («было четырёхзначное, стало двузначное») сильнее любого расчёта

Чек-лист перед тем, как пускать людей

  1. Вызовы модели вынесены из HTTP-хендлера в фоновую обработку.
  2. Есть свой ограничитель параллелизма, настроенный ниже лимитов провайдера.
  3. Ретраи с бэкоффом и разбросом, таймаут стоит на всю цепочку.
  4. В промпт уезжает окно диалога, а не вся история.
  5. Состояние диалога лежит в базе и переживает перезапуск.
  6. Есть лимит шагов на задачу и лимит токенов на пользователя в сутки.
  7. Алерт по расходам приходит за час, а не в конце месяца.
  8. Есть рубильник: ИИ-часть выключается, продукт продолжает работать.

Восемь пунктов. Ни один не про модель, все про инженерию вокруг неё — потому что на ста пользователях ломается именно она.


Промпт напишет каждый. Прод держат единицы — не потому, что это сложно, а потому, что в туториалах этого нет: там нет ста пользователей, поэтому нечему ломаться.

Если у вас на руках сборка, которая работает у вас на ноутбуке, и непонятно, что с ней случится на живых людях, — напишите одной строкой в телеграм . Отвечу, что развалится первым и в каком порядке это чинить.