Разбор · ИИ-агенты · Эвалы

ИИ-агент в продакшне: браузер, тулы и эвалы

Собрать демо агента — 15 минут с любым фреймворком. Довести его до настоящего продакшна — год работы, и почти вся сложность лежит там, куда не заглядывают туториалы: где живёт стейт, как течёт стриминг, почему каждая модель ломает формат по-своему и как вообще тестировать систему, которая на один и тот же вопрос отвечает по-разному. Разбор одного честного пути — агент-аналитик поверх self-service BI, целиком в браузере.

С чего начинает фронтендер: агент в браузере

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

Второе следствие — stateless. Можно было бы поднять стейт на сервере, но тогда на каждый из десятков инстансов Node пришлось бы шарить состояние через общий Redis: запрос приземляется на случайный инстанс, и где-то надо держать сессию по айдишнику. Это дорого. Держать весь контекст диалога в браузере конкретного пользователя — почти бесплатно. Поэтому браузер собирает контекст сам и ходит наружу только за следующим ответом — ровно как локальный кодинг-агент на твоей машине, только агент живёт во вкладке.

СТЕЙТ НА СЕРВЕРЕ Браузер запрос → случайный Node Node Node …×40 Redis общая сессия по id шарим сессию между инстансами → лишняя инфра, дорого СТЕЙТ В БРАУЗЕРЕ Браузер весь контекст диалога только за следующим ответом Node Node Node Redis не нужен инстансы ничего не помнят → почти бесплатно
Один и тот же выбор с двух сторон. На сервере запрос каждый раз приземляется на случайный из десятков инстансов, поэтому сессию приходится шарить через общий Redis — лишний слой инфраструктуры. В браузере весь контекст держит клиент конкретного пользователя, инстансы остаются stateless, Redis не нужен — почти бесплатно. Отсюда и решение вынести стейт в браузер.

Водопровод: BFF, стриминг и имитация SSE

Напрямую из браузера в инференс ходить нельзя — спалишь ключи и системные промпты. Поэтому между ними стоит тонкий BFF на Node: он проксирует запрос, доклеивает ключи, промпты и мониторинг и улетает в модель. Сам по себе он ничего не помнит — весь стейт по-прежнему в браузере.

Дальше выясняется, что никто не готов смотреть на крутилку, пока модель думает, — нужен стриминг. Классический выбор — SSE, но у него неудобное свойство: это GET, и он предполагает stateful-бэкенд (сначала получаешь айдишник, потом вторым запросом тянешь стрим по нему). Для stateless-схемы это не годится. Поэтому SSE имитируют руками: POST-запрос, в ответ открывается стрим, и чанки из него вычитываются вручную. Работает — и, как выясняется, ровно так делают все.

БРАУЗЕР Агент · луп тулы · стриминг Стейт диалога весь контекст здесь BFF · Node stateless · прокси ключи · промпты Инференс внешний API OpenAI-совместимый ① сообщения ④ имитация SSE ③ стрим чанков
Агент и стейт — в браузере; тонкий stateless-BFF прячет ключи и проксирует в инференс. Стейт на клиенте почти бесплатен — на сервере пришлось бы шарить его через Redis на десятки инстансов. Ответ возвращается стримом через имитацию SSE поверх обычного POST.

Тулы, чарты и агентский цикл

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

Важная деталь про доверие. Модели не показывают сырые числа в чате — вместо этого она строит настоящий интерактивный чарт поверх настоящего датасета. Пользователь заходит в него, проверяет, что фильтры наложены правильно, двигает период. Доверие идёт не от того, что «модель написала циферку», а от того, что за ответом стоит реальный график, который можно перепроверить глазами. Плюс отдельный тул-судья в чистом контексте проверяет, что ответ соответствует вопросу и фильтры выставлены верно.

И ещё один шаг на входе — классификатор. Он отсекает вопросы не по теме: встроенному аналитику незачем отвечать, как приготовить свиные крылышки, — это просто сожжённые токены. Всё, что не про данные, лучше отрезать сразу. А то, что про данные, модель отвечает не из «памяти» (там галлюцинации, ничем не валидированные), а подтягивая куски документации через RAG.

список чартов → данные → строит чарт Вопрос Классификатор по теме? Агент · луп тул → данные → чарт Судья ответ ⟷ вопрос Ответ + чарт не по теме → стоп
Классификатор отрезает вопросы не по теме — это сожжённые токены. Дальше агентский цикл на тулах: список чартов → данные → построенный чарт. В конце судья в чистом контексте проверяет, что ответ отвечает на вопрос и фильтры выставлены верно. Доверие — не к цифре в чате, а к чарту, который можно перепроверить.

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

Зоопарк моделей: почему переносимость — это ад

Соблазн думать, что агент пишется один раз. На деле каждый инференс ведёт себя по-своему. Вышла новая версия популярной open-модели — и формат ответа поменялся: она вдруг начала заворачивать ответ во внешний объект, которого раньше не было. Чистка системного промпта помогает, но каждый такой переезд — расследование: почему лезет reasoning, почему не отключается, почему чанки летят иначе, у кого-то долетает usage, у кого-то нет.

