Введение
Кто мы?
Меня зовут Данил Чечков, я Team Lead команды High End Meta Backend в «Леста Игры». Мы занимаемся всей web-составляющей «Мира кораблей». В нашем арсенале огромное количество микросервисов, работающих на Python и Go. Мы отвечаем за покупки в meta-валюте, авторизацию, стабильность инвентаря и профиля игрока, клановые сервисы, а также многое-многое другое.
Наш основной продукт – высококачественные web-сервисы на стыке интеграции с игрой. И, да, интеграция – часть нашей работы.
А ещё мы любим новые технологии и стараемся с ними знакомиться, чтобы оценить, как они могут принести выгоду бизнесу и нам. Одна из таких технологий – LLM
Что сделали?
Гефестыча. Вы когда-нибудь копировали код из PR (MR) и отправляли LLM для объяснений? Вот и мы никогда, а решили попробовать. Попробовали, автоматизировали и интегрировали. Назвали – Hephaestus. Забегая вперёд, скажу, что нам очень понравилось. В этой статье я подробнее расскажу и про сам процесс внедрения, и про его преимущества.
Проблема
С чем столкнулись?
Поговорим о Code Review – это длительный и трудоёмкий процесс, который зачастую превращается в рутину. Чем больше объём изменений, тем меньше желания проводить ревью. Знакомо?
А ещё постоянные сообщения в рабочий мессенджер, свои задачи и созвоны, которые мешают довести процесс до конца.
Одна из мер, которую мы ввели, – обязательный час в неделю на командное Cross Review. Это очень помогло разобрать очередь запросов на слияние, однако, и этого было мало.
Решение
«Если это может быть автоматизировано – это должно быть автоматизировано». Вот так я и подумал, когда пулл-реквесты стали висеть по две недели в ожидании ревью.
Тут я хотел бы сделать важное отступление. В нашем арсенале уже отлажены CI/CD процессы, линтеры, форматеры, единообразная архитектура и огромное количество unit-тестов. Всё это – первое, что вы должны сделать для повышения качества кода, доставляемого в продакшен. LLM – лишь вишенка на торте, которая доступным языком расскажет вам своё мнение.
Прежде чем рассказывать про нашу реализацию, перечислю возможные пути. Первый – закрытые платформы вроде CodeRabbit или Codium. Они хорошо работают, но код уходит на чужие серверы, что для нас риск. Второй – готовые открытые решения, например github/ai-review. Они появились, когда мы уже начали пилить Гефестыча, но сегодня я бы рекомендовал стартовать с них, чтобы сэкономить сотни человеко-часов. Третий – написать свою обёртку поверх self-hosted моделей.
Мы выбрали третий путь. В компании уже был инструмент для взаимодействия с LLM с открытыми весами – OpenWebUI во внутренней сети. Это удобный инструмент, предоставляющий OpenAI-совместимое API. А значит, можно написать инструмент для моделей с открытыми весами и в будущем, если появится такая возможность, переехать на нечто более мощное или закрытое и платное.
Минимальные системные требования: машина с VRAM около 24 гигабайт (у нас 4090), либо смирение с медленной скоростью обработки ревью при делегировании части работы на RAM и CPU.
Да, есть готовые и закрытые решения
Меня подстегнул пост Николая Соболева – сделать своё self-hosted-решение, которое можно крутить во внутренней сети компании и использовать все преимущества open-source-моделей.
Кроме того, уверен: если забить в поисковик Code Review AI, можно найти огромное количество закрытых решений, делающих свою работу очень хорошо.
Если же вам, как и нам, не подходят закрытые платформы, то на сегодняшний день уже есть:
Готовые и открытые решения
github/ai-review – забавно, но первая версия появилась примерно в то же время, когда Гефеста уже интегрировали в разработку нашим департаментом. Было уже поздно сворачивать или резко менять стек проверенных технологий, так что мы продолжили внедрение и развитие своей.
Сегодня я бы присмотрелся к этому варианту – он сэкономит вам сотню человеко-часов разработки: у вас будет решение, которое вы сможете впоследствии корректировать и совершенствовать уже внутренними усилиями.
Отличный вариант, чтобы оценить лично, насколько вообще могут быть полезны LLM-инструменты для Code Review именно вам и вашей команде.
Кроме того, мне удалось подготовить для вас сравнительную таблицу с открытыми self-hosted:
|
Сервис |
Интеграция с системами управления проектами и задачами |
Интеграция с сервисами системы управления версиями |
Интеграция с корпоративной базой знаний |
Кастомизация инструкций (per-project / per-PR) |
RAG для поиска по кодовой базе |
|
❌ |
GitLab, GitHub, Bitbucket Cloud, Bitbucket Server, Azure DevOps, and Gitea |
❌ |
✅ |
❌ |
|
|
JIRA, GitHub Issues, GitLab Issues |
GitHub, GitLab, Bitbucket, and Azure DevOps |
❌ (только enterprise/SaaS) |
✅ |
⚠️ Конфиг есть, но только enterprise по факту (Qodo single-tenant/on-prem) |
|
|
через плагины/MCP |
GitHub, GitLab, Bitbucket, and Azure Repos |
❌ |
✅ |
✅ |
|
|
❌ |
только GitHub |
❌ |
✅ |
❌ |
|
|
❌ |
только GitHub |
❌ |
⚠️ Ограниченно (статические промпты; последнее обновление — дек. 2023) |
❌ |
|
|
❌ |
только GitHub |
❌ |
⚠️ |
❌ |
|
|
❌ |
только GitHub |
❌ |
⚠️ |
❌ |
|
|
❌ |
GitHub + GitLab |
❌ |
✅ |
❌ |
|
|
❌ |
только GitLab |
❌ |
✅ |
❌ |
|
|
❌ |
только GitHub |
❌ |
✅ |
⚠️ (агент обходит файлы в runtime, без постоянного индекса) |
|
|
❌ |
только GitHub |
❌ |
✅ |
✅ Да (индексация репо) — но лицензия BSL-1.1, не полностью FOSS |

