Accueil / Articles

Agents LangGraph parallèles qui évaluent les demandes de pull via des webhooks FastAPI.

Comment un webhook, une file Redis et quatre agents LangGraph transforment une demande de pull en une évaluation structurée par l’IA en environ 30 secondes, avec ngrok pour les tests locaux.

Éditeur : REACTAPP.TOP12 min de lecture2837 mots

Sources: Building a Multi-Agent PR Code Review System With LangGraph, FastAPI, Webhooks, and Docker

Tout ceux qui ont déjà ouvert une demande de fusion connaissent cette attente interminable. Une fonctionnalité est terminée, le code est mis en ligne, puis s’écoulent deux heures, parfois toute une journée, voire plus. Lorsqu’un collègue finit par la examiner, il y a une dizaine de commentaires, dont la moitié concerne des erreurs que le logiciel aurait pu détecter : une clé API enregistrée de manière fixe, une fonction nommée x, une requête SQL vulnérable aux injections. L’auteur corrige tout, met à nouveau à jour le code et attend à nouveau.

La revue de code est essentielle en soi, ce n’est donc pas ce qui est critiqué. Ce qui est frustrant, c’est que tant de retours lors de la première lecture sont mécaniques, avec toujours les mêmes types d’erreurs dans chaque demande de fusion. L’idée derrière ce système est de permettre à une intelligence artificielle de détecter immédiatement tous les problèmes évidents, afin que les reviewers humains puissent consacrer leur temps à l’architecture et à la logique, où le jugement humain est vraiment nécessaire. Cela fonctionne mieux que ce à quoi on pourrait s’attendre.

Pourquoi plusieurs agents valent mieux qu’un seul

La conception naïve envoie la différence de modification vers un modèle avec pour instruction de l’examiner. Cela convient pour de petites modifications. Lorsque les modifications deviennent plus importantes, un modèle chargé d’examiner en même temps la sécurité, les performances et la qualité du code perd son focus, ce qui entraîne une dégradation des résultats.

La spécialisation résout ce problème. On attribue à chaque agent une seule tâche : l’un se concentre uniquement sur la sécurité, un autre uniquement sur les performances, un troisième uniquement sur la qualité du code, et un quatrième prend les trois rapports pour rédiger une analyse claire et professionnelle. Ce modèle à plusieurs agents donne des résultats nettement meilleurs qu’un agent généraliste.

La conception globale, où chaque composant a une responsabilité bien définie :

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)

Étape 1 : le webhook GitHub

Tout commence par un webhook. Lorsqu’une demande de fusion est ouverte ou mise à jour, GitHub envoie une requête HTTP POST vers l’URL que vous avez configurée ; cette requête contient le numéro de la demande, son titre, l’auteur, le répertoire ainsi que l’action qui l’a déclenchée. Un serveur FastAPI écoute sur cette URL.

La première action du serveur face à toute requête est de vérifier la signature. GitHub signe chaque envoi avec un hash HMAC-SHA256 utilisant un secret que vous définissez, et toute incohérence entraîne le rejet immédiat de la requête, ce qui empêche quiconque d’envoyer des événements de demande de fusion falsifiés vers l’endpoint.

La décision suivante est cruciale : le serveur ne commence pas immédiatement l’examen par l’IA. Il envoie une tâche dans une file Redis et répond à GitHub avec 202 Accepted en moins d’une seconde. La raison en est que GitHub attend une réponse via webhook dans les 10 secondes, tandis qu’un examen complet impliquant trois appels simultanés à des LLM prend de 25 à 30 secondes. Un examen en temps réel ferait croire à GitHub que le serveur a échoué et il tenterait à nouveau, ce qui entraînerait plusieurs examens du PR avec des commentaires redondants. Une file de tâches résout ce problème directement : GitHub reçoit sa réponse rapide et le travail réel s’effectue en arrière-plan.

Étape 2 : ngrok pour le développement local

Pendant le développement local, le serveur FastAPI tourne sur localhost:8000, que GitHub ne peut pas atteindre depuis Internet. ngrok comble cette lacune en créant un tunnel sécurisé entre une URL publique et la machine locale. Une seule commande suffit pour le démarrer :

ngrok http 8000

