Часть 2 из 2. В первой части был холивар про самодельные фреймворки. Здесь - цифры: мы дали ИИ-агенту построить одно и то же приложение на Джеймиксе и на голом Spring и честно сравнили результаты.
О чём эта часть
В первой статье я утверждал: строя на голом Spring, вы уже пишете свой Джеймикс - вручную и плохо. Комментаторы возражали: не всё из коробки нужно в проде. Хорошо. Проверим на агенте: когда код пишет не человек, а ИИ - помогает ему фреймворк или мешает?
Мы ждали простого ответа: «меньше кода» или «дешевле токены». Ответ оказался другим, и он интереснее.
Как устроен эксперимент
Один агент (модель GLM-5.2 в Codex CLI), одна задача, два стека.
Задача. Мини-конвейер кредитных заявок: четыре сущности, CRUD-экраны, две роли с разными правами (менеджер и офицер), детерминированный скоринг по формуле, локализация, данные переживают перезапуск. Задача заморожена и байт-в-байт одинакова для обоих стеков. Полный текст - в спойлере ниже.
Два стека - старались сделать максимально сопоставимыми.
Голый Spring |
Джеймикс |
|
|---|---|---|
Основа |
Spring Boot 3.5.16 (Spring Initializr) |
Jmix 2.8.2 Full-Stack (визард Studio, дефолты) |
Стартеры / аддоны |
web, data-jpa, security, validation, Liquibase, HSQLDB |
Базовый Full-Stack-набор (data, security + UI-безопасность, Flow UI, datatools); маркетплейс-аддонов — ноль |
Spring под капотом |
Boot 3.5.16 |
Boot 3.5.14 (привозит Jmix) |
Java |
21 |
21 |
Сборщик |
Gradle 8.14.5 |
Gradle 8.14.4 |
БД и миграции |
HSQLDB (файловая) + Liquibase |
HSQLDB (файловая) + Liquibase |
Инструкции агенту |
общий AGENTS.md, без подсказок про фреймворк и домен |
AGENTS.md + скилл-пак: 20 инструкций, как правильно делать сущности, экраны, роли и миграции именно в Джеймиксе |
Всё остальное одинаково: модель, окружение, база, задача. По три зачётных прогона на каждый стек (всего с пилотами и калибровкой — тринадцать). Весь бенчмарк обошёлся примерно в 45 долларов .
Приёмка. Каждый прогон проверялся по фиксированному чек-листу плюс скрытым тестом: 12 замороженных сценариев скоринга прогонялись через реальный сервис приложения. Замечания агенту выдавались пачкой, после исправления - повторная проверка. Важное правило: права ролей проверяем, заходя под каждой ролью отдельно, а не под админом. Почему это важно - увидите ниже.
Чек-лист приёмки (7 пунктов, одинаков для обоих стеков)
Приложение собирается и стартует; схема БД применяется автоматически; данные переживают перезапуск.
CRUD по всем четырём сущностям через интерфейс; редактирование любой записи сохраняет обновление, а не создаёт дубликат.
Каждая смена статуса заявки оставляет запись в истории статусов.
Скоринг совпадает с эталоном на всех 12 скрытых сценариях.
Права ролей: недоступное действие и блокируется на сервере, И не предлагается в интерфейсе. Проверяется входом под каждой ролью; вход и выход работают.
Подписи интерфейса на месте, единый язык.
Ошибки показываются в понятном виде — без стектрейсов и сырого JSON.
Прогон засчитан, когда все семь пунктов зелёные. Дополнительно фиксировались (но не требовались) метрики: написанные агентом тесты, объём кода, токены, деньги, вмешательства.
Полный текст задачи (TASK.md)
# Задача: мини-конвейер кредитных заявок Построй веб-приложение с базой данных для обработки кредитных заявок. Требования — по поведению; технологии выбирай согласно настройкам проекта. ## Сущности 1. **Клиент**: ФИО, дата рождения, ежемесячный доход, ежемесячные платежи по текущим долгам, стаж работы (месяцев). 2. **Кредитный продукт**: название, минимальная сумма, максимальная сумма, годовая ставка (%), максимальный срок (месяцев), порог скоринга. 3. **Заявка**: клиент, продукт, запрашиваемая сумма, срок (месяцев), статус, балл, решение, дата создания. Статусы: NEW, SCORING, APPROVED, MANUAL_REVIEW, REJECTED. 4. **История статусов заявки**: заявка, статус «из», статус «в», время, комментарий. Создаётся при каждой смене статуса заявки. ## Экраны CRUD (список + создание/редактирование) для всех сущностей. Запуск скоринга по заявке из её экрана. ## Скоринг 1. Ежемесячный аннуитетный платёж: `P*r*(1+r)^n/((1+r)^n-1)`, где P — сумма, r = годовая ставка/12/100, n — срок в месяцах; при r=0 платёж = P/n. 2. DTI = (платежи по текущим долгам + новый платёж) / доход. 3. Knock-out → решение REJECTED (балл всё равно считается): возраст <21 или >70; DTI>0.60; сумма вне [min,max] продукта; срок > макс. срока продукта. 4. Балл 0-100 = взвешенная сумма (границы диапазонов: нижняя включительно, верхняя исключительно; округление half-up): — DTI (вес 0.40): `clamp(100*(0.60-DTI)/0.40, 0, 100)`. — Доход (0.20): ≥150000→100; ≥80000→70; ≥40000→40; иначе 10. — Стаж мес. (0.15): ≥36→100; ≥12→60; ≥3→30; иначе 0. — Возраст (0.10): [30,55)→100; [25,30)∪[55,65)→70; [21,25)∪[65,70]→40. — Сумма/лимит (0.15): ratio=сумма/макс.сумма; ≤0.30→100; ≤0.70→70; иначе 40. 5. Решение: балл ≥70→APPROVED; 50-69→MANUAL_REVIEW; <50→REJECTED; knock-out→REJECTED. ## Роли — **Кредитный менеджер**: CRUD заявок и клиентов, запуск скоринга. Не может редактировать продукты и не может подтверждать заявки в MANUAL_REVIEW. — **Кредитный офицер**: подтверждение/отклонение заявок в MANUAL_REVIEW, управление продуктами. ## Прочее — На первом старте схема применяется автоматически; созданные данные **сохраняются между перезапусками** приложения. — Подписи интерфейса локализованы (единый язык). — Действие, недоступное текущей роли, **не предлагается в интерфейсе** (кнопка/пункт скрыты или заблокированы), а не только отклоняется на сервере. Вход и выход из системы работают. — Редактирование существующей записи открывает её данные и сохраняет **обновление** (не создаёт дубликат) — для любой записи, включая первую в списке. — Ошибки (отказ в доступе, валидация и т.п.) показываются пользователю в понятном виде, без сырых технических ответов.
Детали стенда: провайдер, цены, как чинили грабли
Провайдер зафиксирован. OpenRouter по умолчанию раскидывает запросы между разными хостерами модели - а у них разные цены, кэш и даже качество (квантизация). Первые прогоны на этом миксе дали «поехавшие» цифры, поэтому для зачёта мы жёстко зафиксировали одного провайдера (z.ai). Цены на момент прогонов: $1.40 за миллион входных токенов, $4.40 за выходные, $0.26 за кэшированный вход.
Кэш решает всё в цене. Доля кэша в прогонах - 97-99%, а кэшированный вход в 5 с лишним раз дешевле обычного. Наивная формула «токены умножить на цену» завышает стоимость в разы. Мы считали с учётом кэша и сверялись с дашбордом провайдера - сходится с точностью до ~6%.
Скилл-пак дорабатывали по ходу. Первые прогоны на Джеймиксе вскрыли повторяющиеся ошибки агента - все в одном месте: там, где он уходил от идиомы фреймворка в самодельный код. Например, агент заводил роли, но забывал дать им право на вход в интерфейс, и пользователи не могли залогиниться. Мы превратили эти грабли в правила скилл-пака и перепрогнали Джеймикс-прогоны на исправленной версии (Spring-прогоны не трогали, там не было скилов в принципе). Результат честный и поучительный: ошибка входа исчезла во всех прогонах, а вот самодельную проверку ролей агент один раз написал снова вопреки прямому запрету в правилах. Правило, которое фреймворк может принудить механикой, работает всегда; правило-совет лишь снижает риск.
Один прогон сожгли. Провайдер завис, агент час гонялся за несуществующим багом и пробил лимит токенов ($5 в трубу). Прогон целиком выкинули из зачёта и запустили заново с чистого листа.
Что намерили
Стек |
Прогон |
Стоимость |
Токены |
Замечаний |
Приёмка |
|---|---|---|---|---|---|
Spring |
1 |
$3.14 |
10.2M |
2 |
после исправления |
2 |
$2.39 |
7.9M |
1 |
после исправлений |
|
3 |
$3.94* |
13.3M |
3 |
после исправления |
|
Джеймикс |
1 |
$1.82 |
6.0M |
0 |
с первого раза |
2 |
$2.58 |
8.8M |
0 |
с первого раза |
|
3 |
$2.28 |
7.7M |
1 |
после исправления |
* третий Spring-прогон единственным за всю серию вышел за наш лимит в 12 млн токенов - как раз на раунде исправления трёх дефектов. Мы это не прячем: до исправлений он стоил $2.78, лимит пробил именно ремонт.
Из таблицы - три вывода.
1. По деньгам разницы нет. Джеймикс - $1.82–2.58, Spring - $2.39–3.94. Прогоны на одном и том же стеке отличаются между собой сильнее, чем Джеймикс отличается от Spring. Страх «скилл-пак раздует расход» не подтвердился: кэш съедает вес инструкций. Цену на самом деле определяют замечания и паузы, а не выбор фреймворка. Говорить «на Джеймиксе дешевле» наши данные не позволяют - и мы не говорим.
2. А вот по самостоятельности разница есть. Вопрос: доходит ли агент до приёмки сам, без человека? На Джеймиксе - два прогона из трёх дошли без единого замечания. На Spring - ни один. Причём Spring спотыкался каждый раз об одно и то же: права доступа. Это не невезение, это свойство стека, почему, разберём ниже.
3. Кода на Spring больше и он менее предсказуем. Посчитали по всем шести прогонам (только код, написанный агентом, диффом от стартового проекта, без тестов). Медиана: 1826 строк на Джеймиксе против 2304 на Spring больше на 26%.
Куда пошли строки: таблица и цена переписываний
Прогон |
Java |
Фронтенд |
Экраны/миграции |
Локализация |
Всего |
|---|---|---|---|---|---|
Джеймикс 1 |
1105 |
0 |
568 |
73 |
1746 |
Джеймикс 2 |
1120 |
0 |
640 |
66 |
1826 |
Джеймикс 3 |
1166 |
0 |
719 |
78 |
1963 |
Spring 1 |
1576 |
601 |
211 |
61 |
2449 |
Spring 2 |
1261 |
524 |
185 |
0 |
1970 |
Spring 3 |
1240 |
858 |
197 |
9 |
2304 |
Медианы: 1826 против 2304. Средние: 1845 против 2241. Берём медиану - у Spring разброс большой, среднее его сглаживает.
Считали только код, написанный агентом: то, что генерирует визард Джеймикса, исключено, как и вытащенные агентом «посмотреть» исходники фреймворка. И ещё важный нюанс: статичный подсчёт строк не видит переписываний. Конфиг безопасности на Spring агент переписывал до 14 раз за прогон, главную страницу по 6–10 раз. На Джеймиксе в самом чистом прогоне крутились два файла по 3–4 раза. Строка, написанная один раз, и строка, переписанная четырнадцать, стоят по-разному.
Скоринг: ничья. Формулы посчитались правильно на обоих стеках: скрытый тест 12 из 12 везде. Чистая бизнес-логика агенту одинаково по силам что там, что там. Запомните этот факт, он ещё выстрелит.
Что нашли
У агента нет памяти о стеке, и вопрос лишь в том, где это всплывёт
Мы вЫчитали все рассуждения агента по всем прогонам. Общее место: агент не «помнит» ваш стек, он угадывает и проверяет. Этот налог платят оба подхода, но в разных местах.
Spring платит в рантайме. Каждый прогон агент заново наступал на одни и те же грабли: первая запись получает id=0 (и начинается перебор стратегий генерации ключей), ленивая загрузка JPA взрывается на границе REST, процесс приложения умирает в фоне, статические файлы блокируются конфигом безопасности. Всё это выясняется только при запуске, а это дорого и каждый раз заново.
Джеймикс платит на компиляции. Агент так же уверенно угадывает — но неправильный импорт из Vaadin просто не собирается. Ошибка ловится за секунды, до всякого запуска. Компилятор и конвенции фреймворка работают как ограждение: дорогой рантайм-баг превращается в дешёвый цикл «не собралось — поправил».
Права доступа - ахиллесова пята голого стека
Spring спотыкался на правах в каждом из трёх прогонов. Причина структурная. Безопасность в Spring настраивается URL-паттернами, и единого источника правды просто нет: часть эндпоинтов неизбежно уезжает под общее правило «пускать всех залогиненных», и агент забывает сузить их до роли. В одном прогоне офицер мог создавать и удалять клиентов — а по заданию имел право только подтверждать заявки. Мы проверили запросами: сервер отвечал 200 там, где должен был отказать. Кнопки в интерфейсе при этом никто не прятал.
На Джеймиксе этот класс ошибок закрыт устройством самого фреймворка: одна декларативная политика управляет и сервером, и видимостью кнопок. Забыть «половину» просто негде.
И как чинится - тоже показательно. Spring-агент чинит переписыванием: «перепишу конфиг безопасности начисто», «перепишу фронтенд» — до 14 заходов на один файл, один из рерайтов заодно снёс миграцию с данными. Джеймикс-агент чинит точечно: одна аннотация, сужение одной политики. Размер исправления не растёт с размером приложения.
Честности ради: Джеймикс не броня. Когда агент уходил от идиомы фреймворка в самодельный код (сам писал проверку роли вместо декларации), ошибки возвращались и туда. Фреймворк исключает класс багов ровно до тех пор, пока агент держится его рельсов.
Агент не видит собственных ролевых багов. Вообще
Самая неприятная находка - общая для обоих стеков. Агент завершал каждый прогон уверенным «права настроены, всё работает». Проверяем под обычным пользователем — не работает. Его собственные проверки структурно слепы: тесты гоняются под админом с полным доступом, которому всё можно, и они физически не могут увидеть, что менеджер не может войти, а офицеру доступно лишнее.
Отсюда практическое правило, которое мы вносим в любой процесс с агентами: проверяйте права под каждой ролью отдельно, никогда только под привилегированной. Ровно эта проверка и поймала единственный дефект Джеймикс-прогонов.
А написал ли агент свой Джеймикс?
Обещанный ответ. Он двоякий и это, пожалуй, самый честный вывод бенчмарка.
Да, написал каркас. На голом Spring агент вручную собрал ровно те слои, которые Джеймикс даёт из коробки: свою подсистему пользователей и ролей, CRUD-эндпоинты, админку на полтысячи строк JS, слой DTO. По форме - самодельный Джеймикс. Тезис первой статьи подтвердился, только теперь фреймворк пишет не человек годами, а агент за прогон.
Нет, не написал рельсы. Агент воспроизводит структуру, но не гарантии, которые делают её безопасной: его самодельная безопасность течёт каждый прогон, его админка переписывается по десять раз, его грабли не превращаются в опыт. И главное - его каркас одноразовый: следующий прогон изобретает всё заново, с теми же дырами. Человеческий самопал хотя бы накапливается; агентский же выбрасывается и переизобретается.
Готовый фреймворк даёт агенту то, чего тот не может дать себе сам: правила, которые держатся без его памяти.
Неудобный вопрос: а нужен ли тут агент вообще?
Любой, кто работал с Jmix Studio, спросит: модель данных, экраны и роли там накликиваются визардами за десятки минут. Зачем жечь на это агента?
Вопрос болезненно точный, и наши данные его обостряют: все ошибки Джеймикс-прогонов пришлись ровно на то, что делается визардами (роли, экраны), а в логике, там где визарды бессильны, ошибок не было ни у кого. Гибрид «человек кликает визарды, агент пишет только логику» на нашей задаче дал бы примерно в десять раз меньше затрат по токенам и почти ноль дефектов.
Но и это не приговор агенту. Токены стоят копейки (напомню: весь бенчмарк на 14 ранов съёл $45). Дорогой ресурс - время человека, и гибрид его тратит больше: час ручной работы против двадцати минут присмотра. Один проект - выгодно руками; поток проектов - выгоднее агент. Он масштабируется параллельными запусками, а руки нет. Плюс гибрид требует уметь в Джеймикс, а агентный сценарий интересен как раз тем, кто пока не умеет.
Мы этот гибридный режим не замеряли, это честная прикидка и кандидат на следующий бенчмарк.
Что могло исказить картину
Три прогона на стек и один оператор это сконее направленный результат, не статистика. Выводы достаточно качественные качественные.
Старт не равный: проект Джеймикса из коробки включает готовую подсистему безопасности, Spring нет. Часть преимущества заслуга стартового проекта, а не скилл-пака.
Мы сравниваем «фреймворк + скилл-пак» против «голый стек», а не скилл-пак в вакууме, и его невозможно отделить от фреймворка. Кстати, отдельный контрольный запуск (Spring + общедоступный скилл-пак по Spring) показал: хорошие инструкции закрывают дыры безопасности и на голом стеке, знание можно принести и туда, вопрос лишь в том, что фреймворк приносит его из коробки.
Замеры делались в разное время и частично в разных условиях среды, точные доли процентов между стеками мы не абсолютизируем.
Вместо заключения
В первой части я обещал не размахивать холиварным тезисом, а положить на стол цифры. Кладу.
Деньги: ничья. Дешевле не стало и дороже не стало: $1.8-2.6 за прогон там и там.
Самостоятельность разница есть: Джеймикс-агент дважды дошёл до приёмки сам, Spring-агент ни разу.
Классы багов - главное. Голый стек наступает на одну и ту же дыру в правах каждый прогон и чинит её переписыванием всего конфига. Фреймворк закрывает эту дыру устройством, пока агент держится его правил, конечно.
И да, агент написал свой Джеймикс - каркас без гарантий, одноразовый, с одинаковыми дырами в каждой реинкарнации.
Тезис первой статьи бенчмарк не опроверг, а расширил: да, ваш агент напишет свой Джеймикс. Тоже плохой. Каждый раз заново. Если вы Java разработчик, то разобраться с новым стеком (JS в этом случае) и провести ревью вам будет сложнее, или вообще невозможно. В Джеймиксе же фронт на Java(VAADIN) + простой для понимания декларативный XML.
Выбор фреймворка в агентную эпоху это не про «меньше кода» и не про «дешевле токены». Это про ответ на один вопрос: на чём будут держаться правила вашего приложения - на конвенциях и компиляторе или на памяти агента, которой не существует? Другими словами : как заставить агента переиспользовать инженерные решения и получать воспроизводимый результат после каждой итерации, экономя ваше время на ревью и фикс скилл-паков.
Комментарии открыты. Если ваш агент стабильно выдаёт на голом стеке корректную ролевую модель без единого вмешательства, расскажите, как. Методология выше, задача в спойлере, воспроизводите. А пощупать сам фреймворк перед спором можно тут: сайт, документация, исходники.
ant1free2e
А для Spring Boot UI получался как в петклинике? Его ведь нельзя никому показывать без стыда, а чтобы сделать красиво придется еще пожечь токенов и это будет уже скорее свой common Jmix