Облачные модели запрещены: ревью в закрытом контуре
Есть код, которому нельзя наружу. Если политика безопасности запрещает облачные модели или наружу просто нет пути, ревью может целиком идти на вашем железе. Продукт работает с OpenAI-совместимым API, поэтому Ollama или vLLM на вашем сервере — это настройка, а не форк.
Качество такого ревью задаёт модель, а не место, где она работает. Мы прогнали четыре открытые модели, от 122 миллиардов параметров до 2,8 триллиона, на диффе с четырьмя известными дефектами. В среднем они нашли от 1,7 до всех четырёх, а самая крупная находила все четыре в каждом прогоне. Облачная пара из остальных замеров раздела на том же диффе находит три.
Дальше — какую модель ставить и на каком железе, как настроить Ollama и vLLM, чтобы они не резали промпт и не ходили наружу, что показать безопаснику и почему периметр держит инсталляция, а не репозиторий.
Какую модель ставить
Замер 24 сентября 2026 года. Дифф — коммит с купоном в нашем учебном репозитории: 31 строка и четыре настоящих дефекта. Это дробные центы, почта клиента в строке лога, налог, посчитанный до скидки, и поиск кода купона в обычном объекте, где находятся и унаследованные свойства прототипа. Одна модель работает и генератором, и судьёй — в закрытом контуре обычно так и бывает. На каждую модель по три прогона.
| Модель | Параметры: всего / активных | Железо, примерно | Замечаний | Из 4 дефектов |
|---|---|---|---|---|
| Qwen3.5-122B-A10B | 122B / 10B | 1–2 × H200 | 2,7 | 2,3 |
| DeepSeek-V4-Flash | 284B / 13B | 2 × H200 | 2,0 | 1,7 |
| DeepSeek-V4-Pro-0813 | 1,6T / 49B | 8 × H200 | 3,3 | 2,7 |
| Kimi K3 | 2,8T / 104B | 16 × H200 | 5,3 | 4,0 |
| для сравнения: claude-sonnet-5 + судья claude-opus-5 | — | облако вендора | 3,7 | 3,0 |
Открытые модели мы запускали через OpenRouter: стойки с GPU у нас нет. Веса те же, что вы загрузите в vLLM, но провайдера OpenRouter выбирает сам, и часть провайдеров отдаёт квантованные веса — на полной точности цифры могут отличаться. Время и цена у хостинга свои, у вас они будут другими, поэтому в таблице их нет. Колонка «Железо» — прикидка по объёму весов с запасом под контекст. Строка облачной пары — замер 7 сентября на том же диффе из рецепта «Кто ищет, а кто судит».
Самая крупная открытая модель на этом диффе обошла облачную пару: Kimi K3 находил все четыре дефекта в каждом прогоне. DeepSeek-V4-Pro подошёл к паре вплотную, но поиск по прототипу не нашёл ни разу. Ложных замечаний в опубликованном мы не нашли: всё, что сверх четырёх известных дефектов, — по делу. Например, неизвестный купон молча даёт нулевую скидку, хотя в лог уходит «applied»; скидка и купон вместе могут увести сумму ниже нуля.
У открытых моделей номера строк съезжают: обычно на одну-три, у DeepSeek-V4-Flash — на двадцать и больше; у облачной пары они точные. Это не косметика. Судья проверяет находку по строке, которую она называет, и в двух прогонах из двенадцати снял верное замечание, потому что читал не тот код. В одном из них судья так и объяснил: строка 51 чистая, нарушение на 53-й, — а дефект был на 52-й.
Как подключить свою модель
Продукт не отличает облако от вашего сервера: Ollama, vLLM и другие серверы с OpenAI-совместимым API для него одинаковы. Но модели называйте явно. Без ролей в личном конфиге CLI возьмёт роли из .reviewgate/config.yml репозитория, и если там названа облачная модель, попросит её у вашего сервера — мы так и получили 404.
llm:
provider: ollama
base_url: http://llm.internal:11434/v1
timeout_ms: 1800000 # 30 minutes; the default is 10
generators:
- model: qwen3.5:122b-a10b
judges:
- model: qwen3.5:122b-a10bЕсли сервер — Ollama, два параметра демона обязательны:
OLLAMA_CONTEXT_LENGTH— размер контекста. Без него Ollama выбирает контекст сам, по объёму видеопамяти: на нашей машине вышло 4096 токенов, а промпт ревью даже для диффа в 31 строку весит около 7600. Лишнее Ollama отрезает молча — модель не видит ни правил, ни инструкций, а ревью всё равно проходит «успешно». Единственный след — строкаtruncating input promptв логе демона.OLLAMA_NO_CLOUD=1выключает облачные функции. Без него демон держит соединение с ollama.com, а модели с тегом:cloudиз его библиотеки работают на серверах Ollama, а не на ваших.
$ sudo systemctl edit ollama
[Service]
Environment="OLLAMA_CONTEXT_LENGTH=64000"
Environment="OLLAMA_NO_CLOUD=1"
$ sudo systemctl daemon-reload
$ sudo systemctl restart ollama
# after a review: no output means nothing was cut
$ journalctl -u ollama | grep "truncating input prompt"Для бота то же самое, только переменными окружения. Роли в .reviewgate/config.yml репозитория главнее них: если там названа облачная модель, бот попросит её у вашего сервера, и ревью упадёт с ошибкой. Для репозиториев без своих ролей генератора задаёт LLM_MODEL, судью — LLM_JUDGES.
LLM_PROVIDER=vllm
LLM_BASE_URL=http://llm.internal:8000/v1
LLM_MODEL=Qwen/Qwen3.5-122B-A10B
LLM_JUDGES=Qwen/Qwen3.5-122B-A10B
LLM_TIMEOUT_MS=1800000Если сервер — vLLM, обрезки можно не бояться: слишком длинный промпт он не режет, а отвечает ошибкой, и ревью сообщает об этом в MR. Зато у него своя телеметрия: по умолчанию vLLM отправляет анонимную статистику — железо, архитектуру модели, настройки, без промптов. Выключает её VLLM_NO_USAGE_STATS=1 в окружении сервера vLLM.
Таймаут — не формальность. По умолчанию вызов модели ждёт 10 минут. В нашем замере судья Qwen3.5-122B в одном прогоне рассуждал 25 минут и выдал 131 тысячу токенов, а две попытки до этого оборвал таймаут — на 15 и на 30 минутах. Дайте вызовам запас и посмотрите, сколько идёт прогон, прежде чем ставить ревью на хук.
Безопаснику — показать, а не рассказать
Закрытый контур остаётся утверждением, пока кто-нибудь его не проверил. Ниже — полный список того, что получает модель во время ревью, и команды, которыми скептик сам посмотрит, куда уходят соединения.
Что получает модель, помимо инструкций самого продукта:
- дифф изменения и пути файлов, исключённых из ревью (
ignoreили маркером в самом файле), — без их содержимого; - правила команды, её допущения из
dont_flagи текстreview_prompt— всё из.reviewgate/config.yml; - у бота — метаданные MR: заголовок, автор, ветки, проект, ссылка и описание до 2000 символов; CLI их не отправляет;
- судья — целиком файлы, которых касаются находки, и определения, которые они импортируют; с
full_file_contextто же для изменённых файлов получает и генератор; - с
environment_context— версии стека из ваших манифестов; - в режиме ответов — тред, на который отвечает бот, и строки кода вокруг обсуждаемой.
Чего модель не получает: историю git, рабочее дерево за пределами этих файлов и другие репозитории. А сам код после прогона продукт нигде не хранит.
И проверка. Запустите ревью из CLI со своей моделью и, пока оно идёт, посмотрите на сетевые соединения двух процессов: ревью и модели.
$ lsof -nP -i -a -p "$(pgrep -d, -x reviewgate)"
COMMAND PID FD TYPE NAME
reviewgat 84624 14u IPv4 TCP 127.0.0.1:53958->127.0.0.1:11434 (ESTABLISHED)
$ lsof -nP -i -a -c ollama -c llama-server
COMMAND PID FD TYPE NAME
ollama 84616 3u IPv4 TCP 127.0.0.1:11434 (LISTEN)
ollama 84616 7u IPv4 TCP 127.0.0.1:11434->127.0.0.1:53958 (ESTABLISHED)
ollama 84616 10u IPv4 TCP 127.0.0.1:54060->127.0.0.1:53960 (ESTABLISHED)
llama-ser 84745 3u IPv4 TCP 127.0.0.1:53960 (LISTEN)
llama-ser 84745 4u IPv4 TCP 127.0.0.1:53960->127.0.0.1:54060 (ESTABLISHED)
# the same daemon without OLLAMA_NO_CLOUD=1 adds one line (34.36.133.15 is ollama.com):
ollama 84446 11u IPv4 TCP 10.8.1.1:53824->34.36.133.15:443 (ESTABLISHED)Это живой вывод во время настоящего ревью на локальной модели: ревью ходит только на порт модели, Ollama — только к своему llama-server, и всё это не выходит за 127.0.0.1. Без OLLAMA_NO_CLOUD=1 та же команда показала ещё одну строку — соединение с ollama.com. Для бота в контейнере docker inspect покажет его сети и переменные окружения, то есть куда он настроен ходить, а tcpdump на хосте — куда он ходит на самом деле. Самая сильная проверка — allowlist исходящих, в котором просто нет строки для вендора моделей: если ревью работает с включённым allowlist, спор закончен.
Как устроить контур
Периметр держит инсталляция, а не репозиторий. Бот ревьюит MR по .reviewgate/config.yml из ветки этого же MR, а роль в этом файле может назвать любой бэкенд бота. Если названного бэкенда нет, основной генератор уходит к основному провайдеру инсталляции. Значит, если облако есть на инсталляции хоть где-то — именованным бэкендом или основным провайдером, — одна строка в MR отправит его дифф наружу, а сводка сообщит об этом уже после. Поэтому коду, которому нельзя наружу, нужна своя инсталляция без облака и allowlist исходящих без вендоров моделей. Делить по репозиториям на одной инсталляции можно только код, которому наружу можно.
Судьёй ставьте самую сильную модель, какая есть. Строка находки съезжает у генератора, а дальше решает судья: слабый читает не тот код и снимает верное замечание, сильный находит нужную строку сам. Судья DeepSeek-V4-Pro в таких случаях оставлял находку, а в одном прогоне прямо назвал настоящие номера строк. Если большой модели не хватает на каждый прогон целиком, пусть генератором работает модель поменьше, а большая остаётся судьёй.
И тратьте на модель, а не на проходы. Полноту двигает сама модель: дополнительные проходы судьи не добавят того, чего не увидел генератор, — судье позволено только отнимать. В таблице выше разница между самой слабой и самой сильной моделью — больше двух дефектов из четырёх.
Когда что-то пошло не так
Находки странные: про код, которого нет, на строках, где его нет. Скорее всего, Ollama обрезал промпт, и модель видела только его хвост — без правил и инструкций. Поищите в логе демона строку truncating input prompt и поднимите OLLAMA_CONTEXT_LENGTH.
Ревью падает с ошибкой 404 и именем облачной модели. Роли пришли из .reviewgate/config.yml репозитория, и там названа облачная модель. Для CLI задайте роли в личном конфиге, для бота поправьте роли в репозитории или уберите их, чтобы сработали LLM_MODEL и LLM_JUDGES.
Судья обрывается по таймауту, и находки публикуются без проверки. Прогон при этом не падает: судья выбывает, находки уходят без второго прохода, и сводка говорит об этом отдельной строкой. Модель отвечает дольше, чем ждёт вызов, — дайте timeout_ms (у бота — LLM_TIMEOUT_MS) больше запаса.
В lsof у Ollama есть соединение наружу. Не выставлен OLLAMA_NO_CLOUD=1, и демон держит соединение с ollama.com. Добавьте переменную в окружение сервиса и перезапустите демон.
Ревью сообщает, что контекст не помещается в окно модели. Промпт не влез в окно, которое vLLM задали через --max-model-len: такой промпт vLLM не режет, а отвечает ошибкой, и продукт пересказывает её с советами — убрать сгенерированные файлы через ignore, выключить full_file_context или разбить изменение. Можно и поднять само окно, если памяти на сервере хватает.
Судья снимает верные находки со ссылкой на номер строки. Причины снятия видны в блоке 🔬 при diagnostics: true и в отчёте reviewgate review --json. Строки съезжают у генератора, а судья слишком слаб, чтобы найти нужную сам. Поставьте судьёй модель сильнее — об этом глава выше.
Дальше
- Модели и провайдеры— все настройки схемы и именованные бэкенды
- Кто ищет, а кто судит— почему судья умеет только отнимать и почему он должен быть сильнее генератора
- Безопасность— тот же список со стороны вендора: что уходит и что хранится