- BrainTools - https://www.braintools.ru -

Идеальный склероз в сером ящике — мой опыт в ИИ-программировании

ИИ все прочнее входит в работу программиста. Кто-то все еще отрицает его роль, кто-то с энтузиазмом пробует все новые возможности, но квалифицированное большинство все же трезво замечает, что ИИ пока не годится для сложных проектов, хотя простые задачи уже выполняет неплохо.

Расскажу, как я сделал свой первый шаг к большому проекту на ИИ. Он в значительной степени изобретён с нуля, а не скопирован.

Нейросети пока не могут работать с большими проектами. Даже лучшие их образцы начинают тупить, если кода больше 40-100 кб, и галлюцинировать после 10-60 итераций одного и того же проекта (1000 циклов – это пока что грубый маркетинг). Шестьдесят итераций – это много? Если речь об автономной ИИ-разработке, то очень мало. Если о man-in-the-loop, то более-менее уже потянет.

Я выбрал такие условия:

  • реально нужную задачу (причем тема для меня в новинку)

  • незнакомый язык (т.е. чистый вайб-кодинг)

  • отсутствие RAG (забегая вперёд: чтобы никоим образом не зависеть от истории)

  • невозможность или высокую трудность сделать приложение единым блоком

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

С нейронкой это приводит к тем же проблемам, но может, получится обходной маневр?..

Первый прототип был сгенерирован как один файл (я оставил его в проекте). Он работает, он очень похож на итоговый, но в нем нет и 10% итогового функционала. Уже на второй итерации нейронка стала галлюцинировать – по мелочам (упорно путая формат лога, неспособная исправить ошибки [1]) и по крупному (перестав понимать, что от нее требуется, как-то по-своему формулируя задачу)

Решение увиделось в упоре на гранулярность. То есть, модули должны общаться между собой максимально просто и понятно (по соглашениям, пусть даже и неявным), тогда как внутри самого модуля допускается постоянный рефакторинг – иными словами, модуль отражает не код, а возложенную на него задачу.

Этакий черный ящик, но при этом самодокументирующийся.

Первым я выделил модуль работы с паролями. Чтобы не хранить их в файле, я взял библиотеку для работы с защищенными системными хранилищами – такое сейчас есть в каждой популярной ОС. Я также вынес в модуль кусок графического интерфейса, чтобы получить самотестирование в файле. Мне очень понравилось: не нужно устанавливать среду разработки, не нужно делать конвейер сборки и отдельные тесты. На настройку всего этого обычно уходит не один день, а тут у меня ушло всего 4 дня на целое приложение. Это, считаю, большой прорыв, поскольку сюда входит проектирование, прототипирование, тестирование в несколько этапов, с несколькими версиями, с полным пониманием всего, что происходит.

Следующим был выделен модуль выполнения скриптов. Ему добавилась задача уметь выполнять как на сервере, так и локально, а это две разных библиотеки, увы. К счастью, здесь сработал автоматизированный подход: нейросеть просто повторяла одну и ту же задачу раз десять, накапливая ошибки в логе, и в конце-концов нашелся хороший рабочий подход. Проблема была то в настройке логов, то в кодировках, то в чем-то еще. И да, работу с кодировками в ps1/bat/cmd пришлось вынести в отдельный экспериментальный файл с тестами. Мы вместе проработали несколько подходов, и самый лучший (но не идеальный) отправился в новое ТЗ, а не в код – так естественным образом образовался новый паттерн работы.

Далее я выделил модуль автоопределения типа скрипта, чтобы без лишних проблем выполнялся не только sh/bash, но и powershell, ansible/yml без хоста, python и js. Это может быть удобно и в перспективе делает программирующего агента, который я надеюсь тоже здесь опубликовать.

Теперь самое главное – в чем я увидел отличия работы с нейронкой от работы с командой живых кожаных разработчиков.

Во-первых, нейросети в общем виде не заточены на память [2] контекста. Да, можно сохранить диалог и при необходимости поднять его – “вот тут мы поменяли интерфейс, поправь тип”. Но надо понимать, что в LLM всегда загружается вся история, плюс что-то там еще. А это – избыток токенов и, следовательно, всегда приводит к потере качества. Поэтому каждый диалог лучше начинать с текущей версии модуля и инструкций к нему, как будто работаешь с новым человеком. История – длинные чаты или RAG – оказалась ненужной. Есть только самодокиментированный (иногда даже без комментариев!) код и есть текущее задание по нему. Лаконично. Встроенных тестов должно быть досатточно для понимания нейронкой вашего интерфейса, или можно их описать отдельным файлом-примером, но даже такой подход со временем теряет актуальность, поэтому эволюционно у меня остались только модули с кодом, которые вполне самодостаточны.

