Головна / Статті

Паралельні агенти LangGraph, які аналізують pull request через webhookи FastAPI.

Як webhook, черга Redis та чотири агента LangGraph перетворюють pull request на оцінений, структурований аналіз від ШІ приблизно за 30 секунд, з використанням ngrok для локальних тестів.

Видавець: REACTAPP.TOP12 хв читання2837 слів

Джерела: Building a Multi-Agent PR Code Review System With LangGraph, FastAPI, Webhooks, and Docker

Кожен, хто створював pull request, знає це очікування. Функція готова, код опублікований, а потім йдуть дві години, цілий день чи навіть більше. Коли колега нарешті її переглядає, там є десятки коментарів, і половина з них — це проблеми, які програмне забезпечення могло б виявити саме: жорстко закодований ключ API, функція під назвою x, SQL-запит, схильний до втручання ззовні. Автор все виправляє, знову опубліковує код та знову чекає.

Сам перегляд коду є необхідним, тож це не причина скарг. Розчарування викликає те, що більшість коментарів після першого огляду є механічними, і кожен PR містить однакові категорії проблем. Ідея цієї системи полягає у тому, щоб ШІ негайно виявляло всі очевидні проблеми, щоб людські рецензенти могли присвятити свій час архітектурі та логіці, де справді потрібен людський осуд. Це працює краще, ніж можна було очікувати.

Чому кілька агентів кращі за одного

Примітивний підхід надсилає різницю версій PR до моделі з інструкцією її переглянути. Це підходить для невеликих змін. Однак коли 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: GitHub webhook

Усе починається з 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

Вставте цю адресу у налаштування GitHub webhook, і GitHub зможе надсилати події безпосередньо на ноутбук. Кожного разу, коли відкривається PR, запит проходить від серверів GitHub через сервери ngrok до локального додатку FastAPI. У терміналі весь обмін відображається як один рядок:

23:03:02 POST /api/webhook    202 Accepted  ✅

Одна рядок, ще один – і GitHub задоволений, тоді як справжня робота починається на тлі. Одна застереження: безкоштовний тариф ngrok генерує нову URL після кожного перезапуску, тому URL webhook GitHub потрібно оновлювати щоразу.

Крок 3: конвеєр LangGraph

Це ядро системи. LangGraph – це фреймворк для роботи зі станом та багатьма агентами. Агенти стають вузлами в графі, ребра їх з’єднують, а спільний стан передається між ними. Уявіть собі діаграму потоків, де кожен блок – це ШІ-агент, а стрілки показують, які дані переміщуються куди та в якому порядку.

Спільний стан

Усі агенти читають та записують у один спільний об’єкт стану, який містить номер PR та деталі репозиторію, отриманий диференціал із фактичними змінами в коді, результати перевірки кожного агента та остаточний синтезований коментар. Інформація передається по графу, як естафетна паличка у естафеті: кожен агент додає свою частину перед тим, як передати її далі.

Вузол 0: отримання PR

Спеціалізований вузол працює перед будь-яким агентом та запитує API GitHub щодо назви PR, автора та гілки, а також повного диференціалу кожного зміненого файлу. Він перетворює все це на один охайний текстовий рядок, який можуть прочитати ШІ, а такий самий форматований диференціал надсилається кожному з перевіряючих.

Паралельне розгалуження: агенти 1, 2 та 3

Після отримання даних граф розгалужується одночасно на три агенти. Вони отримують однаковий диференціал, працюють у той самий момент та нічого не знають один про одного.

Агент якості коду існує виключно у своєму власному світі. Його системний запит стосується читабельності, правил найменування, порушень принципу DRY, обробки помилок та підтримки коду, і він повертає структурований JSON з оцінкою від 0 до 10 та списком конкретних проблем, кожна з яких містить назву файлу, номер рядка та пропозицію щодо виправлення.

Агент безпеки шукає лише вразливості: вставку SQL-запитів з незахищених даних, жорстко закодовані ключі, паролі чи токени, відсутність перевірок автентифікації та конфіденційні дані в журналах. Він нічого не знає про продуктивність чи стиль коду.

Агент продуктивності думає лише про ефективність: шаблони запитів до бази даних типу N+1, вкладені цикли складності O(n²) або гірше, блокуючі виклики IO всередині асинхронних функцій, а також відсутність індексів чи можливостей кешування.

