ИИ-ревью кода в GitLab CI: комментарий к merge request через API
Job в GitLab CI получает diff merge request, отправляет его в Claude через Zukko и оставляет в MR комментарий с замечаниями, обновляя его при каждом пуше.
5 мин · · разработка · автоматизация
ИИ code review в GitLab CI не заменяет ревьюера, но снимает с него часть рутины: необработанные ошибки, забытые проверки на None, подозрительные SQL-запросы модель замечает до того, как MR откроет человек. Ниже — job для .gitlab-ci.yml и скрипт на Python: он берёт diff merge request, отправляет его в Claude через Zukko и оставляет в MR один комментарий, который обновляется при каждом новом пуше.
Как это работает
- На событие merge request GitLab запускает pipeline с job
ai-review. - Скрипт считает diff между базой MR (
CI_MERGE_REQUEST_DIFF_BASE_SHA) и текущим коммитом. - Diff уходит в модель через OpenAI-совместимый API Zukko (
https://api.zukko.pro/v1). - Ответ публикуется комментарием через Notes API GitLab:
POST /projects/:id/merge_requests/:merge_request_iid/notes. Если комментарий от прошлого запуска уже есть, он обновляется черезPUT .../notes/:note_id, чтобы не засорять обсуждение.
Все нужные идентификаторы GitLab передаёт в job сам, через предопределённые переменные:
| Переменная | Что содержит |
|---|---|
CI_API_V4_URL | адрес API GitLab v4 (работает и на self-hosted) |
CI_PROJECT_ID | ID проекта |
CI_MERGE_REQUEST_IID | номер MR внутри проекта |
CI_MERGE_REQUEST_DIFF_BASE_SHA | базовый коммит diff merge request |
CI_MERGE_REQUEST_TITLE | заголовок MR |
CI_MERGE_REQUEST_DRAFT | true, если MR — черновик (GitLab 17.10+) |
Переменные CI_MERGE_REQUEST_* доступны только в pipeline для merge request, поэтому job запускается по правилу CI_PIPELINE_SOURCE == "merge_request_event".
Токены и переменные CI/CD
Понадобятся два секрета.
Токен GitLab для комментариев. Удобнее всего — project access token (настройки проекта → Access tokens) со scope api и ролью, которой разрешено комментировать MR, например Developer. Комментарии будут идти от бот-пользователя проекта, а не от вашего аккаунта.
Ключ Zukko вида fetch_... — создаётся в кабинете. Для CI заведите отдельный ключ с дневным и месячным лимитом в долларах и списком разрешённых моделей: тогда ошибка в pipeline не потратит больше лимита. Подробнее — в разделе лимиты ключей.
Оба значения добавьте в Settings → CI/CD → Variables под именами GITLAB_REVIEW_TOKEN и ZUKKO_API_KEY, с флагом Masked, чтобы они не попадали в логи. С флагом Protected переменная видна только pipeline защищённых веток, а ветки MR обычно не защищены, — тогда job её не получит. Учтите обратную сторону: незащищённую переменную может прочитать любой, кто может пушить ветки в проект, поэтому выдавайте токенам минимальные права.
Ещё один нюанс: pipeline для MR из форка по умолчанию запускаются в проекте-форке, и переменные основного проекта туда не попадают.
Job в .gitlab-ci.yml
ai-review:
stage: test
image: python:3.12
variables:
GIT_DEPTH: 0
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_DRAFT != "true"
script:
- pip install --quiet openai requests
- python ci/ai_review.py
allow_failure: true
timeout: 10mЧто здесь важно:
GIT_DEPTH: 0— полная история, иначе базового коммита MR может не оказаться в клоне, иgit diffупадёт.- Образ
python:3.12(неslim) уже содержитgit. allow_failure: true— если API недоступен или кончился баланс, MR всё равно можно влить.- Черновики MR пропускаются, пока автор не снимет статус Draft.
Скрипт ai_review.py
Положите файл в ci/ai_review.py:
import os
import subprocess
import requests
from openai import OpenAI
API = os.environ["CI_API_V4_URL"]
PROJECT = os.environ["CI_PROJECT_ID"]
MR_IID = os.environ["CI_MERGE_REQUEST_IID"]
BASE_SHA = os.environ["CI_MERGE_REQUEST_DIFF_BASE_SHA"]
GITLAB = {"PRIVATE-TOKEN": os.environ["GITLAB_REVIEW_TOKEN"]}
MODEL = os.getenv("REVIEW_MODEL", "claude-sonnet-5")
MAX_DIFF_CHARS = int(os.getenv("REVIEW_MAX_DIFF_CHARS", "60000"))Пояснения к решениям:
- Исключения. Lock-файлы и минифицированный код почти никогда не нужно ревьюить, а токенов они съедают много. Список
EXCLUDE— обычные pathspec git, дополняйте под свой проект. - Ограничение размера.
MAX_DIFF_CHARSзащищает от огромных MR (массовые переименования, сгенерированный код). Для MR, которые всё равно не влезают, лучше разбивать diff по файлам и отправлять несколько запросов. - Один комментарий. Метка
<!-- ai-review -->в начале комментария невидима в интерфейсе, но по ней скрипт находит свой прошлый комментарий и обновляет его. - Ошибки. Если Zukko вернёт ошибку (401 — ключ, 402 — баланс, 403 — модель не разрешена для ключа, 429 — частота запросов), SDK поднимет исключение, job упадёт с понятным текстом в логе, а
allow_failureне даст заблокировать MR.
Стоимость и выбор модели
Каждый пуш в ветку MR запускает ревью заново, так что считать стоит по числу пушей. Пример: diff на 8 000 входных токенов и ответ на 800 выходных на claude-sonnet-5 ($0.30 / $0.30 за 1M токенов в Zukko):
(8 000 + 800) × $0.30 / 1 000 000 = $0.00264 за запуск. При 200 запусках в месяц — 200 × $0.00264 = $0.528.
На glm-5.3-flash ($0.04 / $0.04) тот же объём: 200 × 8 800 × $0.04 / 1 000 000 = $0.0704 в месяц. Модель меняется переменной REVIEW_MODEL без правки скрипта; актуальный список — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Какая модель находит больше настоящих проблем в вашем коде, проверьте на своих задачах: прогоните 10–15 уже закрытых MR с известными багами через две модели и сравните, что каждая нашла.
Частые вопросы
Job падает с «fatal: bad object» на git diff
В клоне нет базового коммита MR. Проверьте, что в job задано GIT_DEPTH: 0, а в настройках проекта (CI/CD → General pipelines) не стоит маленькая глубина клонирования, которая переопределяет значение.
Комментарий не появляется, в логе 401 или 403 от GitLab
Токен не задан, истёк или у него нет scope api. Для project access token проверьте и роль: у Guest может не хватить прав комментировать MR. Если переменная помечена Protected, в pipeline незащищённой ветки её просто нет.
Можно ли оставлять замечания на конкретных строках?
Notes API создаёт общий комментарий к MR. Для комментариев к строкам в GitLab есть Discussions API с указанием позиции в diff — это сложнее: нужно сопоставлять номера строк из ответа модели с версиями diff. Начните с общего комментария.
Не опасно ли отправлять код во внешний API?
Это решение вашей компании: diff уходит модели через Zukko. Если в репозитории бывают секреты, добавьте в EXCLUDE файлы конфигурации и .env, а лучше — не храните секреты в репозитории вовсе. Про форматы API и авторизацию — в документации Zukko.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.