AI/ML
8 мин.
03 сентября 2026

От чекстайла к мультиагентам: как мы выстроили автоматическое код-ревью с помощью ИИ

Вадим Федосеев,технический лидер разработки Альфа-Банка, рассказал, как за год они прошли путь от простых линтеров (checkstyle и PMD) до мультиагентной системы автоматического код-ревью на опенсорс-моделях — с агентом-ревьюером, агентом-критиком, бизнес-ревью по требованиям и балансом между качеством и стоимостью вызовов.

Сегодня расскажу, как мы прошли долгий путь от самых простых проверок до мультиагентных систем.

Немного о себе: в Альфа-Банке я работаю уже пять лет. До этого достаточно долго работал в телекоме, разрабатывал системы биллинга. В Альфе я разрабатываю систему Collection, которая помогает нам собирать просроченную задолженность, поэтому я умею работать не только с техническими долгами, но и с финансовыми. А сейчас я внедряю ИИ в разработку и помогаю команде с этим справиться.

Контекст проекта

Свою презентацию я выстроил в стиле таймлайна. Мы пройдём от каменного века с античными линтерами до космической эпохи, когда у нас появились мультиагенты.

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

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

С чего мы начали: каменный век

С чего мы начали? С того, что скрестили Checkstyle с PMD и попробовали объединить это в один инструмент.

Что из этого вышло?

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

Что не вышло? Мы не смогли сделать более сложные проверки. Линтеры, конечно же, не понимают бизнес-логику, не понимают модули и зависимости, не могут проверить контракты. А мы хотели, чтобы агент проверял код уже примерно так, как это делает человек.

Знакомьтесь, Диана

Поэтому начали с того, что создали агента. Знакомьтесь, это Диана.

Мы сразу решили персонифицировать агента, сделать ему аватар, чтобы разработчики относились к нему не как к какому-то безликому, бездушному инструменту, а как к человеку. То есть мы его очеловечили. Добавили ему пару прикольных цитат.

Диана уже встраивалась в пайплайн юнит-тестов и интеграционных тестов. Мы прогоняли эту проверку.

Самый главный момент — проверяли только изменённые строки, чтобы разработчик не мог сказать: «Это не моё, я отправлять не буду».

По результатам проверок мы формировали комментарии. Это были не стандартные комментарии линтеров: к каждому замечанию мы добавили более человеческое объяснение.

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

В принципе, было неплохо, но нам этого было мало.

Средневековье: элементарные правила

Мы хотели чего-то более умного, поэтому перешли в Средневековье и добавили к нашему линтеру немножко, совсем немножко.

Это были самые элементарные правила, буквально на две строки. Например: не хранить секреты в коде, не писать магические числа, заменять их константами и что-то подобное.

По каждому правилу мы проверяли код. То есть всё работало в цикле: каждый раз проходили по правилам и проверяли код.

В принципе, получилось уже хорошо, но всё ещё не так, как нас устраивало. Это какое-то время работало, и мы решили пойти дальше.

Индустриальная революция: весь дифф в LLM

В индустриальную революцию мы просто взяли и засунули весь дифф в LLM.

Ну, это, конечно, просто звучит. На самом деле было немножко сложнее.

Промпт у нас был сложный, мы его долго отлаживали. Прошли очень большое количество итераций: на каждом прогоне смотрели, что получилось, что не получилось, дорабатывали промпт.

Изначально промпт был заточен под Java, потом мы адаптировали его под другие языки.

Что хорошо получилось? Так как у нас есть дифф, агент видел всё сразу. Он проглядывал изменения, получал какие-то результаты и отправлял комментарии.

На тот момент это была модель GPT. Она была уже лучше, чем то, что было до этого, но всё ещё не так хорошо, как нам хотелось.

Эйфория и её пределы

Мы также впали в эйфорию.

Проверяли пул-реквесты примерно на 300–500 строк — это могло быть около десяти классов. Большие пул-реквесты мы не могли проверять: если дифф превышал примерно 2000 строк, сразу начинались галлюцинации, потому что модель не могла вместить в контекст всё, что мы хотели ей передать.

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

И второе, тоже достаточно больное место, — огромное количество ложноположительных срабатываний.

У LLM есть такая специфика: они стараются угодить человеку и подсвечивают любую потенциальную проблему.

Например: «Если этот параметр будет null, то у нас всё упадёт».

А разработчик смотрит и понимает, что этот параметр никогда null не будет, потому что чуть выше по стеку ему присваивается явное значение.