Il renvoie immédiatement une adresse HTTPS publique telle que :

https://abc123.ngrok-free.app

Collez cette adresse dans les paramètres du webhook de GitHub, et GitHub pourra envoyer les événements directement sur l’ordinateur portable. Chaque fois qu’une PR est ouverte, la demande passe des serveurs de GitHub via les serveurs ngrok jusqu’à l’application FastAPI locale. Dans le terminal, tout cet échange apparaît sous forme d’une seule ligne :

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

Une ligne, puis une seconde, et GitHub est satisfait tandis que le vrai travail commence en coulisses. Une précaution : la version gratuite de ngrok génère une nouvelle URL à chaque redémarrage, il faut donc mettre à jour l’URL du webhook GitHub à chaque fois.

Étape 3 : le pipeline LangGraph

C’est le cœur du système. LangGraph est un framework pour des workflows multi-agents à état. Les agents deviennent des nœuds dans un graphe, les arêtes les relient et l’état partagé circule entre eux. Imaginez un organigramme où chaque case représente un agent d’IA et les flèches indiquent quels données se déplacent où et dans quel ordre.

État partagé

Tous les agents lisent et écrivent dans un objet d’état partagé qui contient le numéro de la PR et les détails du répertoire, la différence obtenue avec les modifications réelles du code, le résultat de l’examen de chaque agent ainsi que le commentaire synthétisé final. Cet objet circule dans le graphe comme un témoin lors d’un relais, chaque agent y ajoutant sa contribution avant de la transmettre au suivant.

Nœud 0 : récupérer la PR

Un nœud dédié s’exécute avant tout agent et interroge l’API GitHub pour obtenir le titre de la PR, son auteur et sa branche, ainsi que la différence complète de chaque fichier modifié. Il convertit tout cela en une chaîne de texte bien structurée que les LLM peuvent lire, et cette même différence formatée est transmise à chacun des examinateurs.

L’étalement parallèle : agents 1, 2 et 3

Après la récupération des données, le graphe se divise en trois agents simultanément. Ils reçoivent la même différence, s’exécutent au même moment et ne savent rien les uns des autres.

L’agent de qualité du code évolue entièrement dans son propre monde. Son prompt système couvre la lisibilité, les conventions de nommage, les violations du principe DRY, le traitement des erreurs et la maintenabilité, et il renvoie un JSON structuré avec une note sur 10 ainsi qu’une liste des problèmes spécifiques, chacun accompagné du nom du fichier, du numéro de ligne et d’une correction suggérée.

L’agent de sécurité ne cherche que des vulnérabilités : injection SQL provenant d’entrées non sanitisées, clés, mots de passe ou tokens enregistrés en dur, absence de vérifications d’authentification, et données sensibles dans les journaux. Il ne connaît rien de la performance ou du style.

L’agent de performance ne pense qu’à l’efficacité : schémas de requêtes de base de données N+1, boucles imbriquées d’une complexité O(n²) ou pire encore, appels IO bloquants à l’intérieur de fonctions asynchrones, ainsi que l’absence d’index ou d’opportunités de mise en cache.

Le parallélisme est crucial pour gérer le temps. Si les trois agents sont exécutés séquentiellement, ils nécessiteront au total entre 60 et 90 secondes. Lorsqu’ils sont exécutés en même temps, ils terminent en environ 7 à 12 secondes, soit le temps nécessaire au plus lent des agents, et non la somme des temps des trois.

Le fan-in : l’agent 4

Le réviseur final attend automatiquement que les trois spécialistes aient terminé. LangGraph gère cette synchronisation, il n’est donc pas nécessaire d’écrire de logique de mise en attente ou de sondage. Lorsque les trois rapports arrivent sous forme de JSON structuré, l’agent 4 effectue une seule action : il écrit le commentaire de révision final en Markdown.

L’agent 4 ne réalise aucune analyse. Il ne reconsulte jamais le code et se contente de combiner et de formater ce que les spécialistes ont rapporté. C’est intentionnel : même si sa formulation est légèrement imprécise, les conclusions restent exactement celles produites par les spécialistes. Son commentaire commence par un jugement global et une note qui correspond à la moyenne des notes des trois agents, puis résume ce que chaque agent a trouvé, avant de se terminer par une liste des problèmes à corriger absolument avant fusion, limitée aux problèmes critiques et de gravité élevée.

