Skip to content
Tutorials13 min read

Revue de code façon PR GitHub pour les agents de coding IA

Mettez en place l'annotation de revue de code façon PR GitHub dans Potato : commentaires en ligne sur le diff, notes de qualité par fichier et verdicts d'approbation ou de rejet pour la sortie des agents de coding.

Potato Team

Pourquoi l'annotation de revue de code compte

La plupart des bancs d'essai pour agents de coding ramènent l'évaluation à un binaire : les tests passent-ils, oui ou non ? SWE-bench annonce un pourcentage de tickets résolus. HumanEval annonce un pass@k. Ces métriques servent aux classements, mais elles n'apprennent rien sur la qualité du code.

Un agent peut faire passer tous les tests et écrire quand même du code que personne ne voudrait maintenir : du code avec une faille de sécurité, un chemin lent, ou un style qui va à l'encontre du reste de la base de code. Un relecteur humain demanderait des modifications sur cette PR même si les tests sont au vert. Si vous voulez des agents qui écrivent du code que les gens fusionnent vraiment, il faut relire le code, pas seulement lancer les tests.

Le schéma d'annotation code_review de Potato fait entrer l'expérience de revue de PR GitHub dans un outil d'annotation. Les annotateurs voient des diffs unifiés avec coloration syntaxique, cliquent sur les lignes du diff pour ajouter des commentaires en ligne, notent les fichiers sur deux dimensions de qualité et rendent un verdict d'approbation, de demande de modifications ou de simple commentaire, comme pour la revue d'une vraie pull request. Pour la référence complète du schéma, voir la documentation d'annotation des agents de coding et le guide d'évaluation des agents.

Voici l'interface de revue de code dans Potato, avec les commentaires en ligne sur le diff et les notes par fichier :

Annotation de revue de code avec commentaires en ligne sur le diff et notes par fichierL'interface de revue de code de Potato, avec commentaires en ligne sur le diff et notes de qualité par fichier


Vue d'ensemble du schéma de revue de code

Le schéma code_review comporte trois couches :

  1. Commentaires en ligne sur le diff : les annotateurs cliquent sur n'importe quelle ligne du diff pour y rattacher un commentaire catégorisé (bug, style, performance, sécurité, logique, suggestion, question)
  2. Notes par fichier : chaque fichier modifié reçoit des notes indépendantes de justesse (1-5) et de qualité du code (1-5)
  3. Verdict global : l'annotateur rend un verdict final : approuver, demander des modifications ou commenter seulement

Cela reproduit une vraie revue de code, ce qui la rend naturelle pour des développeurs, et la sortie structurée qu'elle produit se transpose directement à l'entraînement de modèles de revue de code.


CodingTraceDisplay : comment les diffs sont rendus

Le composant CodingTraceDisplay affiche les traces d'agents de coding comme une suite d'appels d'outils et de leurs sorties, avec un traitement particulier pour les modifications de fichiers. Quand l'agent modifie un fichier, l'affichage montre un diff unifié avec :

  • Lignes rouges : lignes supprimées (préfixées par -)
  • Lignes vertes : lignes ajoutées (préfixées par +)
  • Lignes grises : lignes de contexte (inchangées)
  • Numéros de ligne : anciens et nouveaux numéros dans la gouttière
  • Coloration syntaxique : coloration adaptée au langage, selon l'extension du fichier
  • Clic pour commenter : cliquer sur une ligne ouvre un formulaire de commentaire ancré à cette ligne

Le diff est calculé automatiquement à partir des opérations d'édition de l'agent. Si l'agent a utilisé un outil de recherche-remplacement, Potato reconstruit les états avant/après et génère le diff unifié.

Pour les agents qui produisent plusieurs modifications de fichiers dans une même trace (courant pour les corrections de bugs réelles), chaque fichier obtient sa propre section de diff repliable, à la manière de l'onglet « Files changed » d'une PR GitHub.

Le CodingTraceDisplay affiche les modifications de code avec une coloration syntaxique correcte :

Affichage de trace de coding avec rendu des diffs et arborescence de fichiersCodingTraceDisplay affichant des diffs unifiés avec coloration syntaxique et une barre latérale d'arborescence de fichiers


Catégories de commentaires

Quand un annotateur clique sur une ligne du diff pour ajouter un commentaire, il choisit une catégorie :