Очень много таких комментариев было. Доверие разработчиков у нас упало. Они приходили жаловаться, говорить: «Эта железка опять что-то не там прокомментировала».

Борьба с ложными срабатываниями: два промпта

Поэтому дальше мы посвятили всё своё время именно борьбе с ложными срабатываниями и придумали валидацию.

Как это работает?

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

То есть мы постоянно замеряем полезность комментариев. В основном это была обратная связь от разработчиков: приходили, спрашивали, как тебе ревью, что полезно, что она упустила, что, наоборот, прокомментировала ложно. И по результатам уже смотрели, что нужно менять.

Итак, отправляем файлы для валидации по одному. На выходе получаем набор комментариев и номеров строк — на какой строке у нас что-то плохо.

Дальше идём с этими наборами «строка — комментарий» ко второму промпту и спрашиваем: насколько этот комментарий отвечает нашим ожиданиям?

Это уже немного другой промпт. Если первый направлен на валидацию, на проверку пул-реквеста, то второй направлен на проверку самой проверки.

Собственно, это стало, наверное, самой лучшей доработкой, которую мы сделали. Количество мусора упало в разы, а доверие команды повысилось.

Если раньше было какое-то лёгкое презрение, то после этого появилось некоторое уважение. Разработчики приходили и говорили: «Да, на самом деле, это очень даже хорошо».

И третье — то, что у нас было два промпта, мы могли это настраивать и конфигурировать. Если в одном промпте мы делали чуть больше проверок, часть проверок могли вынести во второй.

Чего не получилось

Что у нас не получилось?

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

А промпт мы не могли усложнять, потому что каждое усложнение приводило к галлюцинациям. Он и так уже был большой: там были все базовые проверки, какие-то валидации на code style, на соответствие нашим стандартам проекта.

Сразу скажу, что мы пробовали проверять фреймворки, например Spring или Hibernate, но это получилось плохо. В этом случае нам нужно было передать модели знания об этих фреймворках. Иначе мы не можем гарантировать, что модель это осознаёт.

То есть нужно было либо как-то дообучать модель, либо сразу засовывать эти знания в промпт, что опять же приводило к его разрастанию и новым галлюцинациям.

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

Нас это устраивало. Что-то лучше мы не могли сделать из-за ограничений модели.

Эра вайб-кодинга

Но грянула эра вайб-кодинга, и у нас стало всё очень весело, потому что разработчики начали вайбить.

Количество кода у нас выросло примерно в полтора раза, а может быть, даже больше.

Также появились масштабные рефакторинги, когда мы просто решали переписать модуль, например, на другой фреймворк или убрать какой-то легаси-код и сделать его чуть быстрее и более комплексным.

Соответственно, объём увеличился, и наше текущее решение перестало справляться.

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

И ещё момент: мы понимаем, что агентская разработка — это будущее. Агенты могут работать не только днём, но и ночью, и в выходные.

Поэтому нам нужен был инструмент, который работает стабильно в любое время и выдаёт стабильный результат.

То, что у нас было на тот момент, этого нам не обеспечивало — всё равно требовался человек.

Космическая эпоха: агент Валя

И мы полностью переписали своего агента.

Мы решили: если у нас Agent Loop может писать код, почему бы Agent Loop не мог валидировать код?

Собственно, мы написали агента для ревью и назвали его Валя, потому что всё у нас на вайбе.

Как это работает?

Сначала мы отказались от пайплайна, то есть перестали совмещать ревью с тестами, и это стало отдельным процессом.

Планировщик забирал из очереди пул-реквесты, потом передавал их агенту.

Агент-ревьюер выбирал нужные скиллы для решения задачи под ревью.

Потом с помощью Agent Loop с ограниченным количеством итераций — до пяти — мы собирали контекст проекта. Смотрели, что у нас за границами, что где-то ещё есть в коде.

Формировали комментарии, дальше передавали их агенту, который перепроверял агента-ревьюера.

Это очень важно.

И по результатам ревью мы отправляли комментарии.

Этап подготовки

Теперь немного расскажу, как это работает.

Можно было бы просто скормить всё своему Cursor и сказать: «Проверяй».

К сожалению, так у нас не работало, пришлось делать немножко сложнее.

Первый этап — подготовка.

Сначала мы прогоняем весь дифф через regex-скан. То есть с помощью регулярных выражений определяем какие-то красные флаги и узкие места.

Например, транзакции, optionals, REST-вызовы, комбинации REST-вызовов с вызовами базы данных.

