← Интеграции

GitLab: PR-Agent, CI и Duo

ревью MR, описания, changelog и Duo Self-Hosted

Что даёт

Автоматическое ревью и описание merge request через PR-Agent (Qodo Merge), генерация changelog и сообщений коммитов из CI, подключение GitLab Duo Self-Hosted к нашим моделям.

Эндпоинт: POST https://api.morsmorii.ru/v1/chat/completions

Какую модель брать

ревью MR — claude-sonnet-5-5; описания MR и сообщения коммитов — claude-haiku-4-5; архитектурный разбор — claude-opus-5-5

Ревью требует понимания кода и осторожности — Sonnet 5.5. Описания и коммиты — короткие тексты по diff, Haiku делает их за копейки. Архитектурный разбор — редкая задача с большим контекстом, где качество Opus окупается.

Ориентир стоимости

ревью MR на 300 строк: ~20 000 токенов на входе и 2 000 на выходе — около 19 ₽; сообщение коммита или описание MR на Haiku 4.5: ~3 000 токенов на входе — около 1 ₽; архитектурный разбор на Opus 5.5: ~50 000 токенов на входе и 5 000 на выходе — около 90 ₽

Шаги

  1. GitLab Duo Self-Hosted (нужны GitLab Premium или Ultimate с Duo Enterprise и свой AI Gateway — лицензии у нас нет, поэтому только по документации):
  2. Admin Area → AI-powered features → Self-hosted models → Add self-hosted model.
  3. Platform: OpenAI-compatible; Endpoint: https://api.morsmorii.ru/v1; Model identifier: custom_openai/claude-sonnet-5-5; API key: ключ Morsmorii.
  4. Затем в Features назначьте модель на Code Suggestions (generation), Duo Chat и Merge request summary; для summary можно завести вторую модель custom_openai/claude-haiku-4-5.
  5. Проверка с их стороны: Admin → Self-hosted models → Test model — это обычный chat/completions к нам.

Конфигурация

Вместо sk-ВАШ_КЛЮЧ_MORSMORII подставьте ключ из раздела «API-ключи». Для инструмента лучше завести отдельный ключ с ограничением по моделям и бюджетом.

PR-Agent (Qodo Merge) как CI-job: .gitlab-ci.yml
# Переменные проекта (Settings → CI/CD → Variables, masked):
#   MORSMORII_API_KEY — ключ Morsmorii (ограничьте его claude-sonnet-5-5 и claude-haiku-4-5)
#   GITLAB_PAT        — personal access token с правами api (комментарии в MR)
pr_agent:
  stage: test
  image:
    name: codiumai/pr-agent:latest
    entrypoint: [""]   # у образа свой ENTRYPOINT, а runner'у GitLab нужен shell
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  variables:
    CONFIG__GIT_PROVIDER: gitlab
    GITLAB__URL: $CI_SERVER_URL
    GITLAB__PERSONAL_ACCESS_TOKEN: $GITLAB_PAT
    OPENAI__KEY: $MORSMORII_API_KEY
    OPENAI__API_BASE: https://api.morsmorii.ru/v1
    CONFIG__MODEL: openai/claude-sonnet-5-5
    CONFIG__FALLBACK_MODELS: '["openai/claude-haiku-4-5"]'
    CONFIG__CUSTOM_MODEL_MAX_TOKENS: "200000"
    PR_DESCRIPTION__MODEL: openai/claude-haiku-4-5
  script:
    - cd /app   # код PR-Agent в образе лежит здесь
    - MR_URL="$CI_SERVER_URL/$CI_PROJECT_PATH/-/merge_requests/$CI_MERGE_REQUEST_IID"
    - python -m pr_agent.cli --pr_url "$MR_URL" describe
    - python -m pr_agent.cli --pr_url "$MR_URL" review
    - python -m pr_agent.cli --pr_url "$MR_URL" improve
PR-Agent как вебхук (сервер, отвечает на /review в комментариях)
docker run -d --name pr-agent -p 3000:3000 \
  -e CONFIG__GIT_PROVIDER=gitlab \
  -e GITLAB__URL=https://gitlab.example.com \
  -e GITLAB__PERSONAL_ACCESS_TOKEN=glpat-... \
  -e GITLAB__SHARED_SECRET=любая_строка \
  -e OPENAI__KEY=sk-ВАШ_КЛЮЧ_MORSMORII \
  -e OPENAI__API_BASE=https://api.morsmorii.ru/v1 \
  -e CONFIG__MODEL=openai/claude-sonnet-5-5 \
  -e CONFIG__CUSTOM_MODEL_MAX_TOKENS=200000 \
  -w /app --entrypoint python \
  codiumai/pr-agent:latest -m pr_agent.servers.gitlab_webhook
