7 уроков при создании чат-агента: разбор инженеров Trigger.dev
Инженеры Trigger.dev сделали чат-агента для собственного дашборда: он помогает разбираться с упавшими задачами, строит графики по данным, ищет по документации. Через несколько месяцев эксплуатации они выложили в блог честный разбор — семь вещей, которые узнали уже после запуска, с ссылками на реальный код. Ниже — пересказ всех семи уроков по-русски: от «первый ответ должен прийти быстро» до автооценки ответов моделью-судьёй. Оригинал — 7 things we wish we'd known before building an AI chat agent.
Первый ответ — сразу, даже если агент ещё не готов
Если агент делает настоящую работу (запускает инструменты, лезет в файлы, поднимает песочницу), он стартует не мгновенно. Но пользователь, который пялится на пустой лоадер, решает, что всё сломалось. Решение Trigger.dev — разделить ответ на два этапа: первый вызов модели идёт прямо из обработчика запроса и стримится пользователю сразу, а тяжёлый агент поднимается параллельно в фоне. Первый ответ обычно и есть вызов инструмента — и уже одно это показывает человеку, что процесс живой: видно, как агент вызывает get_run("abc123"), хотя сам агент ещё догружается.
Хитрость в передаче контекста: фоновому агенту отдают всё из первого шага (текст, вызовы инструментов), промпт и модель берут те же — иначе кэш не сработает, а тон ответа сменится на полуслове. По их опыту это вдвое сократило время до первого полезного отклика.
Зачем это нужно, хорошо видно на наших цифрах: по 4912 завершённой генерации изображения за 14 дней (1–14 октября 2026, база сервиса) медиана от старта до готового файла — 78 секунд, а p95 — 236 секунд. Если интерфейс всё это время молчит, половина пользователей уверена, что всё зависло. Показывайте прогресс, даже если это просто «вызываю инструмент».

Считайте токены честно — иначе сжатие разговора сработает зря
Длинные диалоги сжимают: сводят и подрезают, чтобы влезть в контекстное окно. Срабатывать это должно только когда разговор реально длинный, а для этого нужен точный подсчёт токенов. Первая версия Trigger.dev складывала входные токены каждого шага — но каждый шаг уже содержит всю предыдущую переписку. Итог: один вопрос с пятью вызовами инструментов посчитали как 178 926 входных токенов, хотя реально было 39 557. Сжатие запускалось задолго до нужды. Лечится просто: берите входные токены последнего шага — всё, что модель выдала раньше, на следующем шаге и так станет входом.
Ограничьте повторы — модель будет долбить инструмент до победного
Если вызов инструмента упал, модель почти наверняка попробует снова. И ещё раз. И ещё. Считайте подряд идущие неудачи и ставьте жёсткий лимит, причём внутри самого инструмента: сетевые миграции туда не относятся — их инструмент ретраит сам с коротким backoff, и в счёт они не идут. У Trigger.dev это выглядит так: после трёх неудачных запросов подряд агент получает не новую ошибку, а внятное «лимит исчерпан, отвечай тем, что уже есть».
Кстати, про повторы: по 5301 генерации изображений на нашем сервисе за те же 14 дней повторная попытка понадобилась лишь 89 раз — это 1,7% запросов. Модели не долбят по упавшему инструменту вечно, если им явно сказать «стоп».
Разговор должен переживать обрыв связи
Соединение рвётся по разным причинам, и наивная реализация после переподключения показывает ответ, который «генерится» вечно. Решение — каждому событию стрима присвоить ID: при переподключении клиент говорит «последний чанк у меня был 43», сервер доигрывает всё с 44-го и дальше ведёт стрим вживую. Для этого ответ не может быть просто HTTP-ответом — нужен durable-сток (у них поверх объектного хранилища, есть готовые библиотеки поверх Redis, например resumable-stream).
Заодно они советуют показывать вызовы инструментов по мере их появления — их аудитория разработчики, им это нравится. И вторая сторона медали: входящие сообщения пользователя писать в упорядоченный durable-инбокс. Агент проверяет его между шагами (сообщение «на руль» — прямо в текущий ход) и между ходами (старейшее — в очередь на следующий). У Claude Code это клавиша Enter против Ctrl+Enter.
Версионировать промпты — скучно, но без этого никуда
Промпт — это код, и итерации по нему должны быть дешёвыми, иначе вы будете вносить меньше правок, чем нужно. Схема Trigger.dev: промпт определён в коде, каждая версия сохраняется, переключиться на новую можно из дашборда без деплоя. Каждая версия помечена моделью — сразу видно, если конкретная модель генерит много плохих ответов. В связке с автооценкой это превращает промпт-инженерию из шаманства в измеряемую работу.
Инструменты, заточенные под агента
Тут список коротких, но метких советов:
- Покажите инструменту границы: если результаты обрезаются — это должно быть видно и в описании, и в самом ответе API.
- Дайте агенту рендерить: инструмент
render_viewс карточками ошибок, графиками в фирменных цветах и кнопкой «дальнейшие шаги» оказался неожиданно востребованным. - Дайте инструмент обратной связи — обычный API-эндпоинт, куда агент может отправить репорт. Петля отладки резко укорачивается.
- Дайте контекст пользователя: инструмент
get_current_page(«что сейчас смотрит человек») стоит дорого и окупается быстро. - Суб-агенты распараллеляют задачи и не раздувают контекст основного.
- И дайте агенту машину: возможность скачать код пользователя, запустить скрипт, воспроизвести падение.
Автооценка: 10% диалогов проверяет модель-судья
Каждый завершённый ход с вероятностью 10% уходит в фоновую задачу оценки: модель-судья получает вопрос, результаты инструментов и ответ, ставит оценки от 1 до 5 по трём осям (опирается на факты, ответил по сути, лаконично) и флагует пропущенный инструмент, пробел в документации или галлюцинацию. Оценки хранятся вместе с версией промпта — вопрос и ответ нет. Когда диалог помечен как плохой, инженер открывает трассировку шагов и видит, где именно всё поехало.
Тот же принцип — классифицировать отказы, а не просто считать их — экономит нервы и у нас: по логу ошибок генераций за 14 дней (1–14 октября 2026) из 297 неудачных генераций примерно 40% — срабатывание контент-фильтров, ещё заметная доля — слишком длинный промпт и неподдерживаемое соотношение сторон. Сразу видно, что чинить надо подсказки пользователю про фильтры и лимиты, а не инфраструктуру.
Если сами собираете чат-агента, начните с первого урока: быстрый первый ответ даёт больше всего «вау» на вложенный час работы. А у нас на блоге уже есть разбор, чем агент отличается от чат-бота и как устроен агент-приложение с постоянным чатом — эти два материала хорошо ложатся на уроки выше.