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

AI-ревьюер: как мы научили LLM проверять код

Эрик Гевондян, Data Scientist, рассказал, как в ecom.tech научили LLM проверять код и выстроили трёхэтапный AI-ревьюер — от подготовки контекста и «прогрева» критериев до самого ревью с RAG-поиском, LSP и защитой от зацикливания. Отдельно он поделился, как объективная метрика качества помогла победить скепсис команды: сегодня сервисом пользуются 700+ проектов с 500–600 ревью в день.

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

Расскажу немного про себя. Я Эрик, 2000-го года, высшего образования нет, поработал много где, позанимался вообще всем: и классическим машинным обучением, и компьютерным зрением, а сейчас занимаюсь NLP. Поработал и в финтех-стартапе, и в аутсорсинге. Вообще опыт у меня достаточно широкий.

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

Почему AI-ревью внедряется постепенно

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

Без AI-ревью — то, что происходит сейчас в большинстве компаний — происходит следующее: проходят линтеры, тесты, дальше твои коллеги, такие же программисты, как ты, проводят ревью кода, и после этого с их одобрения мы эту штуку выливаем в кодовую базу. Какие здесь недостатки? Нужно ревью коллег, а коллеги-программисты — это дорогое время. Плюс они должны своё время найти. То есть ревью происходит не моментально: нужно дождаться, пока у твоих коллег появится время. Это проблема, которую мы, собственно, и пытаемся решить.

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

И третий этап — полное AI-ревью, некий идеал, к которому мы стремимся. То есть процесс ревью кода, в котором человек не участвует от слова совсем. Он происходит, наверное, за 10–15 минут, мы не тратим дорогое время программистов, и у нас всё хорошо.

Устройство AI-ревью: три этапа

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

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

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

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

Этап 1: подготовка контекста

Первый этап состоит из трёх параллельных процессов.

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

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

Третий процесс — это структурная карта репозитория. Для ребят, которые занимаются агентским кодингом, будет понятно следующее объяснение. Это, по сути, аналог Agents.md и Claude.md. Мы даём LLM погулять по нашей кодовой базе и понять, что она из себя представляет: какой у нас стек, какую бизнес-задачу мы решаем, что где лежит. И конструируем это в один большой промпт. В отличие от Agents.md, который хранится в качестве отдельного файла, мы отдельный файл не храним, а сразу закидываем это в контекст нейронки на следующих этапах. Опять же, результат кэшируется и переиспользуется.

Этап 2: прогрев

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

Первый процесс. Мы генерируем с помощью LLM на базе описания конкретного критерия синонимичные запросы для семантического поиска. То есть, чтобы пройтись по векторной базе, мы переводим наш запрос в векторное пространство и понимаем, какие векторы наиболее близки к нашим сгенерированным запросам. Все релевантные куски кода, которые подходят под описание критерия, мы вытягиваем и в будущем подадим как одну из частей контекста.

Второй процесс — это матрица релевантности. Что она из себя представляет? Мы строим матрицу, где по одной оси у нас все изменённые файлы, а по другой — каждый критерий, который написан в отдельном yaml-файле. И пытаемся понять, какие конкретно файлы к какому конкретно критерию относятся. По сути, это табличка, которая состоит из галочек и крестиков. Крестик — этот критерий к этому файлу не относится, галочка — относится, в него полезно заглянуть. Делаем мы это за счёт того, что на вход LLM в отдельной сессии показываем компактные дайджест-файлы: какие строки были добавлены, какие функции в классе были изменены, а какие нет. Опять же, эту штуку мы кэшируем и переиспользуем.

Третий процесс — это структурная карта с помощью LSP. Что это такое? Это когда мы для каждого изменённого файла обращаемся к LSP — штуке, которая понимает семантику конкретного языка и умеет отвечать на вопросы. Нейронка ходит и спрашивает: «Какие в этом файле заданы сигнатуры? Какие классы, какие функции, какие методы?» LSP отвечает: «Вот такие классы, такие методы». Или «Кто импортирует этот метод, какие файлы конкретно его импортируют». Мы собираем всю эту информацию — какие сигнатуры, где они импортируются, где вызываются — и это тоже становится одной из частей контекста.

Если LSP недоступен — такое происходит редко, но бывает — мы делаем fallback на MCP-treesitter. Это MCP, который строит чанки, разбивает код, ищет все эти сигнатуры и прочее не по семантике, а по AST. То есть он чуть менее умный, чем LSP, но, чтобы у нас проект-план не ломался, в какой-то степени эту задачу решает.

Этап 3: ревью

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

Здесь мы отправляем её в свободное плавание, даём ей инструменты. Первый инструмент — это read_file, read_many_files, read_directory. Это элементы, которые позволяют читать файлы — один или сразу много — и изучать структуру репозитория. Второй — семантический поиск, про который я рассказывал, то есть векторный поиск. Здесь мы получаем дифф от мерж-реквеста. И MCP-treesitter, к нему у нас тоже отдельно развёрнут доступ. У него есть свои тулы: мы ищем конкретные сигнатуры, ищем find_usage — не обязательно сигнатуры, а в целом текст, в том числе комментарии, в докстрингах и прочем. Есть анализ кода, checkers, который проверяет на синтаксические ошибки.

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

Защита от зацикливания

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