Во-вторых, как выше писал, внутри себя модули постепенно превращаются в черные ящики. Они рефакторятся без вашего ведома, или с небольшим комментарием на эту тему. Количество багов при этом уменьшается, так как нейронка постепенно сводит всё к тому, что лучше всего понимает (о да, модуль обязан быть семантически устойчивым островом!). Git перестает быть пошаговым логированием мыслей разработчика и превращается в логирование стабильных интерфейсных срезов, ну что-ж, чем-то всегда приходится жертвовать.

Третье – стратегия предотвращения галлюцинаций. Нужно подбирать пары нейросетей с разной базой, разные под разные языки. Для питона у меня хорошо зашла схема: 90% кода делать на qwen-3.5-max, а оставшееся, когда Цюен начинает глючить, выполнять на gemma-4-it (это очень дешевые LLM, дорогие работают лучше). Есть много агрегаторов, в т.ч. российских, которые позволяют прозрачно переключаться между моделями. При подходе с короткими диалогами цена может быть очень небольшой, порядка 20-150р/день. Использование новой нейронки с рефактором обычно прерывает галлюцинации, потому что их природа – в самоусилении ошибки, а здесь мы как бы убираем сам источник ошибки, и осцилляции начинаются заново.

И, повторюсь, абсолютно критичным становится умение выделять блоки, чтобы общение между ними было сведено до минима и чтобы они могли тестироваться по отдельности.

Демонстрационный проект все же мелковат, а как перейти к действительно большому проекту? Пока что просто использую тот же самый подход – выцепляю по 2-30 файлов из своего проекта (но в этот раз с отдельными тестами), скармливаю их нейросетям в режиме JSON и сразу запускаю тесты. Возникает интерфейсный дрейф – выходные форматы могут случайно измениться, а входные – обрасти ненужными проверками, не влияющими на приложение, но ломающими тесты. Но с этим понятно как работать – использовать все более строгие описания интерфейсов.

Что для меня не взлетело: Cursor, Qwen studio, Copilot. Да, они работают, но не так быстро и не так точно, как хотелось бы. Возможно, их дизайн ограничен обратной совместимостью и ожиданиями пользователей. Но возможно, я слишком рано забросил их, и следует посмотреть, что там в обновлениях…

Прежде чем выкладывать программу, я настроил с ее помощью то, что хотел, и убедился, что работать с ней легко и для админской песочницы – предпочтительнее других просмотренных инструментов. Это потребовало огромного количества изменений, и я даже понял, отчего заказчики бывают такими невыносимыми занудами:)

Подытожу принципы, сразу с их возможной эволюцией [3].

  1. Максимальная гранулярность. Это то, к чему всё идет – не личное мнение, а закон сложных систем. Еще Илья свет Пригожин вывел, что сложные системы стремятся от множества маленьких элементов к небольшому числу средних, с четкими протоколами взаимодействий. Его универсальный подход полностью оправдывает себя и в программировании, и тогда грануляция получает возможность эволюционировать.

  2. Самотестируемость модуля. Модуль должен содержать встроенные тесты или тестовый запуск прямо в своем файле. На период прототипа – прекрасно, но в дальнейшем, видимо, будет напрягать.

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

  4. Ротация моделей. Применять при возникновении повторяющегося тупежа.

  5. Модуль как серый ящик. Это необходимо для работы п.1, а без него никак. Магический артефакт? Ну так к тому всё и идёт – поэтому важно совершенствовать управление артефактами.

  6. Масштабирование выделением логических фрагментов. Очень перспективное направление, позволяет не только работать с легкими моделями и улучшать качество тяжелых, но и не дает всему коду скомпрометироваться: один блок уходит только одному провайдеру и нейронке, другой – только другому, третий вообще обрабатывается локальной LLM, и служба безопасности довольна.

Всем успехов, результат выложил здесь [4]

Всем успехов, результат выложил здесь

Автор: fathergorry

Источник [5]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/31361

URLs in this post:

[1] ошибки: http://www.braintools.ru/article/4192

[2] память: http://www.braintools.ru/article/4140

[3] эволюцией: http://www.braintools.ru/article/7702

[4] здесь: https://github.com/fathergorry/pyaish

[5] Источник: https://habr.com/ru/articles/1044384/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1044384

www.BrainTools.ru

Rambler's Top100