Потом отправляем эти места вместе с контекстом в LLM и просим подсветить участки, где наиболее возможны какие-то проблемы.

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

Потом всё, что получили, встраиваем в промпт и отправляем дальше на ревью.

Возможности агента: инструменты и скиллы

Немного о возможностях нашего агента — как мы обеспечили всё то, что он умеет.

Во-первых, это инструменты.

Инструменты должны позволять ему работать с кодом, именно с деревом кода. Например, найти все вызовы метода, прочитать файлы в репозитории.

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

Также есть семантический поиск. Когда grep не работает и что-то не удаётся найти, мы пытаемся найти это с помощью семантического поиска и RAG.

И второе — это скиллы. Наверное, это самый мощный инструмент, который у нас был.

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

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

Мы пытаемся научить агента писать код, и параллельно учится агент по проверке кода.

Поэтому, когда агент-ревьюер проверяет код, он подхватывает те скиллы, которые использовались при написании, и с помощью них проверяет результат.

Ограничения

Теперь поговорим об ограничениях.

Потому что работа с моделью — это круто, хорошо, но без ограничений совершенно никуда.

Тайм-ауты.

Часто бывали случаи, когда агент зацикливался, впадал в какую-то рекурсию и блокировал весь процесс.

Он мог по часу висеть, например. Поэтому мы ограничили время до 30 минут на один прогон.

Размер пул-реквеста.

После экспериментов, эмпирическим путём, мы ограничили размер пул-реквеста до 8000 строк.

Потому что чем больше дифф, тем больше агент теряет фокус и начинает писать какие-то придирки.

Например, замечание: ему не понравилось, что у нас переменная не final, хотя она на самом деле может быть и не final.

Поэтому мы ограничили размер: лучше хорошо проверить 8000 строк, чем плохо — 15 тысяч.

Ретраи.

Сеть нестабильна, как известно. Что-то может упасть, LLM может долго что-то думать и отвалиться по таймауту с той стороны.

Поэтому мы добавили ретрай с нарастающей паузой: три ретрая — через 2, 4 и 8 секунд.

После этого стабильность повысилась примерно на 40%. Количество успешных прогонов без проблем практически достигло 100%.

Один пул-реквест за раз.

Были идеи запустить многопоточность, но начиналась конкуренция за ресурсы.

У нас ресурсы достаточно ограничены в плане LLM, потому что она находится под высокой нагрузкой в Альфе. Соответственно, нам не могут выделить много ресурсов, и мы делали это по одному пул-реквесту за раз.

Критик

Теперь расскажу немного про Критика.

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

Опять же, у нас комментарии были полезные, но оставались либо ложноположительные, либо какие-то придирки к стилю.

Это нужно было минимизировать.

Поэтому на вход мы передаём агенту-критику то, что нашли: номер строки и какой-то контекст вокруг кода. Либо это целиком метод, либо какие-то важные части.

Агент-критик может в процессе своей проверки запрашивать дополнительный контекст. Это тоже ограничено — до трёх операций.

И по итогам проверки он выносит вердикт: подтверждён или отклонён.

Важная деталь: в его правилах прописано, что любое замечание неверно, пока не доказана его правота.

То есть он пытается сам себе доказать, что замечание корректное. Если этого не происходит, замечание отклоняется.

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

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

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

По итогам проверок мы можем совершенствовать своего агента и делать аналитику: какое количество комментариев у нас на самом деле полезно, а какое нет.

Бизнес-ревью

Теперь логичное развитие того, что мы делаем под техническое ревью, — это бизнес-ревью.

В принципе, хорошо, когда мы проверяем код с технической точки зрения, проверяем фреймворки и соответствие контексту проекта.

Но неплохо бы проверять ещё и соответствие бизнес-требованиям.

Мы дали агенту доступ к базам знаний с помощью MCP.

У нас в Confluence хранится короткое описание и полное описание процессов.

Как это работает?

Сначала агент идёт в Jira и вычитывает описание задачи. Потом собирает из Jira и Confluence само описание процесса — то, что должно быть изменено, плюс контекст.

Далее из этого описания извлекаются факты.

Например: «Сумма не должна быть отрицательной» — это факт.

«Долг должен перейти в статус 15» — тоже факт.

То есть мы разбиваем всё ТЗ на набор утверждений.

И далее каждое утверждение ищем в коде.

Агент отвечает на два вопроса.

Первое: есть ли в коде то, что у нас есть в ТЗ?

И второе: если есть, то насколько оно корректно реализовано?

