Салют, Хабр!

У GitHub есть GitHub Copilot Code Review, y GitVerse — GigaCode Agent… но мы не можем их использовать, так как это внешние решения, которым не хочется открывать свой код. Когда речь идёт об AI-агентах в разработке, путь большинства крупных компаний — создавать собственный велосипед с нужным количеством колёс и педалей.

Меня зовут Аврора, я занимаюсь инфраструктурой фронтенда в SberDevices. Для упрощения работы мы реализовали систему агентов фронтенда, где скомбинировали два подхода: агентный цикл, где модель получает цель и инструменты, и детерминированный workflow, когда она выполняет отдельные ограниченные задачи. 

Сегодня расскажу о работе наших агентов: об ошибках фронтендеров по статистике AI-ревьюера, парсере для сужения контекста модели и функции системы, которая оказалась (внезапно) самой востребованной.

Проверка кода

AI-ревью автоматически стартует для любого merge request (MR); вместе с тем разработчик может ещё до реквеста запустить его и проверить свой код. 

Мы не дообучали модель для AI-ревьюера — это модель GigaChat-2-Max «из коробки». Промпт задаёт его базовую роль: ты фронтенд-разработчик, проведи ревью кода, если заметил ошибки — опиши их. AI-ревьюер ориентирован на фронтенд-разработку на typescript, хотя ограничений по языкам и фреймворкам нет, код можно проверить на любом языке, который понимает модель (но получится так себе; под другие языки нужно доработать промпт и правила). Для удобства работы ему задано несколько специальных условий:

  • комментировать только конкретную строку, которую изменил;

  • объяснять проблему и предлагать, как исправить в коде;

  • указывать точный диапазон строк, чтобы комментарий прикреплялся точно к строке, где найдена проблема, а не ко всему merge request. 

Сам AI-reviewer написан на TypeScript и NestJS. Взаимодействие с ним полностью серверное:

Технически AI-ревьюер — это не AI-агент. Его workflow задан кодом и выглядит так:

  1. Когда в merge request запускают ревью, GitLab отправляет боту webhook; он также получает идентификаторы проекта и самого MR. Бот через GitLab API запрашивает git diff изменённых файлов.

  2. Бот исключает из git diff служебные файлы, например, конфигурации, создаёт для каждого файла краткое описание и определяет, нужен ли ему подробный анализ. В зависимости от содержимого файл классифицируется как NEEDS_REVIEW («новый контент») или APPROVED («изменения в старом контенте»). Подробного анализа требуют только файлы, где код меняет логику или функциональность фичи/проекта.

  3. Бот отправляет diff в GigaChat. LLM-клиент создаётся через библиотеку gigachat:

import GigaChat from 'gigachat';

// Создаём клиент для обращения к модели
const llmClient = new GigaChat({
  model: 'GigaChat-2-Max',
  credentials: process.env.LLM_TOKEN,
});

// Передаём модели промпт с git diff
const response = await llmClient.chat({
  temperature: 0,
  messages: inputMessages,
});

Бот проверяет структурированный ответ модели, отфильтровывает LGTM и через GitLab A I публикует его в MR комментарием. Плюс ещё одна обязательная часть — краткое резюме изменений в описании MR: что конкретно он содержит? AI-агент снимает эту задачу с разработчика, а живой ревьюер может быстро понять суть merge request без просмотра изменений.  

С января по июль этого года AI-ревьюер проверил более 4 000 MR в 90 проектах. Потенциальные проблемы обнаружены в 8% случаев; это примерно один MR из 13. Кажется, немного, но это связано с тем, что культура разработчиков в нашей команде уже предполагает отдавать на код-ревью проверенный локальными агентами код. Если проблема находилась, бот оставляет около двух замечаний, чаще — одно. 

Чтобы определить, в каких конкретно случаях помогает AI-ревьюер, я проанализировала его комментарии в 2026 году (январь-июль). Сначала распределила их по трём категориям: конкретная проблема, общая рекомендация, комментарий опубликован ошибочно. Оказалось, что в более чем 90% случаев бот сигнализирует о конкретной проблеме. Более точно можно разделить замечания AI-ревьюера по темам:

  • 20% — стиль, именование и поддерживаемость;

  • 18% — ошибки логики и функционального поведения;

  • 16% — работа с null/undefined и обработка ошибок (самое актуальное для фронтендеров);

  • 12% — типы, API и контракты;

  • 11% — синтаксис, сборка и конфигурация.

Оставшиеся 23% делят между собой проблемы производительности, UI, асинхронного кода, тестов и безопасности.