CatégorieCouleurDescriptionExemple
bugRougeLe code contient une erreur fonctionnelle« Ceci lèvera une NullPointerException si user vaut None »
styleBleuProblème de style ou de convention« Le projet utilise snake_case pour les fonctions, pas camelCase »
performanceOrangeCode inefficace« Ceci interroge la base de données dans une boucle ; utilisez une requête groupée »
securityVioletFaille de sécurité« La saisie utilisateur est passée directement à la requête SQL sans assainissement »
logicJauneProblème de logique qui ne provoque pas forcément une panne immédiate« Cette condition devrait être >= et non >, décalage d'un cran à la borne »
suggestionVertSuggestion d'amélioration, pas une erreur« Envisagez un gestionnaire de contexte ici, la gestion des ressources sera plus propre »
questionGrisClarification nécessaire« Pourquoi cet import a-t-il été ajouté ? Il ne semble pas utilisé »

Chaque commentaire dispose aussi d'un corps en texte libre où l'annotateur explique le problème en détail, exactement comme on rédige un vrai commentaire de PR.


Notes par fichier

Après avoir relu le diff de chaque fichier, l'annotateur le note sur deux dimensions :

Justesse (1-5) :

  • 1 : ne fonctionne pas, introduit de nouveaux bugs
  • 2 : fonctionne partiellement, problèmes importants
  • 3 : fonctionne dans le cas nominal mais rate les cas limites
  • 4 : fonctionne correctement, problèmes mineurs
  • 5 : entièrement juste, traite les cas limites comme il faut

Qualité du code (1-5) :

  • 1 : non maintenable, aucune structure
  • 2 : mauvaise qualité, problèmes de style et de conception importants
  • 3 : acceptable, respecte les conventions de base
  • 4 : bonne qualité, propre et lisible
  • 5 : excellent, idiomatique, bien documenté

Options de verdict

Après avoir relu tous les fichiers, l'annotateur choisit l'un des trois verdicts :

  • Approuver : le code est prêt à être fusionné tel quel, ou avec des retouches négligeables
  • Demander des modifications : le code demande des révisions importantes avant fusion
  • Commenter seulement : donner un retour sans trancher sur la fusion

Cela correspond directement aux trois états de revue de PR de GitHub.


Mise en place pas à pas

Étape 1 : convertir les traces d'agents de coding

Les traces d'agents de coding arrivent dans de nombreux formats. Voici des exemples pour trois agents répandus.

Depuis Claude Code (export JSON) :

bash
python -m potato.trace_converter \
  --input claude_code_sessions/ \
  --output data/code_traces.jsonl \
  --input-format claude_code

Depuis Aider (historique de conversation) :

bash
python -m potato.trace_converter \
  --input aider_logs/ \
  --output data/code_traces.jsonl \
  --input-format aider

Depuis SWE-Agent (répertoire de trajectoires) :

bash
python -m potato.trace_converter \
  --input swe_agent_trajectories/ \
  --output data/code_traces.jsonl \
  --input-format swe_agent_trajectory

Le convertisseur produit un format JSONL standardisé. Chaque ligne contient une trace avec la tâche, les étapes de l'agent et les diffs de fichiers :

json
{
  "trace_id": "swe_bench_123",
  "task": "Fix the TypeError in django/db/models/query.py when calling .values() on an empty QuerySet",
  "repo": "django/django",
  "base_commit": "a1b2c3d",
  "steps": [
    {
      "type": "thought",
      "content": "Let me look at the QuerySet.values() method to understand the TypeError."
    },
    {
      "type": "action",
      "tool": "search",
      "input": "def values",
      "file": "django/db/models/query.py"
    },
    {
      "type": "observation",
      "content": "Found at line 234: def values(self, *fields):\n    ..."
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "django/db/models/query.py",
      "old_content": "    def values(self, *fields):\n        clone = self._clone()\n        clone.query.set_values(fields)",
      "new_content": "    def values(self, *fields):\n        clone = self._clone()\n        if not fields and not self.query.values_select:\n            return clone\n        clone.query.set_values(fields)"
    },
    {
      "type": "action",
      "tool": "edit",
      "file": "tests/queries/test_values.py",
      "old_content": "class ValuesTests(TestCase):",
      "new_content": "class ValuesTests(TestCase):\n    def test_empty_queryset_values(self):\n        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n        qs = Author.objects.none()\n        result = list(qs.values())\n        self.assertEqual(result, [])"
    }
  ],
  "files_changed": [
    {
      "path": "django/db/models/query.py",
      "diff": "--- a/django/db/models/query.py\n+++ b/django/db/models/query.py\n@@ -234,6 +234,8 @@\n     def values(self, *fields):\n         clone = self._clone()\n+        if not fields and not self.query.values_select:\n+            return clone\n         clone.query.set_values(fields)"
    },
    {
      "path": "tests/queries/test_values.py",
      "diff": "--- a/tests/queries/test_values.py\n+++ b/tests/queries/test_values.py\n@@ -1,4 +1,10 @@\n class ValuesTests(TestCase):\n+    def test_empty_queryset_values(self):\n+        \"\"\"Calling .values() on empty QuerySet should not raise TypeError.\"\"\"\n+        qs = Author.objects.none()\n+        result = list(qs.values())\n+        self.assertEqual(result, [])"
    }
  ]
}