Глубже — два слоя. Каждая модель по-своему понимает tool-calling, и вызовы приходится выкусывать разными регэкспами. Те, кто делает универсальный инференс, вынуждены знать особенности каждой модели и унифицировать их у себя — но если ходишь между несколькими источниками, каждый унифицировал по-своему, и договориться они не могут. А в on-prem ещё веселее: клиент приносит «любой OpenAI-совместимый API», и что там внутри — неизвестно.

Лечится это не файликом-под-каждую-модель, а общим кодом, который становится всё более устойчивым: он не знает про конкретную модель, он знает — пришло или не пришло это поле в структуре ответа; если не пришло — делаем так. Умные топовые модели прощают кривой промпт и переваривают всё; поэтому целиться надо не в них, а идти в обратную сторону — разгружать контекст и упрощать так, чтобы справилась и слабая модель. На on-prem вполне может стоять быстрый, но не самый умный 120B, и он тоже должен всё это переварить.

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

Эвалы: самая дорогая часть

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

Железный пользователь третья LLM + pass^k · очень дорого LLM-судья по каждому критерию отдельно · дорого Код-парсинг · смоуки матчим значение, без LLM · дёшево дороже ↑ дешевле
Перевёрнутая пирамида: чем выше — тем дороже, медленнее и реже гоняешь. Система недетерминированная, поэтому каждый уровень прогоняют по многу раз и смотрят на устойчивость (pass@k / pass^k), а не на один прогон. Для оценки согласованности судей идёт в ход настоящая статистика — вроде каппы Коэна, которая показывает, не случайно ли эксперты сошлись.

И одного прогона мало. Систему недетерминированную гоняют по многу раз и смотрят не на один результат, а на устойчивость: остался ли агент за пять прогонов в тех же границах. Порог не обязан быть 100% — где-то допустимо флапать, считаешь «прошли на 80% — ок». Эвалы дороже и хрупче, чем классические E2E-тесты: каждый прогон стоит токенов, а любое изменение может их «сломать», хотя это далеко не всегда настоящая поломка — много false negative, которые надо разгребать. Отсюда стратегия — забрать максимум дешёвыми проверками кодом и звать судью только там, где без него никак.

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

Заменит ли это аналитиков

Короткий ответ — пока нет. Маленькому бизнесу биай-система, возможно, и не нужна: взял кодинг-агент, поднял ClickHouse, построил дашборды сам. Большому — нужна, и там же вопросы безопасности. Но данных, которые вытаскивает виртуальный аналитик, для качественной аналитики пока недостаточно. Настоящий аналитик на вопрос «какие были продажи» не отвечает одной цифрой — он добирает контекст, объясняет, почему так, что ещё может понадобиться. Это как «заменят ли тестировщиков разработчиками»: тесты написать можно, но вокруг куча теории, без которой продукт не построишь.

Разработка без рук: план → ревью → реализация → валидация

Отдельная тема — как всё это пишется. Код руками почти не пишется с зимы. Флоу устроен как цикл из нескольких моделей, проверяющих друг друга:

← очередь тикетов / багов ① План модель A ② Реализация сильная модель ③ Валидация эвалы · Playwright ④ Ревью + правки вторая модель, diff модель A пишет, модель B ревьюит, агент сам себя гоняет не ок → в цикл PR
Несколько моделей проверяют друг друга: план ревьюит вторая модель, код после реализации — тоже, а на сложных задачах агент сам себя гоняет через эвалы и Playwright, пока не сойдётся. На входе — очередь тикетов; так закрывались баги, что висели по два месяца.

Поверх — автоматизация входящего потока. Багрепорты из внутренней кнопки падают в очередь; агенту говоришь «посмотри очередь, найди проблемы в формулах» — он забирает тикеты, сам поднимает эвал-систему, что-то правит разово, а что-то добавляет в корзинку эвалов. Так закрылись тикеты, что висели по два месяца. Повторяющееся — сохраняется в скиллы, чтобы не настраивать руками. Тот же приём на дежурстве, когда команда неделю работает третьей линией поддержки: 50–70% тикетов закрываются моделью — она находит, кто последним трогал код, связывает всплеск ошибок с конкретным релизом, разгружает дежурного.

Почему работы стало больше, а не меньше

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

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

Куда это движется

Скорость выросла в 3–5 раз: задача, которая раньше делалась неделю, закрывается за день, а что-то можно запустить на ночь и забрать результат к утру. Направление — меньше кода, больше продуктового понимания: обвешивать систему метриками, общаться с клиентами, потому что время освободилось и его логично вложить в качество. Контур будущего — не команда вокруг продукта, а один-два человека вокруг большой штуки, под которыми крутится куча моделей (совсем один — рискованно из-за того же бас-фактора).

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