Ваш ИИ-агент из туториала умрёт на 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 товаров.
Сюда же добавляется задержка: сто тысяч токенов на входе — это секунды только на чтение промпта, каждый раз.
нужна цифра: ваш реальный счёт до и после на проекте с каталогом — даже порядок («было четырёхзначное, стало двузначное») сильнее любого расчёта
Чек-лист перед тем, как пускать людей
- Вызовы модели вынесены из HTTP-хендлера в фоновую обработку.
- Есть свой ограничитель параллелизма, настроенный ниже лимитов провайдера.
- Ретраи с бэкоффом и разбросом, таймаут стоит на всю цепочку.
- В промпт уезжает окно диалога, а не вся история.
- Состояние диалога лежит в базе и переживает перезапуск.
- Есть лимит шагов на задачу и лимит токенов на пользователя в сутки.
- Алерт по расходам приходит за час, а не в конце месяца.
- Есть рубильник: ИИ-часть выключается, продукт продолжает работать.
Восемь пунктов. Ни один не про модель, все про инженерию вокруг неё — потому что на ста пользователях ломается именно она.
Промпт напишет каждый. Прод держат единицы — не потому, что это сложно, а потому, что в туториалах этого нет: там нет ста пользователей, поэтому нечему ломаться.
Если у вас на руках сборка, которая работает у вас на ноутбуке, и непонятно, что с ней случится на живых людях, — напишите одной строкой в телеграм . Отвечу, что развалится первым и в каком порядке это чинить.