# В проекте: Settings → Webhooks → URL http://ваш-хост:3000/webhook,
# Secret token = GITLAB__SHARED_SECRET, события: Merge request, Comments.
Changelog из коммитов: job на curl
changelog:
  stage: deploy
  image: alpine:3.20
  rules:
    - if: $CI_COMMIT_TAG
  variables:
    GIT_DEPTH: "0"   # полная история: иначе git describe не найдёт прошлый тег
  before_script:
    - apk add --no-cache curl jq git
  script:
    - PREV=$(git describe --tags --abbrev=0 "$CI_COMMIT_TAG"^ 2>/dev/null || git rev-list --max-parents=0 HEAD)
    - LOG=$(git log --no-merges --pretty='- %s (%h)' "$PREV".."$CI_COMMIT_TAG")
    - |
      jq -n --arg log "$LOG" --arg tag "$CI_COMMIT_TAG" '{
        model: "claude-haiku-4-5",
        messages: [
          {role: "system", content: "Ты пишешь changelog для релиза. Сгруппируй изменения: Новое, Исправления, Внутреннее. Кратко, по-русски, markdown."},
          {role: "user", content: ("Релиз " + $tag + "\n\nКоммиты:\n" + $log)}
        ]}' > req.json
    - |
      curl -sS --fail https://api.morsmorii.ru/v1/chat/completions -H "Authorization: Bearer $MORSMORII_API_KEY" \
        -H "Content-Type: application/json" -d @req.json | jq -r '.choices[0].message.content' > CHANGELOG-$CI_COMMIT_TAG.md
    - cat CHANGELOG-$CI_COMMIT_TAG.md
  artifacts:
    paths: [CHANGELOG-$CI_COMMIT_TAG.md]
Описание MR из diff (Haiku), комментарием в MR
mr_summary:
  stage: test
  image: alpine:3.20
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  variables:
    GIT_DEPTH: "0"   # при глубине 20 git diff A...B отвечает «no merge base»
  before_script:
    - apk add --no-cache curl jq git
  script:
    - git fetch origin "$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" --depth=200
    - DIFF=$(git diff "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME"...HEAD | head -c 60000)
    - |
      jq -n --arg diff "$DIFF" '{
        model: "claude-haiku-4-5",
        messages: [
          {role: "system", content: "Опиши merge request по diff: что меняется, зачем, на что смотреть ревьюеру. По-русски, до 10 строк, markdown."},
          {role: "user", content: $diff}
        ]}' > req.json
    - |
      SUMMARY=$(curl -sS --fail https://api.morsmorii.ru/v1/chat/completions -H "Authorization: Bearer $MORSMORII_API_KEY" \
        -H "Content-Type: application/json" -d @req.json | jq -r '.choices[0].message.content')
    - |
      jq -n --arg body "$SUMMARY" '{body: $body}' | curl -sS -X POST \
        "$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
        -H "PRIVATE-TOKEN: $GITLAB_PAT" -H "Content-Type: application/json" -d @-
Сообщение коммита, один раз: хук .git/hooks/prepare-commit-msg (сохраните файл и chmod +x)
#!/bin/sh
# $1 — файл сообщения; $2 — источник (пусто при обычном git commit, тогда и пишем)
[ -n "$2" ] && exit 0
[ -z "$MORSMORII_API_KEY" ] && exit 0
MSG=$(git diff --cached | head -c 30000 | jq -Rs '{model: "claude-haiku-4-5", messages: [
  {role: "system", content: "Напиши сообщение коммита по diff: заголовок до 72 знаков, пустая строка, 1-3 строки сути. По-русски."},
  {role: "user", content: .}]}' \
  | curl -sS --fail https://api.morsmorii.ru/v1/chat/completions \
      -H "Authorization: Bearer $MORSMORII_API_KEY" -H "Content-Type: application/json" -d @- \
  | jq -r '.choices[0].message.content // empty')
[ -n "$MSG" ] || exit 0
{ printf '%s\n\n' "$MSG"; cat "$1"; } > "$1.tmp" && mv "$1.tmp" "$1"
Сообщение коммита, каждый раз
export MORSMORII_API_KEY="sk-ВАШ_КЛЮЧ_MORSMORII"   # или в ~/.bashrc
git add -A
git commit   # редактор откроется с сообщением от Haiku — поправьте и сохраните
# git commit -m "..." хук не трогает: сообщение уже задано

Как ограничить ключ

  1. В разделе «API-ключи» создайте отдельный ключ для этого инструмента — так расход виден отдельно, а отзыв не заденет остальное.
  2. В поле «Модели» оставьте только рекомендованные выше: ключ ответит 403 на любую другую модель.
  3. Задайте месячный бюджет в рублях: после него — 429 key_budget_exceeded, списание останавливается.
  4. В команде каждый сотрудник заводит свои ключи: расход по людям видно в разделе «Команда» и в GET /v1/usage.

Тонкости

  • Ключ для CI ограничьте моделями claude-sonnet-5-5 и claude-haiku-4-5 и месячным бюджетом (например, 2 000 ₽) — расход по ключу виден в /developers и в GET /v1/usage.
  • PR-Agent ходит через litellm: префикс openai/ обязателен, а CONFIG__CUSTOM_MODEL_MAX_TOKENS нужен, потому что наши id ему неизвестны.
  • Запуск PR-Agent в CI (entrypoint: [""] и cd /app) и как вебхук (python -m pr_agent.servers.gitlab_webhook) — по документации PR-Agent; проверьте тег образа на Docker Hub: у PR-Agent бывает отдельный тег для вебхука (codiumai/pr-agent:gitlab_webhook), тогда --entrypoint и команду после образа можно убрать.
Как проверено: проверено его же запросами. Запрос PR-Agent (system+user, temperature 0.2 на claude-sonnet-5-5) — 200 после правки 06.09; jobs changelog и сообщение коммита (тот же запрос jq + curl, что в хуке) выполнены как в CI: alpine + jq + curl, по git log и diff этого репозитория, Haiku ответил по-русски. Комментарий в MR, вебхук и Duo Self-Hosted — по документации: инстанса GitLab с лицензией нет.