Étape 2 : configurer le schéma de revue de code

Créez votre config.yaml :

yaml
annotation_task_name: "Coding Agent Code Review"
 
data_files:
  - "data/code_traces.jsonl"
 
item_properties:
  id_key: "trace_id"
  text_key: "task"
 
# Display coding agent traces with diff rendering
display:
  type: "coding_trace"
  trace_key: "steps"
  diff_key: "files_changed"
  syntax_highlighting: true
  show_line_numbers: true
  collapse_large_diffs: true
  max_uncollapsed_lines: 200
 
annotation_schemes:
  - annotation_type: "code_review"
 
    # Inline comment categories
 
    # File-level ratings
 
    # Overall verdict
 
# Annotator settings
annotator_config:
  allow_back_navigation: true
 
# Output settings
output:
  path: "output/"
  format: "jsonl"

Étape 3 : lancer le serveur d'annotation

bash
potato start config.yaml -p 8000

Rendez-vous sur http://localhost:8000. Vous verrez la première trace d'agent de coding avec la description de la tâche, les étapes de raisonnement de l'agent et les diffs de fichiers rendus avec coloration syntaxique.

Étape 4 : le déroulé pour l'annotateur

Voici le flux de revue habituel :

  1. Lire la tâche : comprendre ce qui a été demandé à l'agent (par exemple, « Corriger le TypeError dans django/db/models/query.py »)
  2. Parcourir la trace : faire défiler les étapes de raisonnement de l'agent pour saisir son approche
  3. Relire le diff de chaque fichier :
    • Lire le diff avec la coloration syntaxique
    • Cliquer sur une ligne pour ajouter un commentaire en ligne
    • Choisir une catégorie de commentaire (bug, style, performance, etc.)
    • Rédiger le corps du commentaire pour expliquer le problème
    • Noter le fichier sur la justesse (1-5) et la qualité du code (1-5)
  4. Rendre un verdict : approuver, demander des modifications ou commenter seulement
  5. Envoyer : cliquer sur « Submit » ou appuyer sur Ctrl+Entrée

Des raccourcis clavier accélèrent le travail :

RaccourciAction
j / kNaviguer entre les fichiers
cOuvrir un commentaire sur la ligne sélectionnée
1-5Attribuer la note de la dimension courante
aMettre le verdict sur approuver
rMettre le verdict sur demander des modifications
Ctrl+EnterEnvoyer la revue

Format d'exportation

Chaque revue envoyée produit un objet JSON structuré :

json
{
  "trace_id": "swe_bench_123",
  "annotator": "reviewer_01",
  "timestamp": "2026-03-22T14:32:11Z",
  "review": {
    "inline_comments": [
      {
        "file": "django/db/models/query.py",
        "line": 236,
        "side": "right",
        "category": "logic",
        "body": "This early return skips set_values entirely, but if fields are provided later via .values('name'), the previous empty .values() call will have returned a clone that never went through set_values. Consider checking if this clone is still valid downstream."
      },
      {
        "file": "tests/queries/test_values.py",
        "line": 5,
        "side": "right",
        "category": "suggestion",
        "body": "Consider adding a test case for .values() followed by .values('name') to verify the chaining behavior after your fix."
      }
    ],
    "file_ratings": [
      {
        "file": "django/db/models/query.py",
        "correctness": 3,
        "code_quality": 4
      },
      {
        "file": "tests/queries/test_values.py",
        "correctness": 4,
        "code_quality": 4
      }
    ],
    "verdict": "request_changes"
  }
}