В случае, когда LLM вызывает тул, а тот возвращает ошибку, после 5 ошибок мы прерываем этот цикл и говорим LLM, что ты больше не можешь вызывать его, по крайней мере в качестве следующей операции. И у нас 100 итераций на файл: на 85% лимита мы говорим LLM, что пора давать вердикт — ты затянул свою работу, давай всё-таки решай, конкретный критерий нарушен или не нарушен.

Если у нас лимит исчерпан — такое бывает, — то есть нейронка не поняла критерий в итоге и не захотела конкретно к этому моменту сказать, нарушен критерий в коде или нет, у нас есть двухфазный экстракшн плюс вердикт. Это когда мы вызываем отдельную сессию LLM и говорим ей: выдерни из всех комментариев, которые ты проводила за прогон, пожалуйста, все факты. То есть, когда LLM говорила, например, «вот здесь происходит вот это» — выдерни все эти факты, и потом по этим фактам выдай вердикт. Если эта штука падает, у нас есть fallback: мы делаем то же самое регуляркой, ищем текст из разряда «критерий нарушен / не нарушен» и проставляем вердикт.

Выдача результата

Если вердикт положительный — критерий не нарушен, комментарий мы не оставляем. Если критерий нарушен, мы в GitLab оставляем комментарии по конкретному куску кода и объясняем, что критерий нарушен. Такое бывает, если критерий в целом нерелевантен по отношению к этому мерж-реквесту и мы каким-то чудом нерелевантный критерий пропустили на первом и втором этапе. Всё это мы складываем в S3 и в комментариях к GitLab отдаём, в том числе, ссылки на детализированные логи.

Блок-схема и инфраструктура

Вот блок-схема. У нас есть мерж-реквест, он заходит в первый этап. Критерии у нас хранятся, я покажу их структуру чуть позже. LSP и MCP-сервер развёрнуты отдельно. Второй, третий этап. Это наша инфраструктура. Поступает Merge Request — он поступает не сразу в сервис, который исполняет код-ревью, а сначала диспетчер задач через Kafka отправляет запрос в сервис, который эту задачу выполняет. Кафка, стораджи и прочее.

LLM-инфраструктура. Мы пользуемся LLM-боксом, у нас три GPU. Модель, которую мы используем для ревью, — это GLM 5.1. Модель, которую мы используем для RAG-индексирования, — это Qwen. Есть балансировщик, штука, которая регулирует нагрузку между нашими GPU. Слой данных — это Postgres, pgvector, Redis, Stream, и внешние сервисы, о которых я рассказал: MCP и LSP у нас в развёрнутом виде.

Структура критерия

Вот, собственно, структура критерия. Это yaml-файл. Здесь два критерия. Первый критерий — это новые функции тестируют покрытие. И здесь банальный scope, про который я не рассказывал. Scope — это, по сути, область, на которой мы разрешаем нейронке оставлять комментарии. То есть diff — это когда нейронка оставляет комментарии только по изменённому коду. А scope repository — это когда мы говорим нейронке, что она может оставлять комментарии по любому коду в репозитории.

Как мы составляем критерии

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

Проблема доверия и объективная метрика

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

По этой причине, чтобы с этой проблемой справиться, нам нужна объективная метрика качества. Это цифра, которая является истиной в последней инстанции, к которой мы всегда обращаемся, если нам говорят, что наш сервис работает плохо. То есть это аргумент, который как бетонная плита падает и с которым тяжело спорить.

И мы придумали такую метрику. Что мы сделали? Мы для каждого направления составили свой CSV-файл, такую табличку, в которой есть ссылка на мерж-реквест, конкретный критерий, который в этом MR был нарушен, файл, в котором он был нарушен, и конкретные строки в этом файле, на которые мы ожидаем, что нейронка отреагирует.

Прогоняем LLM, смотрим, какие комментарии она оставила, и сверяем с разметкой, которую мы сделали. Считаем true positive, false positive, false negative, precision, recall и так далее. И, собственно, делаем выводы, где наша нейронка ошибается.

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

Результаты

На сегодняшний день у нас 700+ проектов, которые пользуются нашим AI-ревьюером. Количество ревью в день в среднем 500–600, средняя длительность — 10–15 минут. Всё, всем спасибо, я готов ответить на вопросы.

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

Полезно
AI/ML
От легаси до Greenfield Один агентный SDLC — два очень разных пути
Александр Котков, руководитель отдела инженерии интеллектуальных систем ZeBrains, рассказал, как перевести на агентский SDLC абсолютно любой проект — от десятилетнего недокументированного легаси до Greenfield с нуля, и почему главное в этом пути не промпты, а работа с контекстом.
Полезно
5 мин.
AI/ML
AI First компания: от AI-SDLC к AI PDLC
Алоян Дмитрий, генеральный директор WILIX, на Agentic Dev Camp рассказал, как перевести компанию на AI-first разработку и выстроить PDLC поверх SDLC: от «петли намерений» до автоматической «петли реализации», а также про Harness, IDP и токен-роутер.
Полезно
3 мин.
Графики оплат в договорах поставщиков: как настроить в 1С:ERP, чтобы не было задолженности
Как настроить графики платежей поставщикам в 1С, чтобы видеть точные даты оплат, снизить просрочки и упростить финансовое планирование? Разбираем ключевые настройки, которые помогут синхронизировать закупки, финансы и бухгалтерию.

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