Пишем AI-помощника для ревью пулл-реквестов: выбор модели и разработка серверной части

Пишем AI-помощника для ревью пулл-реквестов: выбор модели и разработка серверной части

Я — Полина Ященко, старший инженер по разработке ПО в YADRO. Мы с командой тестируем гипотезы и активно применяем искусственный интеллект для улучшения процессов разработки. Недавно мы запустили AI-ревьюера — бота, который помогает находить проблемы в стиле и логике кода.

Мы создали бота, чтобы упростить ревью пулл-реквестов. В команде есть стажёры, которые допускают типичные ошибки, например, открывают слишком большие PR. Иногда просто не хватало времени, чтобы оперативно всё проверить. Расскажу, как мы выбирали модель и разрабатывали серверную часть. Бот не обладает высокой производительностью, но отлично справляется с основной задачей — помогает инженерам находить и исправлять повторяющиеся ошибки.

Что такое AI-ревьюер

AI-ревьюер — это бот для пулл-реквестов (Pull Request, PR), который анализирует добавленный и изменённый код и оставляет комментарии с предложениями по улучшению. Он выявляет проблемы, связанные со стилем, логикой и конструкциями кода. Ревьюеру остаётся только проверить запрос и проанализировать дополнительные аспекты: наличие pytest-маркеров, соответствие автотеста его описанию в TestY TMS.

Как исследовали модели

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

Выбор модели

Исследование проводилось от самых маленьких моделей к крупным — так мы искали оптимальный баланс между нагрузкой и аналитическими возможностями. Для CPU-сервера использовали модели в формате GGUF, оптимизированные для быстрой загрузки и работы.

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

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

Ответы моделей:

  • phi-2: придумывал несуществующие логи, давал бесполезные советы.
  • Tinyllama: предлагал переустановить библиотеку, но не понимал суть ошибки.
  • Mistral: предложил корректные причины и решения — установку через pip и сборку с C-Python.
  • Qwen3: перечислил несколько возможных причин, включая несовместимость версий Python и отсутствие библиотеки в системе. Ответ был обрезан из-за ограничения по токенам, но полезность высокая.

Выводы по моделям:

  • phi-2 и Tinyllama отвечали непоследовательно, иногда выдавали бессмыслицу.
  • Mistral и Qwen3 хорошо анализировали ошибки и предлагали реальные решения.
  • Qwen3 показала лучшую производительность на слабом железе без GPU, что соответствовало нашим условиям.

Какую модель выбрали — станет ясно дальше.

Выбор способа развёртывания сервера

Мы рассматривали несколько инструментов для запуска API-сервера с LLM:

  • ctransformers: Python-библиотека для GGUF-моделей на CPU. Отклонена из-за устаревания и сложной документации.
  • llama-cpp-python: интеграция с OpenAI API. Не выбрана, так как llamafile использует её под капотом.
  • vLLM: удобный инструмент, но его нельзя установить через pip на CPU. Сборка из исходников усложняет миграцию, а производительность ниже, чем у альтернатив.
  • llamafile: выбран. Позволяет запускать модель одним файлом, разворачивает OpenAI-совместимый сервер, легко настраивается и быстро работает.

В качестве модели выбрали Qwen3-Coder-4b — оптимизирована для анализа кода, требует минимум ресурсов и хорошо отвечает на промпты.

Размер модели — 4 миллиарда параметров. Чем больше параметров, тем сложнее задачи модель может решать, но для нашей задачи 30b избыточны. 4b — оптимальный выбор.

Анализ кода занимает от одной до восьми минут в зависимости от объёма. Мы ограничиваем ответ до 700 токенов, чтобы избежать слишком длинных комментариев.

Оптимальные параметры модели:

  • Размер контекста — 48 000 токенов (хватает для файлов на 1000–1500 строк).
  • Температура — 0.3 (умеренная креативность, акцент на точность).
  • RoPE Scaling — Yarn, с параметрами rope-freq-base=1000000 и rope-freq-scale=0.1 для лучшей обработки длинного кода.
  • 8 CPU-потоков для вычислений.
  • Параллельная обработка отключена — все запросы в одном потоке.
  • Пропускная способность — 512 токена в секунду.
  • Включены mlock и no-mmap — модель блокируется в RAM, чтобы избежать замедлений из-за свопа.