Un piège dans l’état parallèle

Un détail technique subtil est facile à négliger. Une fois que les agents parallèles ont terminé leur travail et écrit de nouvelles données dans l’état partagé, chacun ne doit restituer que la clé qu’il possède. Si chaque agent renvoyait l’ensemble de l’objet d’état, LangGraph recevrait simultanément trois copies conflictuelles de chaque champ partagé et générerait une erreur InvalidUpdateError. La solution est simple mais pas évidente : associer un réducteur Annotated aux champs que les agents parallèles modifient, et faire en sorte que chaque agent ne renvoie que son propre résultat. On apprend cela en rencontrant cette erreur, et la plupart des tutoriels LangGraph ne l’évoquent jamais.

Étape 4 : le résultat

La preuve se trouve dans un test. Un fichier Python contenant quatre problèmes intentionnels, un secret codé en dur, une faille d’injection SQL, une boucle imbriquée de type O(n²) ainsi que des noms peu appropriés a été mis sur une branche et soumis en demande de fusion. Une revue est apparue sur cette PR 27 secondes plus tard. Voici le code qui a été intégré :

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

Et c’est ce que le système a publié sur le 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. 🙌

Dans les 27 secondes qui ont suivi la création de la demande de pull, l’analyse a identifié quatre catégories réelles de problèmes, avec des références précises aux lignes concernées et des suggestions de correction. Aucun être humain n’a été impliqué et il n’y a eu aucune attente : seuls des retours structurés et exploitables ont été fournis immédiatement.

Exécution locale

Tout commence par une seule commande Docker : les variables d’environnement sont définies, un token GitHub est créé ainsi que le webhook configuré, ngrok relie le serveur local à GitHub, et une PR de test montre que l’analyse apparaît en temps réel. Changer entre Claude et OpenAI ne nécessite qu’une seule variable d’environnement, et les étapes de configuration sont décrites dans le fichier README du répertoire.

Leçons tirées du processus

Les agents spécialisés surpassent les agents généralistes. Une tâche par agent permet d’obtenir des résultats plus précis et ciblés, car l’agent de sécurité n’est pas influencé par le style du code et l’agent de performance ne consomme pas de tokens pour les noms de variables.

La file d’attente des tâches fait partie de l’architecture et n’est pas une option. Le délai de 10 secondes imposé par GitHub est strict, et sans Redis ainsi qu’un travailleur en arrière-plan, les livraisons échouées sont réessayées et des avis dupliqués s’accumulent. De nombreux tutoriels omettent cet aspect, pourtant c’est ce qui rend le système fiable.

L’exécution parallèle change la donne. Trois appels séquentiels de 10 secondes chacun nécessitent au moins 30 secondes, tandis que trois appels parallèles n’en nécessitent que 10. LangGraph rend la mise en œuvre du parallélisme très simple, et la différence est considérable.

C’est l’output structuré qui rend les agents prêts à être utilisés en production. Chaque agent renvoie un schéma JSON strict, et si le parsing échoue, la tâche échoue proprement sans rien être envoyé sur GitHub. Le système fournit soit un bon avis, soit rien du tout, mais jamais des données inutiles.

Démarrez avec l’abstraction du LLM dès le premier jour. Un modèle de type usine qui permet de basculer entre Claude et OpenAI grâce à une seule variable d’environnement prend environ dix minutes à mettre en place et économise des heures de refacturation par la suite, puisque les modèles peuvent être testés librement et que leur qualité peut être évaluée.

Où cela laisse la revue de code

La revue de code ne disparaîtra pas, et pour les décisions architecturales, la logique métier ainsi que les compromis de conception délicats, rien ne remplace l’intervention humaine. La première analyse mécanique est une autre affaire : les secrets codés en dur, les injections SQL, les boucles quadratiques et les noms peu clairs peuvent tous être automatisés, ce qui libère les reviewers humains pour des tâches nécessitant de l’intelligence humaine. Ce système détecte les problèmes évidents en environ 30 secondes, permettant aux collègues de se concentrer sur ceux qui sont importants.