Имплементация
OpenWebUI
Вполне себе полноценный чат с любой загруженной в ollama LLM.
Я решил попробовать там скормить в виде .patch файлов diff и поспрашивать за качество изменений. И знаете – qwen3-coder:30b неплохо с этим справился. Да, ревью нельзя назвать совершенным, т.к. у qwen просто нет вашего контекста, но это поправимо. Однако, что qwen точно смог заметить, smelly, security и perf code issues. Получилось довольно душное Code Review, но с этим можно работать и настроить инструмент именно на ваш проект.
Open WebUI API
Кроме функционала чата, OpenWebUI также предоставляет OpenAI-совместимый API — казалось бы, вот он, фундамент для универсального инструмента автоматизации. Но не спешите. Именно на этом этапе мы совершили ошибку.
Усвоенные уроки
Я вам советую использовать llama.cpp напрямую, потому что в OpenWebUI API для загрузки документов и преобразования их в векторы для RAG — синхронное.
Заметили мы это слишком поздно, только на этапе проверок интеграции RAG и базы знаний для расширения контекста. Нас ввело в заблуждение асинхронное API для чата. Мы полноценно интегрировали весь функционал, проводя проверки на небольшом количестве файлов и получая ответы со статус-кодом 200.
А когда столкнулись с реальными нагрузками, осознали: в базу знаний попадает намного меньше файлов, чем мы отправляем. Пришлось чинить на ходу и превращать асинхронный код в синхронный.
Это огромный блокер, если вы планируете распространить сервис на большое количество департаментов, которые будут запускать систему в ревью ежедневно. Сразу остановитесь и переключитесь на ollama, llama.cpp или vllm.
Проблема в том, что это будет очень медленное ревью. В зависимости от того, как вы будете выгружать файлы в контекст, это может занять много времени даже асинхронно. Мы кормим LLM diff и пропускаем через эмбедеры всю кодовую базу исходной ветки. Всё это дает нам хорошее качество ревью и планы для будущих оптимизаций.
Следующее, что мы сделали неверно, – написали небольшой клиент. Ради пары эндпоинтов мы не хотели интегрировать целую библиотеку, поэтому ограничились самописным клиентом к этому API.
Предостерегу вас от наших ошибок и порекомендую: сразу используйте Pydantic AI. Это целый набор инструментов, с которым организация подобной системы займёт не так много логики. Кроме того, это полноценный конструктор для того, чтобы вы могли сделать что угодно с LLM.
Псевдокод
Начинаем с провайдера
Предположим, что вы уже подняли ollama/llama.cpp. Если ещё нет, то это довольно легко сделать:
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.ollama import OllamaProvider
ollama_provider = OllamaProvider(
base_url="http://localhost:11434/",
api_key="your_amazing_api_key",
)
model = OpenAIChatModel(
model_name="qwen3-coder:latest",
provider=ollama_provider,
)
agent = Agent(model=model)
result = agent.run_sync("Hello world!")
print(result.output)
Это очень лаконичный код на фоне того, что мы написали для наших клиентов.
К слову, такая реализация уже как таковая даст вам приемлемые показатели ревью. Но лучше добавить ей контекст.
А какой контекст?
Давайте на пути к реализации представим, что у вас есть такие методы, и заодно обсудим, почему они важны.
Jira/GitHub/Gitlab Issues
Контекст и качество задачи напрямую повлияют на результат ревью. Начните с пересмотра процессов, если ваши задачи выливаются в запрос на слияние в 500 млн изменений на 150 тысяч файлов.
# docs: Содержание,тайтл и коментарии к задаче
def _get_issue_context(issue_code:str) -> str:
pass
Если задача была про синие кнопки, а на выходе получили оранжевые шрифты, у LLM будут вопросы. И это вполне себе правильные вопросы, но скорее процессуального характера.
В нашем случае получилось воспроизвести интересный кейс. Есть библиотека на фронте, в которой расположены иконки. Есть эпик, в котором сформированы задачи на обновление версии библиотеки в сервисах. Каждый ПР в таком случае выглядит одинаково – вы обновляете версию пакета, прописываете CHANGES и ждёте RFI хотя бы апрува.
Гефест приходит в 2 таких PR и ставит одному approve, а другому need work:


Вот в его ответах прослеживается зерно истины – я не могу поставить approve, т.к. не знаю, чего вы там поменяли в этой библиотеке. Нужны проверки QA.
В то же время в другой задаче комментарием к Jira issue было чётко указано, какая версия библиотеки нужна. Гефест говорит: мужик, ты всё сделал правильно – вот тебе approve.
Корпоративная база знаний
# docs: Документация для сервиса/репозитория
def _get_service_docs(docs_ids: list[str]) -> list[str]:
pass
Я не стану распыляться, лишь скажу – для проектов нужна документация. Чтобы понимать, что там вообще происходит и как оно было задумано создателем.
Самое главное – diff
Чем больше ваша задача, тем больше получается diff. А чем больше diff, тем больше контекстное окно необходимо. С разными инвестициями можно добиться разной длины контекста модели, но лучше начать с оптимизации процессов постановки задач. Чем меньше изменение, тем легче и лучше оно будет отсмотрено, протестировано и доставлено.
Есть 2 стула варианта реализации. Первый: классический .patch файл. Его можно сформировать с помощью git или использовать уже готовое API для этого:
-
Github: https://patch-diff.githubusercontent.com/raw/{projectKey}/{repositorySlug}/pull/123.patch
-
Bitbucket: https://your_host/api/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}.patch
Второй вариант, на котором мы остановились, – формировать diff в виде .md файлов. Мы написали свой парсер.
И тут, пожалуй, требуется пояснение.
Разные модели ведут себя по-разному с разным типом diff. Серьёзно, qwen3-coder сходит с ума с .patch файлом. gpt-oss:20b, кстати, ведёт себя довольно схожим образом.
Кроме того, важен не только формат, но и его компоновка. Сначала мы формировали каждый файл в отдельное сообщение. Удивительно, но даже при большом контекстном окне LLM умудрялись их терять. qwen3-coder вообще довольно плохо работает с сообщениями от пользователя, если на них ранее не было ответов.
Поэтому мы пошли таким путём: собираем весь diff в md. Пакуем в одно сообщение и отправляем.
# docs: Запрос diff
async def get_diff(project: str, repository_slug: str, pr_id: str) -> str:
pass
Codebase
from pathlib import Path
# docs: Запрос файлов для внедрения в контекст
async def get_code_context(project: str, repository_slug: str, pr_id: str) -> list[Path]:
pass
Это, пожалуй, самая сложная в реализации часть. Вам необходимо прогнать через эмбедеры кучу файлов, отобрать нужные для ревью и подложить их в контекст.
Вариантов на самом деле много:
-
Класть всю кодовую базу, формировать запрос на то, чтобы вытащить из каждого файла вектора и положить их в базу данных – в дальнейшем это поможет ориентироваться по их близости с вектором запроса.
-
Написать парсер импортов. Если у вас небольшой проект, язык программирования один или два, то какие проблемы. Самостоятельно прошлись по древу, выбрали важное и подложили контекст.
-
Сформировать source tree и попросить LLM выбрать необходимые для ревью файлы самостоятельно.
Мы остановились на первом и решили довериться эмбедингу и RAG. Путь довольно трудный. Предостерегу вас: необходимо будет продумать не только процесс векторизации, но и что делать с файлами по мере их «охлаждения». Это задача, которая может занять очень много времени и потребовать более глубокого понимания того, как работают нейросети. Однако, преодолев эти трудности, у вас получится шикарное Code Review.
Что дальше?
Каждый из источников информации может стать отдельной tool для LLM.
...
agent = Agent(model=model, deps_type=PRDeps)
# docs: Содержание, тайтл и коментарии к задаче
@agent.tool
def get_issue_context(ctx: RunContext[PRDeps]) -> str:
issue_code = ctx.deps.issue_code
issue_context = _get_issue_context(issue_code)
return issue_context
# docs: Документация для сервиса/репозитория
@agent.tool
def get_service_docs(ctx: RunContext[PRDeps]) -> str:
docs_ids = ctx.deps.docs_ids
documentation = _get_service_docs(docs_ids)
return documentation
# docs: Запрос diff
@agent.tool
def get_diff(ctx: RunContext[PRDeps]) -> str:
diff = _get_diff(
project=ctx.deps.project,
repository_slug=ctx.deps.repository_slug,
pr_id=ctx.deps.pr_id,
)
return diff
...
Я очень рекомендую к прочтению инструкцию pydantic по этому поводу и скажу, что pydantic AI оперирует дополнительным вариантом — instructions: это своеобразное расширение системного промпта.
Кроме того, что уже есть, вы можете настроить поведение tools так, что модель сможет сама ставить approve либо need work в зависимости от результатов ревью.
Retrieval Augmented Generation
Если вам не знакомо это понятие, то я рекомендую вам почитать статьи на Хабре, в которых это уже описано (например, тут и тут).
Pydantic AI, к слову, предоставляет функционал и для этого:
От себя скажу, что настроить эту штуку довольно сложно: это занимает наибольшее количество времени, однако, результат того полностью стоит. У LLM появляется контекст того, что вообще происходит в репозитории, она может давать индивидуальные советы с учётом вашего кода и даже иногда удивлять тем, откуда у неё вообще эта информация.
Мы воспользовались готовым решением для формирования эмбедингов и пожалели. Теперь, уже после внедрения технологии, нам необходимо переделывать на более правильный вариант. Это тяжело и дорого, поэтому подумайте об этом – потратьте время на этапе разработки.
Даже без этого шага качество ревью будет оставаться удовлетворительным. Это будет похоже на ревью разработчика, который недавно пришёл из другой компании и пытается навязать свои правила, однако, security и perf issues система всё же выявит.
В нашем конкретном случае введение RAG позволило улучшить качество проводимого ревью тем, что количество замечаний Гефеста, на которые реально хочется обратить внимание, выросло примерно до 7 из 10.
Как это выглядит?
Давайте из этой формулировки перейдём к следующему преимуществу системы.
AIaaS
При построении такого сервиса важно понимать, что это не просто proof of concept, а универсальный инструмент на вырост. И ставку вы делаете не только на развитие открытых моделей, а на развитие LLM как отрасли и инструмента.
Недавним примером того, что ставка выигрышная, стал релиз Opus 4.6 с контекстным окном в 1M токенов. Представьте, каких показателей получится достичь, если даже полностью открытая младшая модель Qwen3 Coder уже сегодня удовлетворительно проводит ревью. Разница между релизами, к слову, полгода.
Обобщая вышесказанное, вы создаёте обёртку, которая может работать на любом типе LLM-моделей. С увеличением качества последних ваш инструмент будет показывать лучшие результаты с минимальными затратами на обслуживание. Потрясающе?

Автор: DanilChechkov