Чтобы эффективно обрабатывать длинные контексты, мы масштабируем токены: rope-freq-scale заставляет модель воспринимать 100 000 токенов как 10 000. Это позволяет использовать параметры, обученные на коротких контекстах. Чтобы избежать путаницы в порядке слов, rope-freq-base повышает «разрешение» диапазона.

Разработка серверной и клиентской части

Изначально ключевыми критериями были простота развёртывания и контроль над процессом. Сервер запускался на виртуальной машине во внутреннем облаке как systemd-сервис. Это позволяло просматривать логи через Journald и автоматически перезапускать сервис при сбоях.

Модель запускалась одной командой:

root/Qwen_Qwen3-4B-Q4_K_M.llamafile --server --nobrowser --host 0.0.0.0 --port 9000 -c 48000 -t 8 --temp 0.3 --batch-size 512 --parallel 1 --mlock --no-mmap --rope-scaling yarn --rope-freq-base 1000000 --numa distribute --rope-freq-scale 0.1

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

Клиентская часть состояла из функций:

  • получение git diff;
  • определение параметров запроса (длина ответа);
  • отправка запроса на сервер;
  • публикация ревью.

Алгоритм выбора кода был простым: если в diff от 10 до 20 строк — ответ до 300 токенов, больше — до 500. Маленькие изменения не отправлялись.

Со временем стало ясно, что Qwen3-4B недостаточно для глубокого анализа. Мы хотели использовать модель с большим количеством параметров, но без увеличения своих ресурсов. Поэтому перешли на платформу Dify.

Теперь используется модель Qwen3-30b-A3B-Instruct-2507. Вся логика — промпты и ИИ-обработка — собрана в Dify.

Ключевое изменение: решение о том, что и как анализировать, перешло с клиента на сервер. Раньше клиент сам определял, какой код отправлять. Теперь он просто передаёт git diff с контекстом, а ИИ решает, нужно ли ревью и какой длины.

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

В итоге мы перешли от самописного сервера с 4B-моделью к облачному пайплайну с 30B-моделью. Клиент стал тоньше, сервер — умнее, а качество ревью выросло.

Запрос к серверу прост — это OpenAI-совместимый API:

В промте используется /nothink, чтобы отключить «размышления вслух». Эта функция тратит токены и не нужна для нашей задачи.

Алгоритм ревью теперь сложнее:

  • Анализируем git-diff каждого файла (только .py).
  • Разбиваем на сегменты добавленных строк.
  • Выделяем контекст для каждого сегмента.
  • Отправляем контекст ИИ.
  • ИИ решает, нужно ли ревью и какой длины.
  • Формируется комментарий нужного размера.
  • Ответ публикуется в Bitbucket.
  • Переходим к следующему сегменту, затем к следующему файлу.

Клиентская часть требует доработки:

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

Proof of concept

Мы реализовали схему: Bitbucket → Albert → Jenkins → AI → Bitbucket. Такая же архитектура используется для других проверок в TATLIN.BACKUP.

Процесс:

  1. При открытии PR срабатывает webhook в Bitbucket.
  2. Запрос отправляется на сервис Albert.
  3. Albert фильтрует событие и вызывает Jenkins API с параметрами PR.
  4. Jenkins запускает job, который запускает Python-скрипт.
  5. Скрипт получает diff через Bitbucket API, парсит добавленные строки и выделяет контекст.
  6. Код отправляется на AI-сервер, приходит ревью.
  7. Скрипт публикует комментарии в PR по файлам и строкам.

На разработку ушёл месяц — это не основная задача команды. Планируем улучшать бота: добавим возможность диалога с ним и загрузим базу кода наших репозиториев. Это снизит универсальность, но повысит качество ревью. Команда разработчиков СХД уже создаёт похожий пайплайн для Rust-кода.

Читать оригинал