Ce format structuré s'utilise directement pour entraîner des modèles de revue de code et pour l'analyse agrégée.


Analyse : exploiter les données de revue

Charger les revues

python
import json
import pandas as pd
from pathlib import Path
 
reviews = []
for f in Path("output/").glob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            reviews.append(json.loads(line))
 
print(f"Loaded {len(reviews)} code reviews")

Répartition des catégories de commentaires

python
from collections import Counter
 
all_comments = []
for rev in reviews:
    for comment in rev["review"]["inline_comments"]:
        all_comments.append(comment)
 
category_counts = Counter(c["category"] for c in all_comments)
print("Comment categories:")
for cat, count in category_counts.most_common():
    print(f"  {cat}: {count}")

Notes moyennes par fichier

python
ratings = []
for rev in reviews:
    for fr in rev["review"]["file_ratings"]:
        ratings.append(fr)
 
ratings_df = pd.DataFrame(ratings)
print("Average ratings by file:")
print(
    ratings_df.groupby("file")[["correctness", "code_quality"]]
    .mean()
    .round(2)
    .to_string()
)

Répartition des verdicts

python
verdict_counts = Counter(rev["review"]["verdict"] for rev in reviews)
total = sum(verdict_counts.values())
print("Verdict distribution:")
for verdict, count in verdict_counts.most_common():
    print(f"  {verdict}: {count} ({count/total*100:.1f}%)")

Taux de bugs par agent

Si vos traces comportent un champ agent, vous pouvez comparer les taux de bugs entre agents :

python
agent_bugs = {}
for rev in reviews:
    agent = rev.get("agent", "unknown")
    bug_count = sum(
        1 for c in rev["review"]["inline_comments"]
        if c["category"] == "bug"
    )
    if agent not in agent_bugs:
        agent_bugs[agent] = []
    agent_bugs[agent].append(bug_count)
 
print("Average bugs per review by agent:")
for agent, bugs in sorted(agent_bugs.items()):
    print(f"  {agent}: {sum(bugs)/len(bugs):.2f} (n={len(bugs)})")

Cas d'usage

Entraîner des modèles de revue de code

Les commentaires en ligne structurés, les notes par fichier et les verdicts issus de l'annotation de revue de code de Potato font d'excellentes données d'entraînement pour des modèles de revue de code automatisée. Chaque revue apporte :

  • Un retour localisé, rattaché à des lignes précises du diff
  • Des problèmes catégorisés (bug, style ou performance)
  • Des signaux de qualité à plusieurs granularités (ligne, fichier, ensemble)

C'est le format de données qu'utilisent des outils comme CodeRabbit ou le relecteur IA de Graphite, mais produit par des experts humains plutôt que distillé depuis un LLM.

Évaluer des agents de coding sur SWE-bench

SWE-bench vous dit si l'agent a résolu le ticket (tests au vert), pas si le code est fusionnable. En passant les solutions SWE-bench par une annotation de revue de code, vous distinguez les agents qui résolvent les tickets avec du code propre de ceux qui les résolvent avec des bricolages. Le classement obtenu est plus nuancé et colle mieux au vécu des développeurs.

Constituer des jeux de données de qualité de code

Agrégez les données de revue de code sur de nombreuses traces pour constituer des jeux de données des problèmes de qualité récurrents dans le code généré par IA. Ces jeux de données servent à :

  • Affiner les modèles de génération de code pour éviter les erreurs courantes
  • Construire des linters propres aux motifs du code généré par IA
  • Entraîner des classifieurs qui signalent les problèmes probables dans la sortie de l'agent avant la revue humaine

Résumé

Le schéma code_review de Potato place le déroulé de revue de PR GitHub à l'intérieur de l'évaluation d'agents. Les commentaires en ligne, les notes par fichier et les verdicts que vous recueillez vous donnent des données structurées sur la qualité du code, ce qui vous en apprend bien plus qu'un résultat de test réussite/échec. Ce sont ces données qu'il vous faut, que vous entraîniez un modèle de revue de code, que vous sépariez les solutions SWE-bench propres des bricolages, ou que vous établissiez simplement une base de référence de qualité pour votre agent.