Ошибки логики и функционального поведения: код синтаксически корректен, но использует не те данные, поэтому формирует неправильный URL
Ошибки логики и функционального поведения: код синтаксически корректен, но использует не те данные, поэтому формирует неправильный URL
Ещё пример ошибки логики и функционального поведения. При изменении входного текста значение не пересчитывается, и интерфейс может показать устаревшие данные. Бонусом это можно обозначить как ошибку использования хуков в реакте (актуально для фронтендеров)
Ещё пример ошибки логики и функционального поведения. При изменении входного текста значение не пересчитывается, и интерфейс может показать устаревшие данные. Бонусом это можно обозначить как ошибку использования хуков в реакте (актуально для фронтендеров)
Ещё больше ошибок!
Проблемы с формой входных данных и контрактом компонента или страницы
Проблемы с формой входных данных и контрактом компонента или страницы
Проблемы с null/undefined и обработкой отсутствующих значений: fallback на пустую строку может скрыть отсутствие обязательного значения
Проблемы с null/undefined и обработкой отсутствующих значений: fallback на пустую строку может скрыть отсутствие обязательного значения

Тестирование AI-ревьюера проходило в репозитории-песочнице (example-micromonorepo): там собраны примеры фронтенд-сервисов и тестовых MR на разных технологиях, где можно проверять сценарии ревью. Мы подключили бета-версию бота к вебхуку песочницы. Тест на изолированном стенде и в песочнице — это в целом наш обычный подход к тестированию инфраструктурных изменений. Благодаря подключению в виде вебхука после релиза бота обновлённая версия моментально появилась у всех пользователей.

AI-ревью можно отключить на старте MR на уровне репозитория в GitLab, но этого почти никто не делает. Вместе с ботом разработчик получает опцию автоматически оповещать человеческих ревьюеров через корпоративный мессенджер: не нужно копировать информацию по своему MR в канал месседжера и просить коллег посмотреть.  

Дополнительно мы реализовали небольшого бота Chuck Norris. Он просто напоминает ревьюерам о забытых MR, приходя в личку в корпоративном мессенджере раз в сутки и затем — спустя несколько дней. Это позволяет снизить количество забытых и потерянных MR. Проанализировала востребованность бота (данные за январь-август):

  • в общей сложности почти 4 тысячи напоминаний в 51 проекте;

  • медиана — 2 пинга на один MR. Среднее не репрезентативно из-за нескольких рекордсменов с 100+ пингами;

  • почти половине MR хватило одного напоминания.

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

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

Состояние процесса ревью хранится в MongoDB: участники, голоса, кворум, состояние MR, время последнего пинга. А основным хранилищем результатов выступает сам GitLab. 

Как проверить результат?

Механизм AI-ревьюера подразумевает, что после ревью код пройдёт аппрув двух разработчиков. Если кворум достигнут, изменения можно вливать в основную ветку. Как и любой AI, модель может допускать ошибки, поэтому все комментарии на ревью — рекомендательные. Разработчик-автор MR сам решает, согласен он с замечанием или нет, и I won’t fix it.

Определение ошибок

Ещё одна возможность системы агентов фронтенда — анализ логов. Разработчик отправляет в канал корпоративного мессенджера ссылку на job, упавшую на GitLab; агент анализирует логи и сообщает, что не так. Workflow здесь задан программно, а в самой фиче сочетаются код и AI-агент на основе GigaChat. Как работает анализ логов:

1. Бот находит в сообщении разработчика id job, проверяет через GitLab API, что этот job и вправду failed, и скачивает полный trace.

Формально trace может содержать вывод любых команд. Конкретно наш AI-агент работает со сценариями сборки и деплоя. Это сборка приложения, docker-сборка, установка зависимостей, линтеры и проверки типов, nginx и Ansible, ошибки сети и инфраструктуры.

2. Из trace самописный парсер выделяет лог последней упавшей команды и отправляет в GigaChat-2-Макс вместе с контекстом сервиса (название, репозиторий, окружение и т.д.). 

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

