Как проверить локальную LLM перед покупкой AI-среды?
Разбираем, как проверить локальную LLM на React, Go и PostgreSQL: от контракта и миграций до прав доступа, тестов, попыток и ревью.

Локальную LLM нельзя оценить по красивой генерации нового экрана. Перед покупкой AI-среды ей нужно дать небольшой, но неприятно реалистичный репозиторий и потребовать закончить сквозное изменение: поменять контракт API, провести миграцию PostgreSQL, сохранить разграничение доступа, дописать тесты и исправить дефект, который проявляется только после сборки всех частей. Такая работа быстро показывает, понимает ли модель систему или лишь уверенно продолжает текст.
Результат пилота выражают не впечатлениями от диалога, а числом попыток до принятого изменения и временем инженерного ревью. Эти две величины ловят то, что скрывают демонстрации: повторные подсказки, ручное переписывание, пропущенные последствия и патчи, которые компилируются, но не годятся для выпуска. Скорость первого ответа можно записать, однако покупать среду по ней опасно.
Пилот обязан воспроизводить ваш обычный репозиторий
Хороший пилот использует уменьшенную копию привычной архитектуры, а не игрушечный проект. Для связки React, Go и PostgreSQL достаточно одного бизнес объекта, двух ролей и нескольких состояний, но границы между слоями должны остаться настоящими. Если в рабочем коде есть OpenAPI, миграции, middleware авторизации, сгенерированный клиент и тестовые контейнеры, оставьте их в стенде. Именно на стыках модель чаще всего теряет контекст.
Подготовьте исходную точку, которую команда собирает одной командой. В ней должны проходить тесты, применяться миграции с нуля и работать короткий пользовательский сценарий. Зафиксируйте commit, версии инструментов и команды запуска. Иначе один кандидат получит теплый кеш и готовую базу, а другой потратит время на несовместимую версию генератора. Такое сравнение ничего не говорит о модели.
Задача должна быть новой для конкретного репозитория, но знакомой по типу вашей команде. Например, у заявки появляется состояние cancelled, отменять ее может автор до начала обработки, а администратор может сделать это в любой момент. Нужно вернуть причину запрета через API, показать кнопку только допустимому пользователю, сохранить дату отмены и не сломать старых клиентов. В этой формулировке уже есть контракт, данные, доступ, интерфейс и обратная совместимость.
Не давайте модели готовый план файлов. Иначе вы проверите исполнение инструкции, хотя покупаете способность исследовать кодовую базу. Разрешите читать весь стенд, запускать команды и задавать вопросы. Запретите сеть, реальные секреты и произвольные внешние зависимости. Локальность модели не превращает неизвестный пакет в безопасный пакет.
До запуска составьте эталон последствий, но не эталонный патч. Команда заранее отмечает, какие обработчики, схемы, запросы, компоненты и тесты должна затронуть задача. Этот список нужен ревьюеру, чтобы замечать пропуски одинаково у всех кандидатов. Если после пилота вы расширили список из-за разумного решения модели, сохраните пояснение: это полезное открытие, а не повод задним числом менять баллы.
Один и тот же репозиторий следует прогнать минимум в двух независимых сессиях с очищенным контекстом. Модель, которая один раз угадала нужный файл, еще не доказала повторяемость. При этом не смешивайте в одном результате разные модели, правила агентов и наборы инструментов. Вы покупаете всю среду, поэтому сравнивать нужно воспроизводимые конфигурации, а не название LLM в отрыве от оболочки.
Изменение API проверяет дисциплину контракта
Сильная модель начинает изменение API с поиска существующего контракта и потребителей, а не с правки первого найденного обработчика. Она должна выяснить, где живет источник истины: в OpenAPI, Go типах, маршрутах или отдельной схеме. Затем она меняет источник, обновляет производные файлы и проверяет, что React клиент получает новое состояние без ручного расхождения типов.
OpenAPI Specification описывает документ как языконезависимый контракт, который люди и программы могут понять без чтения исходного кода. Для пилота это практическая граница: если проект объявляет OpenAPI источником истины, патч в одном Go обработчике не считается изменением API. Модель обязана обновить схему, сгенерировать или согласованно поправить клиент и показать проверку совместимости.
Попросите сохранить старого потребителя. Самая показательная ловушка заключается не в добавлении поля, а в изменении смысла ответа. Если раньше запрет возвращал общий 403, а новый вариант добавляет машинный код cannot_cancel_started, старое поле и статус должны остаться понятными прежней версии интерфейса. Модель, которая ради удобства переписывает ответ целиком, создает скрытую миграцию клиентов.
Проверяйте не красоту JSON, а четыре свойства. Успешный ответ соответствует схеме. Ошибки имеют стабильный код, а не текст, который интерфейс вынужден разбирать. Необязательные поля действительно допускают отсутствие. Сгенерированные типы не редактируются вручную, если репозиторий считает их производными. Эти признаки видны в diff и не требуют спорить о стиле.
Полезно заранее выдать модели приемочный контракт вместе с командами, но без подсказки о реализации:
Задача: добавить отмену заявки с состоянием cancelled.
Условия: автор отменяет только pending; admin отменяет pending и active.
Совместимость: существующие поля ответа и HTTP-статусы не менять.
Проверка:
make generate-check
go test ./...
npm test
npm run build
Вернуть: план, diff, результаты команд и известные ограничения.
Фраза make generate-check полезна только тогда, когда такая цель действительно есть в стенде. Если ее нет, добавьте до сравнения кандидатов или укажите реальные команды отдельно. Пилот не должен награждать модель за угадывание локальных соглашений, которых нигде нельзя прочитать.
Отдельно оцените, объяснила ли модель свое решение до редактирования. План не обязан быть длинным. Достаточно назвать источник контракта, потребителей и риск совместимости. Если план говорит только «обновлю backend и frontend», модель еще не показала, что нашла границы изменения. Разрешите одну уточняющую реплику, но считайте каждую вашу подсказку о конкретном файле новой попыткой.
Миграция схемы должна пережить откат и старый код
Безопасность миграции измеряется тем, может ли старая и новая версия приложения некоторое время работать с одной базой. Модель должна предложить расширяющее изменение, заполнение данных и позднее ужесточение схемы, если задача затрагивает существующие строки. Один ALTER TABLE может пройти на пустой тестовой базе и остановить рабочую таблицу.
Документация PostgreSQL предупреждает, что ALTER TABLE по умолчанию берет ACCESS EXCLUSIVE, если конкретная форма не оговаривает иной режим. Это блокировка, конфликтующая со всеми режимами доступа. Я не требую от модели помнить таблицу блокировок наизусть, но требую заметить риск, проверить документацию доступной версии и не заявлять «миграция без простоя» без доказательства.
Для поля времени отмены разумная первая миграция добавляет nullable столбец. Новый код умеет читать NULL, а старая версия просто не знает о столбце. Если бизнес правило позже требует NOT NULL для отмененных записей, его можно ввести отдельной проверкой после заполнения исторических данных. Модель должна различать обратимость SQL файла и эксплуатационную обратимость выпуска. DROP COLUMN в down миграции синтаксически откатывает схему, но уничтожает уже записанные даты.
Дайте стенду не только чистую базу, но и снимок с данными до изменения. В нем нужны заявки обоих состояний, пользователи обеих ролей и хотя бы одна запись, которая нарушит наивное ограничение. Затем прогоните три траектории: создание базы с нуля, обновление старой базы и запуск старой версии приложения после применения расширяющей миграции. Последняя проверка обнаруживает миграцию, которая незаметно требует одновременного выпуска всех сервисов.
Оцените транзакцию и время блокировки отдельно. Команда может завернуть несколько DDL команд в транзакцию, однако длинное заполнение данных удержит блокировки и раздует откат. Для крупной таблицы модель должна хотя бы предложить пакетное заполнение, наблюдение за прогрессом и отдельный выпуск ограничения. В маленьком пилоте выполнять миллионы строк не нужно. Достаточно, чтобы решение называло порог и способ измерить длительность на копии рабочей статистики.
Спросите, что произойдет при повторном запуске миграции после частичного сбоя. Слепое добавление IF NOT EXISTS часто нравится генераторам, потому что делает команду зеленой, но оно способно скрыть столбец неверного типа. Лучше, когда инструмент миграций однозначно записывает примененную версию, а проверка схемы сверяет тип, nullability и ограничение. Идемпотентность не означает игнорирование несовпадений.
Модель проходит этот этап, если ее миграция применима в обе стороны, новая версия работает на обновленной базе, старая версия не падает после расширения, а пояснение честно называет необратимые данные. За обещание нулевого простоя без анализа блокировок ставьте ноль по безопасности миграции, даже если тесты завершились успешно.
Правила доступа проверяют на сервере, а не по кнопке
Правило доступа считается реализованным только после серверной проверки объекта и роли. Скрытая кнопка в React улучшает интерфейс, но запрос можно отправить напрямую. Пилот должен заставить модель провести одно и то же правило через обработчик, сервисный слой и интерфейс, не создавая три расходящихся версии условия.
OWASP ASVS предлагает использовать требования как измеримую основу проверки технических средств защиты, включая отдельную область контроля доступа. В этом пилоте полезна не попытка выполнить весь стандарт, а его принцип: пользователь получает только разрешенные функции и объекты. Применительно к отмене заявки сервер обязан проверить действие, роль и принадлежность конкретной заявки, а не доверять переданному owner_id.
Постройте матрицу из ролей и состояний до запуска. Автор отменяет свою pending, не отменяет чужую и не меняет active. Администратор отменяет оба состояния. Неавторизованный запрос получает отказ. Если система скрывает существование чужого объекта, ожидаемый код для чужой заявки может отличаться от обычного запрета, но это решение нужно зафиксировать заранее. Иначе ревьюер начнет оценивать собственные предпочтения.
Слабый патч часто сначала загружает объект по идентификатору, затем проверяет роль, а сообщение об ошибке раскрывает его состояние или владельца. Другой частый дефект возникает, когда UI получает роль из изменяемого локального состояния и показывает административное действие. Сам показ кнопки не катастрофа, если сервер все равно запрещает запрос, но он доказывает, что модель не нашла единый источник разрешений для интерфейса.
В Go удобно выразить правило функцией, принимающей проверенного субъекта и загруженную заявку, а не набором булевых флагов из HTTP параметров. Тесты этой функции дают компактную таблицу решений. Интеграционный тест обработчика затем доказывает, что идентичность пришла из доверенного middleware, объект загружен из базы, а запрет не изменяет строку. Не требуйте конкретной архитектуры, если ваш проект устроен иначе, но требуйте одного серверного места, где принимается решение.
Проверьте побочный эффект отказа. После запрещенного запроса состояние, cancelled_at и журнал действий должны остаться прежними. Генератор может сначала выполнить UPDATE, а потом обнаружить запрет при построении ответа, особенно если существующий слой смешивает авторизацию с сохранением. Зеленый тест на статус 403 такого дефекта не ловит, если он не перечитывает запись.
Модель должна также объяснить гонку между проверкой состояния и записью. Два запроса могут одновременно увидеть pending; один начинает обработку, второй отменяет. Решение зависит от правил продукта, но атомарный UPDATE ... WHERE status = 'pending' с проверкой числа строк обычно надежнее отдельного чтения и записи. Если модель игнорирует конкурентное изменение, пилот обнаружил риск, который редко виден в сгенерированном интерфейсе.
Тесты должны доказывать поведение, а не повторять код
Набор тестов проходит оценку, когда он падает на исходном commit, проходит после патча и проверяет наблюдаемое поведение. Модель, которая пишет тест после реализации и копирует в него ту же ошибочную формулу, создает убедительную декорацию. Поэтому часть приемочных тестов держите скрытой от агента, а созданные им тесты оценивайте отдельно.
Разделите проверки по границам отказа. Тест функции доступа быстро покрывает матрицу ролей и состояний. Интеграционный тест Go обработчика проверяет аутентификацию, транзакцию и фактическую строку PostgreSQL. Компонентный тест React проверяет наличие действия и обработку стабильного кода ошибки. Один сквозной сценарий связывает слои, но не должен заменять все быстрые проверки.
В Go запустите не только go test ./..., но и те анализаторы, которые уже приняты в репозитории. Детектор гонок через go test -race ./... ищет обращения к общей памяти во время реально выполненного кода. Официальная документация Go прямо оговаривает это ограничение: он находит только гонки, которые произошли при запуске. Поэтому зеленый результат не доказывает отсутствие гонок, а слабое покрытие конкурентного пути почти обнуляет смысл команды.
Для React не привязывайте приемку к внутреннему имени state или точному дереву компонентов. Тест должен действовать от имени пользователя: найти доступную кнопку, инициировать отмену, дождаться результата и проверить изменение интерфейса. Если модель вынуждена переписать все тесты после безобидного рефакторинга компонента, она закрепила реализацию вместо контракта.
Попросите модель показать отрицательный тест. Он важнее еще одного счастливого пути: чужой автор отправляет корректный запрос на существующую заявку, получает ожидаемый отказ, а база не меняется. Второй сильный случай воспроизводит конфликт состояния между чтением и записью. Если среда не умеет поднять PostgreSQL для интеграционного теста, зафиксируйте ручную настройку и включите ее во время ревью. Не подменяйте реальную семантику PostgreSQL моками, когда оцениваете миграцию и транзакцию.
Проверьте тесты мутацией. Временно удалите серверную проверку владельца или инвертируйте условие состояния и убедитесь, что набор краснеет. Это занимает несколько минут и показывает чувствительность лучше, чем процент покрытия. Верните код к патчу до следующего измерения. Модель не получает дополнительную попытку за дефект, который выявил такой контроль, пока сама не объяснит и не исправит его.
Считайте изменения тестовой инфраструктуры частью стоимости. Если агент обновил десятки снимков, отключил предупреждение или увеличил timeout, ревьюер должен выяснить причину. Иногда обновление оправдано, но массовое ослабление проверок нельзя засчитывать как успешный прогон. Особенно подозрительны skip, удаление утверждений и обработчик ошибки, который превращает отказ команды в лог.
Исправление дефекта показывает, умеет ли модель расследовать
Отдельный дефект нужен потому, что реализация по описанию и расследование неизвестной причины проверяют разные способности. Дайте модели воспроизводимый симптом без указания слоя. Например, после отмены заявки счетчик активных элементов уменьшается, но после обновления страницы возвращается прежнее число. Причина может жить в SQL фильтре, кеше Go сервиса, ключе React запроса или несовпадении нового enum.
Хорошая сессия сначала воспроизводит сбой, собирает минимальные наблюдения и сужает область поиска. Плохая сразу меняет компонент, потому что симптом виден в браузере. Смотрите не на объем рассуждений, а на проверяемые гипотезы: какой запрос возвращает неверное состояние, где формируется счетчик, меняется ли строка в базе, инвалидируется ли кеш после успешной транзакции.
Не принимайте исправление без регрессионного теста, который падает до патча. Если агент не может запустить старую версию после изменения файлов, ревьюер может временно применить только тест к исходному commit. В отчете должно быть видно красное состояние, затем зеленое. Тест, который всегда был зеленым, не доказывает связь с дефектом.
Полезная ловушка заключается в похожих именах. Допустим, API возвращает cancelled, доменная модель использует canceled, а база хранит cancelled. Человек быстро видит опечатку, модель часто «исправляет» один слой и расширяет расхождение. Сильное решение находит все представления, выбирает источник истины и либо проводит совместимое переименование, либо добавляет явное преобразование на границе.
Другая ловушка связана с порядком транзакции. Обработчик может очистить кеш до COMMIT; параллельный запрос успеет заполнить кеш старыми данными, после чего неверный счетчик останется до истечения срока. Простой повторный запрос в последовательном тесте не воспроизводит окно. Модель должна хотя бы объяснить порядок событий и разместить инвалидирование после успешной фиксации либо отказаться от кеширования этого значения.
Оценивайте найденную первопричину отдельно от качества патча. Иногда модель точно расследует дефект, но предлагает слишком широкий рефакторинг. Это полезный сигнал: поиск у нее сильный, контроль области изменения слабый. В покупке AI-среды важна не средняя «умность», а профиль ошибок и возможность ограничить агента правилами репозитория.
После исправления попросите перечислить измененные предположения. Ответ «починил кеш» слишком беден. Нужна конкретика: какое значение устаревало, какое событие теперь его сбрасывает, какой тест удерживает поведение и что осталось непроверенным. Такое объяснение сокращает ревью только тогда, когда совпадает с diff и результатами команд.
Попытки и время ревью дают честную цену результата
Главная единица пилота называется принятой попыткой: непрерывная сессия от исходного commit до патча, который проходит приемку без ручного редактирования инженером. Новая попытка начинается, когда человек сообщает пропущенный файл, диктует решение, просит исправить провал теста или сбрасывает контекст после ухода модели в сторону. Уточнение бизнес правила, которого не было в задании, не штрафуется.
Записывайте события в журнал сразу, а не восстанавливайте по памяти. Для каждой попытки нужны начало, конец, исходная подсказка, дополнительные указания, выполненные команды и итог. Отдельно помечайте сбои самой среды: падение контейнера или недоступный раннер не говорит о качестве модели, но влияет на эксплуатационную пригодность продукта.
Время ревью начинается, когда модель объявила патч готовым, и заканчивается решением принять его или отправить на новую попытку. В него входят чтение diff, запуск приемочных команд, проверка миграции, просмотр правил доступа и разбор объяснения. Не включайте ожидание занятого CI, обед и настройку стенда. Если инженер сам исправил строку, патч не принят: запишите ручное вмешательство и начните новую попытку либо завершите кейс провалом.
Одна карточка удерживает оценку от расползания:
- Попытки: число сессий до принятого diff, не больше заранее заданного лимита.
- Ревью: активные минуты инженера, меньше обычного изменения того же класса.
- Приемка: все обязательные проверки пройдены.
- Ручные правки: ноль строк для принятой попытки, причины вмешательства записаны.
- Повторяемость: независимые прогоны дают одинаковый класс исхода.
Лимиты задайте до теста. Если обычное изменение такого размера занимает у разработчика рабочий день и час ревью, кандидат должен улучшить именно вашу базовую линию, а не чужой рекламный пример. Не смешивайте время генерации и ревью в одну сумму: быстрая модель может создавать дорогой для чтения diff, а медленная выдавать аккуратный патч с первой попытки.
Добавьте вес тяжести пропуска. Неверный текст кнопки и обход авторизации не должны компенсировать друг друга средним баллом. Дефект доступа, потеря данных при миграции или ложный зеленый тест делает попытку неприемлемой независимо от скорости. Для остальных замечаний можно считать минуты и число циклов.
Сравнивайте распределение, а не лучший прогон. Для каждой конфигурации полезны медиана попыток, медиана ревью и худший безопасный исход на одинаковом наборе задач. При малом числе прогонов не изображайте статистическую точность: покажите сырые результаты. Решение о покупке все равно станет инженерным суждением, но оно будет опираться на наблюдения.
Покупать стоит управляемый процесс, а не название модели
Покупка оправдана, когда среда стабильно уменьшает число циклов и активное ревью без провалов доступа, данных и совместимости. Лидер по синтетическим задачам может проиграть в вашем репозитории из-за слабого поиска, неудобного запуска инструментов, потери контекста или неясного diff. Пилот проверяет систему целиком: модель, агентов, ограничения, выполнение команд и способ передать результат человеку.
Сначала установите обязательные ворота. Ни один кандидат не проходит при обходе серверной авторизации, разрушительной миграции без плана данных, скрытом отключении тестов или отправке исходников за пределы разрешенного контура. Затем сравнивайте попытки и ревью среди тех, кто прошел ворота. Так скорость не маскирует риск.
Локальная LLM имеет смысл, если ваши ограничения требуют исполнения в российском контуре, контроля над моделями или работы без передачи кода во внешние сервисы. Но слово «локальная» ничего не говорит о качестве изменения, составе телеметрии и происхождении зависимостей. Попросите поставщика показать маршрут данных, журналы сетевых обращений, механизм обновления моделей и способ запретить агенту команды. Эти утверждения проверяют технически и закрепляют в условиях поставки.
Посмотрите, можно ли перенести результат. Экспорт исходного кода, обычный Git diff и воспроизводимые команды уменьшают зависимость от среды. Закрытая история диалога без патча, логов команд и версий модели затруднит разбор дефекта через месяц. Снимки и откат полезны, но они не заменяют понятную историю изменений.
Для проверки TakProsto можно собрать тот же стенд на React, Go и PostgreSQL, использовать режим планирования до изменений, затем экспортировать код и проверить diff своими командами. Платформа работает на серверах в России, использует локализованные открытые модели и заявляет, что данные не уходят в другие страны, поэтому пилот должен подтвердить эти свойства наблюдением за реальным запуском, а не повторением описания продукта.
Не требуйте, чтобы модель выигрывала каждый кейс. Требуйте предсказуемой остановки: она признает неизвестное, не обходит запрет, показывает провал команды и оставляет репозиторий в понятном состоянии. Агент, который иногда просит уточнение, дешевле агента, который уверенно меняет правило доступа.
Финальное решение запишите до окончания доступа к пилоту. В нем укажите конфигурацию, исходные commit, сырые журналы, принятые попытки, минуты ревью, опасные пропуски и условия повторной проверки после обновления модели. Если команда не может восстановить, почему кандидат победил, она не провела закупочный тест. Она провела демонстрацию с более длинным сценарием.
FAQ
Сколько задач нужно дать локальной LLM в пилоте?
Одной сквозной задачи хватит для первого отсева, если она затрагивает API, базу, доступ, интерфейс и тесты. Для решения о покупке повторите ее в независимых сессиях и добавьте еще один дефект другого типа.
Можно ли проверять модель на закрытом рабочем репозитории?
Можно только после проверки маршрута данных, журналирования, сетевых обращений и правил хранения кода. Для первого пилота безопаснее взять уменьшенную копию архитектуры с синтетическими данными и без секретов.
Что считать новой попыткой при тестировании LLM?
Новая попытка начинается, когда инженер подсказывает конкретный файл, диктует способ исправления или возвращает модель после проваленной приемки. Уточнение отсутствующего бизнес правила не нужно считать ошибкой модели.
Как измерять время ревью сгенерированного кода?
Запускайте таймер после заявления модели о готовности и останавливайте после принятия или возврата патча. Считайте чтение diff, приемочные команды и проверку рисков, но исключайте ожидание CI и посторонние перерывы.
Нужна ли модели сеть во время пилота?
Нет, если все зависимости и документация стенда доступны локально. Запрет сети также показывает, умеет ли среда соблюдать ограничения, но он должен быть одинаковым для всех сравниваемых конфигураций.
Почему недостаточно запустить только unit-тесты?
Unit-тесты не доказывают, что миграция работает на PostgreSQL, middleware передает проверенную личность, а React понимает ответ API. Нужны проверки на границах слоев и хотя бы один сквозной пользовательский сценарий.
Как проверить безопасность миграции PostgreSQL?
Примените ее к чистой базе и к снимку старой схемы с данными, затем проверьте совместимость со старой версией приложения. Отдельно разберите блокировки, заполнение данных, повторный запуск и потерю данных при откате.
Должна ли LLM сама писать приемочные тесты?
Пусть пишет, это часть оценки, но не раскрывайте ей весь набор приемки. Скрытые тесты защищают пилот от ситуации, когда реализация и тест повторяют одну ошибку.
Как сравнить две AI-среды с разными моделями?
Используйте одинаковый commit, правила, набор инструментов и лимиты, а каждую конфигурацию прогоните несколько раз с чистым контекстом. Сравнивайте принятые попытки, активное ревью и опасные пропуски, а не лучший ответ.
Когда результат пилота достаточен для покупки?
Когда среда повторяемо сокращает циклы и ревью относительно вашей базовой линии и проходит обязательные ворота по доступу, данным и совместимости. Решение должно опираться на сохраненные diff, команды и журналы, а не на впечатление от демонстрации.