Паралелізм має значення для часу виконання. Якщо працювати послідовно, трьом агентам знадобиться в сумі 60–90 секунд. Якщо працювати одночасно, вони завершують роботу приблизно за 7–12 секунд — це час найповільнішого агента, а не сума часу всіх трьох.

Kількість підключених агентів: 4

Остаточний рецензент автоматично чекає, поки всі три спеціалісти не завершать роботу. LangGraph забезпечує цю синхронізацію, тому не потрібно писати логіку очікування чи запиту. Коли три звіти надходять у форматі структурованого JSON, агент 4 виконує одну дію — записує остаточний коментар рецензії у форматі Markdown.

Агент 4 не виконує жодного аналізу. Він ніколи не повертається до коду та лише об’єднує та форматує інформацію, надану спеціалістами. Це робиться навмисно: навіть якщо формулювання трохи змінюються, результати залишаються точно такими, які отримали спеціалісти. Його коментар починається з загального висновку та оцінки, яка є середнім значенням оцінок трьох агентів, потім узагальнює те, що знайшов кожен агент, і закінчується списком проблем, які необхідно виправити перед злиттям, обмеженим критичними та проблемами високої серйозності.

Підводна камінь у паралельному стані

Тонкий технічний нюанс легко пропустити. Як тільки паралельні агенти завершують роботу та зберігають зміни у спільному стані, кожен з них має повернути лише ключ, який належить йому. Якщо кожен агент повертатиме весь об’єкт стану, LangGraph одночасно отримає три суперечливі копії кожного спільного поля та викине помилку InvalidUpdateError. Рішення просте, але неочевидне: до полів, у які записують паралельні агенти, слід додати редуктор типу Annotated, щоб кожен агент повертав лише власний результат. Це можна дізнатися лише шляхом зіткнення з цією помилкою, а більшість навчальних матеріалів LangGraph про це не згадують.

Крок 4: результат

Доказ — у тесті. Файл на Python, який містив чотири навмисно створені проблеми, жорстко закодовану таємницю, вразливість до SQL-ін’єкцій, вкладену циклізацію типу O(n²) та погану номенклатуру, був завантажений у відгалуження та оприлюднений як запит на з’єднання. Огляд цього запиту з’явився через 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, а тестовий PR демонструє миттєвий перегляд результатів. Для перемикання між Claude та OpenAI достатньо змінити одну змінну середовища, а кроки налаштування описані у файлі README репозиторію.

Уроки, отримані під час розробки

Спеціалізовані агенти показують кращі результати, ніж універсальні. Виконання однієї задачі кожним агентом дає більш точні та цілеспрямовані результати, адже агент з питань безпеки не відволікається на стиль коду, а агент з питань продуктивності не витрачає токени на назви змінних.

Очерга завдань є частиною архітектури, а не додатковим опцією. 10-секундний таймаут GitHub є дуже суворим, і без Redis та фонового працівника невдалі спроби виконання завдань повторюються, що призводить до накопичення дублікатів оглядів. Багато навчальних матеріалів ігнорують цей аспект, проте саме він робить систему надійною.

Паралельне виконання змінює ситуацію. Три послідовні виклики по 10 секунд кожен потребують щонайменше 30 секунд, тоді як три паралельні — лише 10. LangGraph значно спрощує реалізацію паралелізму, і різниця є значною.

Структурований вихід — це те, що робить агентів готовими до використання у продакшені. Кожен агент повертає дані за суворою схемою JSON, і якщо парсинг зазнає невдачі, завдання без проблем відхиляється, і нічого не надсилається на GitHub. Система або повертає якісний огляд, або нічого, але ніколи не генерує беззмістовний вихід.

Абстрагуйте LLM з першого дня. Шаблон фабрики, який дозволяє перемикатися між Claude та OpenAI за допомогою однієї змінної середовища, створюється приблизно за десять хвилин та економить години на подальшому рефакторингу, оскільки моделі можна випробовувати та порівнювати якість їхнього аналізу без обмежень.

Як це впливає на перегляд коду

Перегляд коду не зникне, а для прийняття архітектурних рішень, бізнес-логіки та складних дизайнерських компромісів ніщо не замінить людину. Однак механічний перший аналіз — це інша справа: жорстко закодовані секрети, SQL-атаки, квадратичні цикли та нечіткі назви можна автоматизувати, що звільняє людей-рецензентів для роботи, яка вимагає людського інтелекту. Ця система виявляє очевидні проблеми приблизно за 30 секунд, щоб колеги могли зосередитися на важливіших.