Параллельные агенты LangGraph, которые анализируют pull request через webhook FastAPI.
Как webhook, очередь Redis и четыре агента LangGraph превращают pull request в оценённый, структурированный анализ с использованием ИИ примерно за 30 секунд, при этом ngrok используется для локальных тестов.
Источники: Building a Multi-Agent PR Code Review System With LangGraph, FastAPI, Webhooks, and Docker
Каждый, кто создавал pull request, знает, что это за ожидание. Функция готова, код загружен, но потом проходят два часа, целый день или даже больше. Когда коллега наконец рассматривает его, там уже дюжина комментариев, и половина из них касается проблем, которые программное обеспечение могло бы само выявить: жестко заданный ключ API, функция с именем x, SQL-запрос, уязвимый к инъекциям. Автор все исправляет, снова загружает код и снова ждет.
Сам процесс ревью кода крайне важен, поэтому жалоб нет. Раздражает то, что большинство первичных замечаний носят механический характер — одни и те же категории проблем в каждом PR. Идея этой системы заключается в том, чтобы ИИ сразу выявлял все очевидные ошибки, чтобы люди-рецензенты могли сосредоточиться на архитектуре и логике, где действительно нужен человеческий осмотр. Это работает лучше, чем можно было ожидать.
Почему несколько агентов лучше одного
Примитивный подход заключается в отправке различий между версиями кода на обработку модели с указанием проверить их. Это подходит для небольших изменений. Однако по мере увеличения объема изменений модель, которой необходимо одновременно учитывать аспекты безопасности, производительности и качества кода, теряет концентрацию, в результате чего качество анализа снижается.
Специализация решает эту проблему. Каждому агенту поручается одна задача: один занимается исключительно безопасностью, другой — производительностью, третий — качеством кода, а четвертый объединяет результаты трех анализов и составляет четкий, профессиональный отчет. Такая модель с несколькими агентами дает значительно лучшие результаты, чем один универсальный агент.
Общая структура системы предусматривает, что каждый компонент несет ответственность за одну задачу:
You open a Pull Request on GitHub
│
▼
GitHub fires a webhook
│
▼
FastAPI receives it instantly
└── Verifies HMAC signature
└── Pushes job to Redis queue
└── Returns 202 to GitHub ✅
│
▼
ARQ Worker picks up the job
│
▼
LangGraph Pipeline starts
│
┌─────────┼─────────┐
▼ ▼ ▼
Agent 1 Agent 2 Agent 3
Quality Security Performance
│ │ │
└─────────┼─────────┘
▼
Agent 4
Final Reviewer
│
▼
Comment posted on your PR ✅
(in under 30 seconds)
Шаг 1: webhook GitHub
Всё начинается с webhook. Когда открывается или обновляется pull request, GitHub отправляет запрос HTTP POST на указанный вами URL; в этом запросе содержатся номер PR, его название, автор, репозиторий и действие, которое его запустило. Сервер FastAPI прослушивает этот URL.
Первым действием сервера при получении любого запроса является проверка подписи. GitHub подписывает каждый пакет данных с помощью хэша HMAC-SHA256 с использованием секретного ключа, заданного вами; несоответствие подписи означает немедленное отклонение запроса, поэтому никто не может отправлять фальшивые события PR на конечную точку.
Следующее решение имеет решающее значение: сервер не начинает процесс анализа с помощью ИИ сразу. Он помещает задачу в очередь Redis и отвечает GitHub кодом 202 Accepted менее чем за секунду. Причина в том, что GitHub ожидает ответ от webhook в течение 10 секунд, тогда как полный анализ с тремя одновременными вызовами LLM занимает от 25 до 30 секунд. Проведение анализа в реальном времени заставило бы GitHub считать, что сервер сбился, и попытаться снова, в результате чего PR будет анализироваться несколько раз с дублирующимися комментариями. Очередь задач полностью решает эту проблему: GitHub получает быстрый ответ, а фактическая обработка происходит на фоне.
Шаг 2: ngrok для локальной разработки
Во время локальной разработки сервер FastAPI работает по адресу localhost:8000, к которому GitHub не может получить доступ из Интернета. ngrok устраняет эту проблему, создавая защищенный туннель между публичным URL и локальным компьютером. Для его запуска достаточно одной команды:
ngrok http 8000
Он немедленно возвращает публичный https-адрес, например:
https://abc123.ngrok-free.app
Вставьте этот адрес в настройки webhook GitHub, и GitHub сможет напрямую отправлять события на ноутбук. Каждый раз, когда открывается PR, запрос проходит от серверов GitHub через серверы ngrok к локальному приложению FastAPI. В терминале весь обмен отображается в виде одной строки:
23:03:02 POST /api/webhook 202 Accepted ✅
Одна строка, вторая под ней — и GitHub доволен, пока на самом деле начинается работа в фоновом режиме. Одно предупреждение: бесплатный тариф ngrok генерирует новый URL при каждой перезагрузке, поэтому URL webhook GitHub необходимо обновлять каждый раз.
Шаг 3: пайплайн LangGraph
Это ядро системы. LangGraph — это фреймворк для рабочих процессов с состоянием и несколькими агентами. Агенты становятся узлами в графе, рёбра соединяют их, а общее состояние передаётся между ними. Представьте схему, в которой каждый квадрат — это ИИ-агент, а стрелки показывают, какие данные перемещаются куда и в каком порядке.
Общее состояние
Все агенты читают и записывают в один общий объект состояния, содержащий номер петиции, детали репозитория, полученные различия с фактическими изменениями в коде, результаты проверки каждого агента и окончательный синтезированный комментарий. Этот объект передается по графу, как эстафетная палочка в эстафете: каждый агент добавляет свою часть информации перед тем, как передать её дальше.
Узел 0: получение данных петиции
Специальный узел запускается до любого агента и запрашивает в API GitHub название петиции, автора и ветку, а также полные различия для каждого изменённого файла. Все эти данные преобразуются в один аккуратный текстовый строковый формат, понятный нейросетям, а тот же форматированный разделитель передаётся каждому из рецензентов.
Параллельная обработка: агенты 1, 2 и 3
После получения данных граф распределяет задачи сразу трём агентам. Они получают одинаковые данные, работают одновременно и не знают ничего друг о друге.
Агент по качеству кода существует полностью в своем собственном мире. Его системное указание охватывает читаемость, правила номенклатуры, нарушения принципа DRY, обработку ошибок и возможности технического обслуживания; он возвращает структурированный JSON с оценкой от 0 до 10, а также списком конкретных проблем, для каждой из которых указаны имя файла, номер строки и предложенное решение.
Агент по безопасности ищет только уязвимости: внедрение SQL-запросов через необработанные данные, жестко закодированные ключи, пароли или токены, отсутствие проверок аутентификации и наличие конфиденциальных данных в логах. Он ничего не знает о производительности или стиле кода.
Агент по производительности думает только об эффективности: шаблоны запросов к базе данных типа N+1, вложенные циклы сложности O(n²) или худшей, блокирующие вызовы ввода-вывода внутри асинхронных функций, а также отсутствие индексов или возможностей кэширования.
Параллелизм имеет значение для времени выполнения. Если выполнять задачи последовательно, трем агентам потребуется в общей сложности от 60 до 90 секунд. Если выполнять их одновременно, они закончат работу примерно за 7–12 секунд, что соответствует времени работы самого медленного агента, а не сумме времени всех трех.
Количество подключаемых агентов: 4
Финальный рецензент автоматически ждет, пока все три специалиста не закончат работу. LangGraph обеспечивает такую синхронизацию, поэтому не требуется писать логику ожидания или запрашивания данных. Как только три отчета поступают в формате структурированного JSON, агент 4 выполняет одну операцию — записывает финальный комментарий рецензии в формате Markdown.
Агент 4 не выполняет никакого анализа. Он никогда не пересматривает код, а лишь объединяет и форматирует информацию, предоставленную специалистами. Это сделано намеренно: даже если формулировки немного меняются, основные выводы остаются точно такими же, какие сделали специалисты. Его комментарий начинается с общего вердикта и оценки, являющейся средним значением оценок трех агентов, затем приводится краткое описание того, что обнаружил каждый агент, и в конце — список проблем, требующих устранения перед слиянием, ограниченный критическими и высокоприоритетными проблемами.
Ловушка в режиме параллельной работы
Тонкая техническая деталь легко ускользает от внимания. Как только параллельные агенты завершат работу и записывают изменения в общее состояние, каждый из них должен вернуть только тот ключ, который у него есть. Если каждый агент будет возвращать всё объект состояния, LangGraph получит три конфликтующих копии каждого общего поля одновременно и выдаст ошибку InvalidUpdateError. Решение простое, но неочевидное: к полям, в которые пишут параллельные агенты, нужно привязать редьюсер типа Annotated, чтобы каждый агент возвращал только свой собственный результат. Об этом узнаёшь, столкнувшись с ошибкой, при этом большинство учебных материалов по LangGraph её не упоминают.
Шаг 4: результат
Доказательство — в тесте. Файл на Python с четырьмя специально созданными задачами, жестко закодированной тайной, уязвимостью к инъекциям SQL, вложенным циклом сложности O(n²) и плохими именами переменных был загружен в ветку и представлен как pull request. Оценка по этому PR появилась через 27 секунд. Вот тот код, который был внесен:
import sqlite3
# Hardcoded secret — security agent should catch this
API_KEY = "sk-prod-supersecretkey123"
def get_user(user_id):
conn = sqlite3.connect("users.db")
# SQL injection — security agent should catch this
query = f"SELECT * FROM users WHERE id = {user_id}"
return conn.execute(query)
def process(users):
# O(n²) nested loop — performance agent should catch this
for user in users:
for item in users:
print(user, item)
# Bad naming — quality agent should catch this
def x(a, b, c):
return a + b + c
И вот что система опубликовала в PR:
## 🤖 AI Code Review
**Overall Verdict:** ❌ REJECTED
**Score:** 2.33/10
---
### 📋 Code Quality
Score: 2/10 | 8 Issues Found
Function naming is completely unclear — x, a, b, c give no
indication of purpose. No error handling on database operations.
No docstrings or type hints on any function.
### 🔒 Security
Score: 2/10 | 4 Critical Vulnerabilities
Hardcoded API key on line 3 exposes credentials in version
control — CRITICAL. SQL query on line 9 directly interpolates
user input, creating a SQL injection vulnerability — CRITICAL.
### ⚡ Performance
Score: 3/10 | 4 Issues Found
Nested loop in process() creates O(n²) complexity — will become
extremely slow as the users list grows. Database connection is
opened but never closed, creating a connection leak.
---
### 🚨 Must Fix Before Merge
- Remove API_KEY hardcoding — use environment variables instead
- Parameterize the SQL query to prevent injection attacks
- Refactor nested loop — use set operations or single pass
- Close database connections properly or use a context manager
---
Great effort on the overall structure! Addressing these critical
issues will significantly improve the security and reliability
of this code. Looking forward to the revised version. 🙌
Всего через 27 секунд после создания петиции по ревью были выявлены четыре конкретные категории проблем с указанием соответствующих строк и предложениями по их устранению. В процессе не участвовали люди, не требовалось ожидания — сразу поступил структурированный и практически применимый отзыв.
Запуск локально
Всё начинается с одной команды Docker: задаются переменные окружения, создается токен GitHub и настраивается webhook, ngrok подключает локальный сервер к GitHub, а тестовая петиция демонстрирует мгновенное появление ревью. Для переключения между Claude и OpenAI достаточно изменить одну переменную окружения, а шаги настройки описаны в файле README репозитория.
Уроки, извлеченные в процессе
Специализированные агенты превосходят универсальные. Одна задача на агента позволяет получать более точные и целенаправленные результаты, поскольку агент безопасности не отвлекается на стиль кода, а агент производительности не тратит токены на имена переменных.
Очередь задач является частью архитектуры, а не опцией. 10-секундный таймаут GitHub очень строг, и без Redis вместе с фоновым работником неудачные попытки выполнения задач повторяются, что приводит к накоплению дублирующихся обзоров. Многие учебные материалы пропускают этот аспект, однако именно он делает систему надежной.
Параллельная обработка меняет расчеты. Три последовательные вызова по 10 секунд каждый требуют как минимум 30 секунд, тогда как три параллельных — всего 10. LangGraph значительно упрощает реализацию параллелизма, и разница огромна.
Структурированный вывод делает агентов готовыми к использованию в производстве. Каждый агент возвращает данные в строгом формате JSON, и при неудачной обработке задача корректно сбрасывается, и ничего не отправляется на GitHub. Система либо предоставляет качественный обзор, либо ничего не выдает, но никогда не генерирует бесполезный контент.
С самого начала используйте абстракцию LLM. Шаблон фабрики, позволяющий переключаться между Claude и OpenAI с помощью одной переменной окружения, требует примерно десяти минут на создание и экономит часы времени на последующей рефакторке, поскольку модели можно тестировать и свободно сравнивать качество их работы.
Как это влияет на ревью кода
Ревью кода не исчезнет, а для принятия архитектурных решений, работы с бизнес-логикой и сложными дизайнерскими компромиссами ничто не заменит человека. С другой стороны, механический первичный анализ — это другая история: жестко закодированные секреты, инъекции SQL, квадратичные циклы и неоднозначные названия можно автоматизировать, что освобождает людей, проверяющих код, для работы, требующей человеческого интеллекта. Эта система выявляет очевидные проблемы примерно за 30 секунд, чтобы коллеги могли сосредоточиться на более важных.