Как в Faire (мультибрендовая торговая площадка) внедрили автоматизиорванные Code Review c LLM (статья - мой перевод для нашего ТГ канала посвященного разработке софта при помощи LLM).
В Faire мы верим в ценность код-ревью и всегда придерживаемся этой практики. Хотя многие аспекты код-ревью требуют глубокого понимания проекта, существуют также множество общих требований, которые можно учесть без дополнительного контекста. Например, наличие ясного заголовка и описания, достаточное покрытие тестами, соблюдение стиля кода, выявление изменений несовместимых между сервисами. Похоже, что LLM хорошо подходят для выполнения таких общих задач код-ревью. Имея достаточно информации о pull request: метаданные, diff, логи сборки и отчеты о покрытии тестами, можно эффективно настроить LLM для добавления полезной информации, выявления багов или потенциально опасных изменений и даже автоматического исправления простых ошибок.
Мы подробно рассмотрим жизненный цикл ревью и подход RAG, разработанный в Faire для выполнения различных контекстно-специфических автоматизированных ревью. Мы также уделим внимание нашему ревью покрытия тестами, которое выявляет области с низким покрытием и предлагает дополнительные тесты, чтобы продемонстрировать гибкость и ценность автоматизации ревью.
Fairey — Оркестратор LLM
Мы разработали сервис LLM оркестратор, который назвали Fairey. Он отслеживает запросы от пользователей в чате и разбивает их на все необходимые шаги для получения ответа, включая вызов LLM, получение дополнительной информации, выполнение функций и дополнительную логику.
Fairey интегрирован с API OpenAI. Мы создали интерфейс для управления AI-ассистентами, где можно настраивать инструкции ассистента или добавлять функции, которые он может вызывать. Функции предоставляют ассистентам возможность получение дополнительной информации — метод, известный как RAG (Retrieval Augmented Generation).
RAG быстро становится индустриальным стандартом для предоставления необходимой информации LLM, чтобы выполнять контекстно-специфические задачи. LLM обучены на очень широком наборе данных, но обычно не на данных вашей компании. И даже если вы начали тонкую настройку моделей с открытым исходным кодом, как это делает Faire, LLM все равно будет нуждаться в специфической для случая информации — в нашем случае, это информация об изменениях в коде, которые рассматриваются в ревью.
Каждый вызов функции связан с обратным вызовом (callback) в оркестрационном сервисе, который активируется, когда GPT решает, что это нужно. Благодаря этому Fairey может получать нужные данные по мере необходимости, вместо того чтобы сразу загружать весь объем информации в главный запрос.
Жизненный цикл ревью
Fairey также интегрирован с вебхуками GitHub, которые сообщают о событии, как только что-то интересное происходит с pull request. Мы реагируем на каждый вебхук GitHub различными способами, включая проверку можно ли запустить автоматическое ревью для этого pull request. Если pull request соответствует критериям (например, язык программирования или конкретное содержимое diff), Fairey обращается к OpenAI для выполнения ревью.
После взаимодействия с OpenAI мы анализируем результаты, чтобы оценить их полезность. Если полезная информация есть, мы добавляем ревью в pull request. Ревью обычно включают в себя комментарии, советы, и даже предложения по изменениям в коде.
Чтобы избежать дублирования каждое ревью хранит в себе метаданные, показывающие, что уже было покрыто. Это также позволяет продолжать обсуждение предыдущих веток или использовать результаты прошедших ревью в качестве входных данных для последующих.
Ревью покрытия тестами
Покрытие тестами показывает все ли ветки кода выполняются при запуске тестового набора. Для простоты представим себе pull request, который реализует новую функцию, защищенную настройкой из нашего Settings Framework. Код может выглядеть следующим образом:
const isSettingEnabled = getSettingValue();
if (myNewSetting) {
doTheNewThing();
} else {
doTheOldThing();
}
Покрытие тестами покажет нам охватывают ли наши тесты ветки кода, где настройка включена и не включена. А также сообщит если мы вообще не добавили тесты для нового кода!
Мы используем Jest для unit testов frontend кода, минимальное покрытие тестами является обязательным. При создании pull request CI система отберет тесты с флагом --changedSince, заставляя Jest выполнять только те тесты, в которых есть зависимости с файлами, включенными в pull request. Мы также используем флаг --coverage, который заставляет Jest собирать и выводить отчет о покрытии, который затем можно просмотреть на нашем внутреннем портале разработки (см. ниже для примера).
Ревью покрытия тестами запускается, если мы получаем вебхук от GitHub о том, что выполнение тестов на покрытие завершилось с неудачей. Это означает, что покрытие в pull request ниже нашего требуемого порога покрытия. Обычно это происходит, потому что разработчик добавил новые файлы без соответствующих unit test, или создал новые ветки в коде, которые не охвачены существующими тестами.
Ревью покрытия тестами использует кастомного ассистента (как видно на скриншоте выше). Он имеет доступ к нескольким важным функциям:
fetch_github_diff, захватывает diff изменений в pull request.
fetch_github_pull_request, получает метаданные pull request, такие как заголовок, описание и т.д.
fetch_code_coverage_report, загружает lcov отчет из артефактов сборки нашей CI.
fetch_github_file_contents, загружает полное содержимое указанного файла.
Системный промпт такого ассистента выглядит примерно так:
You are an expert React and Typescript programmer.
Using the test coverage reports and contents of source code and corresponding
test files, suggest new test cases that would increase coverage of the source
code.
Test files in our frontend codebases are located in a `./__tests__/` folder
next to the source code, with `*.test.ts` suffix instead of `*.ts`. A couple
of examples,
```yaml
- source: `src/something.ts`
tests: `src/__tests__/something.test.ts`.
- source: `app/home/utils.ts`
tests: `app/home/__tests__/utils.test.ts`
```
Мы собираем отчеты о покрытии кода с помощью lcov для каждого pull request. Ревью покрытия тестами настроено таким образом, чтобы использовать эти отчеты для определения областей кода с низким покрытием. В дополнение системным инструкциям ассистента выше, ревью также имеет шаблон промпта:
You are performing a code review of ${diffSource}. The PR has failing test coverage checks for ${failingCoverage{.Use the diff and the code coverage report to identify the changes in the PR that are not covered by tests.
For those changes, suggest test cases that should be added to the code.
Use the existing test file as a reference when suggesting test cases. Do not suggest
Когда пользователь создает pull request, система сборки сообщает о неудачном выполнении проверки покрытия тестами. Это запускает процесс, в котором Fairey инициирует чат с ассистентом, используя приведенный выше шаблон запроса. Для формирования ответа ChatGPT запрашивает выполнение функций, которые Fairey исполняет и возвращает результаты обратно в ChatGPT, и в конечном итоге создается ответ.
Затем мы создаем ревью в Github, используя этот ответ, и размещаем его в pull request.
Оценка результатов ревью
LLM невероятно гибки в своих входных данных, но также сильно варьируются по качеству выходных данных. GPT могут выдавать ложные результаты (так называемые «галлюцинации»), а также создавать чрезмерно многословные или не относящиеся к делу ответы. Мы оцениваем качество ревью с помощью двух параметров: количественного и качественного. Количественная оценка проводится с использованием Фреймворка для оценки LLM, а качественная оценка - опрос конечных пользователей (наших инженеров).
Для количественной оценки мы создаем набор тестовых сценариев, проводим ревью и снова запускаем их, рассчитывая оценку точности при помощи таких инструментов как Gentrace, CometLLM или Langsmith. Когда мы проводим ревью, мы отправляем входные и выходные данные в эти инструменты. Каждую такую пару можно использовать для создания тестового случая. Затем, когда мы вносим изменения в ревью (например, корректируем запрос, меняем модель, получаем другие данные и т.д.), мы можем снова пропустить ревью через все тестовые сценарии. После этого мы используем LLM для оценки качества результата.
Верно — результаты ревью, созданного с помощью LLM, оцениваются с использованием... LLM!
Что касается качественной оценки, мы используем платформу DX для измерения и повышения производительности разработчиков. DX получает уведомление каждый раз, когда выполняется ревью, и запрашивает отзывы у автора pull request. Эти отзывы, будь то положительные или отрицательные, затем передаются в Gentrace, где они могут использоваться как фильтры для выбора лучших тестовых случаев.
Fixtures
Результаты pull request ревью очень временное явление, информация, связанная с ревью, может измениться к тому моменту, когда инженер соберется внести правки и пересмотреть его.
Для этого в Fairey есть функция "fixtures". Она сохраняет результаты выполнения функций как snapshots, которые можно использовать позже. Мы делаем это, используя историю чата OpenAI, сохраняя результаты вызовов функций в специальные файлы fixtures и загружая их в хранилище. Когда ревью запускается снова, эти файлы считываются и передаются в ChatGPT как результаты вызовов функций, вместо того чтобы выполнять их заново.
Ранние результаты автоматизированных ревью
В Faire мы стремимся к максимальной продуктивности. Автоматизация ревью с помощью LLM помогает в этом за счет сокращения трудозатрат на простые рутинные задачи и возможности сосредоточиться на самых значимых и сложных частях ревью — таких как правильное выполнение требований к продукту, стройность архитектурных решений, долгосрочная поддерживаемость и переиспользование кода.
На данный момент мы достигли успеха, измеряемого положительным отзывом пользователей и высокой точностью автоматизированных ревью, которые:
Обеспечивают соблюдение стиля кода
Оценивают качество заголовков и описаний
Диагностируют сбои сборки, включая предложения автоматических исправлений
Предлагают дополнительные тесты для покрытия
Выявляют несовместимые изменения
Все еще есть куда расти — с каждым новым типом ревью качество результата сначала сильно варьируется. При работе с LLM некорректные или запутанные входные данные приводят к непредсказуемым результатам. Чтобы добиться трёх «C» — последовательности (consistent), краткости (concise) и правильности (correct), требуется тщательно прорабатывать содержание и структуру входных данных, использовать широкий набор тестовых сценариев и более сложные техники, такие как самооценка (self-eval) и Chain-of-Thought (цепочка рассуждений). Итерационный подход позволяет инженерам улучшать запросы и соответственно возможности модели.
Использование LLM при правильной настройке может предоставить инженерам новый уровень помощи в быстром предоставлении качественных продуктов нашим пользователям. С нетерпением ожидаем дальнейших шагов в развитии этой технологии вместе с Generative AI.
Наш ТГ канал, в котором мы пишем о том как разработчики, аналитики, тестировщики, менеджеры уже могут применять LLM и вообще AI в процессе создания программного обсепеченния и обсуждаем планы на будущее.