Например, если у нас написано «сумма не должна быть отрицательной», мы ищем проверку на положительную сумму.

Если написано «долг должен прийти в статус 18», агент идёт и смотрит: а там статус Close, а Close — это статус 15, а не 18.

Там разработчик, может быть, ошибся или посмотрел немножко не туда.

И мы получаем замечание: в ТЗ есть вот такое утверждение, а в коде реализовано совершенно другое.

Это, наверное, одна из самых ценных доработок, которые мы получили.

Потому что мы сняли когнитивную нагрузку с сеньоров-разработчиков, которые смотрят ревью.

Если человек пришёл смотреть ревью и не находится в контексте задачи, ему нужно обязательно пойти в эту задачу, прочитать её, разобраться, вникнуть, понять, о чём она, и только потом проверять соответствие бизнес-требованиям.

А здесь агент сохраняет эти факты в задаче. Поэтому разработчик может прочитать не само задание, а именно плоский список требований и уже по нему проверять код.

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

Вероятностная природа LLM

Теперь важный факт.

Мы знали, что LLM — это вещь вероятностная.

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

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

К нам приходили разработчики и жаловались: «Почему я вроде всё сделал, а теперь мне придётся всё переделывать?»

К сожалению, это так.

Мы не указываем агенту путь, которым он должен идти, когда делает ревью. Он сам решает, куда идти.

Эти пути могут быть разными, часто даже кардинально различными. И в зависимости от этого мы получаем разный набор комментариев.

Нам пришлось с этим смириться.

Мы отказались от цели достигнуть 100% верных комментариев, потому что это невозможно, по крайней мере на данном этапе развития искусственного интеллекта.

Поэтому мы стараемся добиться цифры, близкой к ста.

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

И смотрим не на то, сколько у нас в моменте полезных комментариев, а на тренды.

То есть мы считаем precision, точность, с помощью разработчиков: приходим за обратной связью, спрашиваем, потом смотрим.

Например, берём комментарии разработчиков, которые уже оставлены в пул-реквесте, и пытаемся понять, почему модель это пропустила, а разработчик нашёл.

Если у нас точность растёт, значит, всё хорошо.

Если точность падает, мы начинаем принимать какие-то меры.

Про деньги

И теперь ещё хочу поговорить о важном — о деньгах.

Потому что вызов модели — это деньги. Неважно, где это происходит: подписка или внутренний кластер.

Эти деньги надо считать, потому что если ваш агент дороже разработчика, он никому не нужен.

От чего может зависеть стоимость?

Во-первых, от модели.

Чем умнее модель, чем больше она думает и размышляет, тем больше мы за это платим.

Поэтому мы всегда ищем баланс между ценой и качеством.

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

Второе — объём кода.

Так как у нас есть Loop и он stateful, он таскает за собой при каждом вызове контекст предыдущих вызовов.

Чем больше контекст, тем больше мы тратим ресурсов и, соответственно, тем выше стоимость.

И третье — количество вызовов: чем больше вызовов модели, тем выше цена.

Поэтому мы стараемся как можно сильнее снизить количество этих вызовов.

Как мы экономим?

Во-первых, разделяем модели.

Как я уже говорил, сначала прогоняем всё через практически бесплатный regex-сканер — это алгоритмическая проверка.

Дальше используем очень простую модель, которая просто делает подсветку, — недумающую.

После этого у нас есть дорогая, хорошая думающая модель, которая на самом деле делает проверки качественно.

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

Ей не нужно размышлять. Ей нужно просто ответить на вопрос, насколько корректно замечание.

В итоге мы нашли баланс между ценой и качеством. Сейчас с точки зрения экономики у нас всё достаточно хорошо.

Что мы поняли за этот год

И напоследок — что мы поняли за этот год, который прошли от линтеров до мультиагентов?

Во-первых, самое важное и главное — это доверие команды.

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

Поэтому для нас важнее: пусть он найдёт 10 хороших комментариев, чем 20, из которых хороших только 10.

Второе — это проверки, проверки, проверки на каждом этапе.

На каждом этапе мы проверяем, например, что у нас output корректный, что файл существует, что строки соответствуют диффу.

Это всё алгоритмические проверки модели.

Ну и мы фильтруем комментарии по серьёзности, оставляем самые серьёзные и важные.

И, конечно же, стабильность — это тайм-ауты, лимиты, ретраи, всё то, что делает агента стабильным и помогает нам не падать.

И, конечно же, цена — самое главное. Всегда смотрите на цену и на то, сколько это стоит.



Другие статьи

Обсудить
проект