function parseJobLog(trace: string): string[] {
  return trace
    // Убираем служебные маркеры и цвета терминала
    .replaceAll(/section_start|section_end/g, '')
    // eslint-disable-next-line no-control-regex
    .replace(/\x1B\[[0-9;]*[mK]/g, '')
    // Оставляем секцию script, исключая after_script
    .split('Running after_script')[0]
    // Разбиваем вывод на блоки выполненных команд
    .split('$ ');
}

const failedCommandLog = parseJobLog(trace).at(-1);

В парсере стоит:

  • разделить универсальный и предметный парсинг.

  • Добавлять контекст выборочно. Конфиги и артефакты — только для тех job, где они помогают объяснить ошибку. Например, если это ошибка деплоя, из артефактов отправляем nginx-конфиг и исходный Jinja2-шаблон из репозитория.

  • предусмотреть такой корнер-кейс, как падение до пользовательского script. Ошибка runner, registry или сети может произойти раньше первой команды. Если парсер не нашёл начало script и последнюю пользовательскую команду (с причиной падения), он обрабатывает весь доступный trace.

const commandLogs = parseJobLog(trace);
const relevantLogs = commandLogs.at(-1) || extractRunnerFailure(trace);

Как можно увидеть, в коде парсер последовательно разбивает trace на строки, находит начало секции script и отбрасывает after_script — иначе его ошибки и служебные сообщения могут смешаться с основной командой. Затем он удаляет управляющие последовательности GitLab и ANSI-коды терминала, разделяет лог по приглашению shell $, возвращает массив блоков: команда и весь её вывод. Анализатор берёт последний блок:const failedCommandLogs = parseJobLog(trace).at(-1);Именно блок последней выполнявшейся команды с высокой вероятностью стала причиной падения job.

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

CI-логи потенциально содержат токены, адреса и переменные окружения, поэтому стоит отдельно очищать чувствительные данные (мы очищаем).

3. GigaChat ищет ошибки в логе. 

Согласно промпту, модель выступает помощником команды frontend-инфраструктуры. А пользователи модели — frontend-разработчики, которые могут знать, а могут и не знать устройство Ansible, nginx и процесса деплоя. 

4. AI-агент возвращает в тот же тред корпоративного мессенджера полный результат анализа логов.

В него входят такие данные, как:

  • на чьей стороне проблема — разработка, инфраструктура, не определено; 

  • какая стадия и причина падения указаны в GitLab;

  • лог упавшей команды;

  • нормальное человеческое объяснение, что именно произошло; 

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

В конце — очень важное предупреждение
В конце — очень важное предупреждение

Как проверить результат?

Используем программные проверки + пользовательские реакции: разработчик может поставить ответу лайк или дислайк. Для этой части системы нам важно сохранять данные, поэтому промпт и ответ модели храним в PostgreSQL gigachain с привязкой к postId треда корпоративного мессенджера. История взаимодействий позволяет разбирать неудачные ответы и анализировать реакции пользователей, а также сравнивать качество разных версий агента и модели.

Починка сборки 

Когда бот нашёл и озвучил разработчикам причины падения job, он может предложить автоматически исправить ошибку (если та на стороне разработки, а не инфраструктуры). Сейчас автоисправление поддерживает несколько сценариев — сборка приложения, Docker-сборка, конфигурация nginx. По сути мы начали с тех сценариев, которые:

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

  2. позволяют собрать достаточный контекст и предложить локальное изменение = AI-агента легко научить их чинить;

  3. дают объективно проверить результат без участия человека. 

Автоисправлением занимается классический AI-агент. Он получает исходники сервиса и набор tools, которые позволяют:

  • читать файл;

  • просматривать структуры каталогов;

  • искать по коду;

  • создавать и редактировать файлы;

  • точечно заменять тексты;

  • запускать shell-команды;

  • получать результаты сборки, линтера, теста и проч.

Количество обращений к модели не ограничено кодом, как в предыдущих вариантах: она работает итеративно, пока не решит задачу. Цель агента — «почини сборку»; он сам решает, какой инструмент вызвать и с какими аргументами. Результат каждого вызова возвращается модели. 

В целом мы имеем дело не с harness, но с инфраструктурой связанных агентов-сервисов, которые передают друг другу структурированные данные. Предыдущий этап — анализ логов — становится потенциальным входом в следующий. Но запускает переход человек: если проблема относится к одному из поддерживаемых сценариев, в треде появляется кнопка Try fix with AI. Её должен нажать создатель треда.  

Когда автоисправление закончено, CI повторно собирает проект. Если сборка прошла — появляется отдельная ветка и MR с изменениями в default branch. Важно: этот MR по-прежнему проверяет и аппрувит человек! Но AI-ревьюер тоже. 

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

Агент нашел ошибку в директиве nginx, исправил, сделал MR с этим изменением
Агент нашел ошибку в директиве nginx, исправил, сделал MR с этим изменением

По нашим подсчётам к агенту обращаются от 10 до 30 раз за день, то есть на момент написания статьи — минимум 1800 раз.  

Как проверить результат? 

Здесь основная проверка — сама сборка. Агент получает развалившийся проект. Если после merge request агента проект успешно собирается, значит, агент справился.

Заключение  

С развитием искусственного интеллекта генерация кода быстро дешевеет — быстрее, чем проверка. По ощущениям, вскоре компании будут производить больше кода, чем способны осмысленно отревьюить, протестировать, задеплоить и промониторить его востребованность в проде. Мне кажется, это определит ландшафт разработки в ближайшем горизонте: вкладываться стоит в то, что удешевит проверку кода. Это прежде всего тесты, типы, воспроизводимые сборки, способы максимально сократить радиус поражения при ошибке; инструменты, которые повышают наблюдаемость, и инструменты для проверки корнер-кейсов. Словом, всё, что обеспечивает стабильность и предсказуемость системы, а также упрощает жизнь живым разработчикам. Любым способом: самой востребованной функцией системы агентов оказался… автоматический призыв тестировщиков в проект после обновления кода. Разработчик никому не пишет и ни с кем не общается — просто даёт команду, а бот тэгает тестировщиков в треде релиза. Это самая неочевидная для нас функция, но насколько она оказалась нужна!

Комментарии (0)