Skip to content
Tutorials11 min read

Code-Review im Stil eines GitHub-PR für KI-Coding-Agenten

Code-Review-Annotation im Stil eines GitHub-PR in Potato einrichten, mit Inline-Kommentaren im Diff, Qualitätsbewertungen pro Datei und Urteilen zu Freigabe oder Ablehnung für die Ausgabe von Coding-Agenten.

Potato Team

Warum Code-Review-Annotation zählt

Die meisten Benchmarks für Coding-Agenten reduzieren die Evaluation auf eine binäre Frage: Sind die Tests durchgelaufen oder nicht? SWE-bench nennt einen Prozentsatz gelöster Issues. HumanEval nennt pass@k. Solche Kennzahlen taugen für Leaderboards, aber nicht dafür, Codequalität zu verstehen.

Ein Agent kann jeden Test bestehen und trotzdem Code schreiben, den niemand pflegen möchte: Code mit einer Sicherheitslücke, mit einem langsamen Pfad oder in einem Stil, der sich mit dem Rest der Codebasis beißt. Ein menschlicher Reviewer würde bei so einem PR Änderungen anfordern, auch wenn die Tests grün sind. Wer Agenten will, deren Code tatsächlich gemergt wird, muss den Code reviewen und nicht nur die Tests laufen lassen.

Das Annotationsschema code_review in Potato bringt die Review-Erfahrung eines GitHub-PR in ein Annotationswerkzeug. Annotatoren sehen Unified Diffs mit Syntaxhervorhebung, klicken auf Diff-Zeilen, um Inline-Kommentare zu hinterlassen, bewerten Dateien auf ein paar Qualitätsdimensionen und geben ein Urteil ab: freigeben, Änderungen anfordern oder nur kommentieren, genau wie beim Review eines echten Pull Request. Die vollständige Schemareferenz steht in der Dokumentation zur Annotation von Coding-Agenten und im Leitfaden zur Agent-Evaluation.

So sieht die Code-Review-Oberfläche in Potato aus, mit Inline-Kommentaren im Diff und Bewertungen pro Datei:

Code-Review-Annotation mit Inline-Kommentaren im Diff und DateibewertungenDie Code-Review-Oberfläche von Potato mit Inline-Kommentaren im Diff und Qualitätsbewertungen pro Datei


Überblick über das code_review-Schema

Das Schema code_review hat drei Ebenen:

  1. Inline-Kommentare im Diff: Annotatoren klicken auf eine beliebige Zeile im Diff und hängen dort einen kategorisierten Kommentar an (Bug, Stil, Performance, Sicherheit, Logik, Vorschlag, Frage)
  2. Bewertungen pro Datei: Jede geänderte Datei bekommt unabhängige Bewertungen für Korrektheit (1-5) und Codequalität (1-5)
  3. Gesamturteil: Der Annotator fällt ein abschließendes Urteil: freigeben, Änderungen anfordern oder nur kommentieren

Das bildet ein echtes Code-Review nach, fühlt sich für Entwickler also vertraut an, und die strukturierte Ausgabe lässt sich direkt für das Training von Code-Review-Modellen verwenden.


CodingTraceDisplay: Wie Diffs dargestellt werden

Die Komponente CodingTraceDisplay stellt Traces von Coding-Agenten als Folge von Tool-Aufrufen und deren Ausgaben dar, mit besonderer Behandlung von Dateiänderungen. Bearbeitet der Agent eine Datei, zeigt die Anzeige ein Unified Diff mit:

  • Roten Zeilen: gelöschte Zeilen (mit - am Anfang)
  • Grünen Zeilen: hinzugefügte Zeilen (mit + am Anfang)
  • Grauen Zeilen: Kontextzeilen (unverändert)
  • Zeilennummern: alte und neue Zeilennummern in der Randspalte
  • Syntaxhervorhebung: sprachabhängig anhand der Dateiendung
  • Klick zum Kommentieren: Ein Klick auf eine Zeile öffnet ein Kommentarformular, das an dieser Zeile verankert ist

Das Diff wird automatisch aus den Bearbeitungsoperationen des Agenten berechnet. Hat der Agent ein Suchen-und-Ersetzen-Tool benutzt, rekonstruiert Potato den Zustand vorher und nachher und erzeugt daraus das Unified Diff.

Bei Agenten, die in einem Trace mehrere Dateien ändern (bei echten Bugfixes der Normalfall), bekommt jede Datei ihren eigenen einklappbaren Diff-Abschnitt, ähnlich dem Tab „Files changed" eines GitHub-PR.

Die CodingTraceDisplay stellt Codeänderungen mit passender Syntaxhervorhebung dar:

Coding-Trace-Anzeige mit Diff-Darstellung und DateibaumCodingTraceDisplay zeigt Unified Diffs mit Syntaxhervorhebung und einem Dateibaum in der Seitenleiste


Kommentarkategorien

Klickt ein Annotator auf eine Diff-Zeile, um einen Kommentar zu hinterlassen, wählt er dazu eine Kategorie:

KategorieFarbeBeschreibungBeispiel
bugRotDer Code enthält einen funktionalen Fehler„Das wirft eine NullPointerException, wenn user None ist"
styleBlauProblem mit Codestil oder Konventionen„Das Projekt benutzt snake_case für Funktionen, nicht camelCase"
performanceOrangeIneffizienter Code„Das fragt die Datenbank in einer Schleife ab; besser eine Batch-Abfrage"
securityViolettSicherheitslücke„Nutzereingaben landen ohne Bereinigung direkt in der SQL-Abfrage"
logicGelbLogikproblem, das nicht sofort zu einem Fehler führen muss„Die Bedingung müsste >= statt > sein, Off-by-one an der Grenze"
suggestionGrünVerbesserungsvorschlag, kein Fehler„Ein Context Manager wäre hier sauberer für die Ressourcenverwaltung"
questionGrauKlärungsbedarf„Warum wurde dieser Import ergänzt? Er scheint nicht benutzt zu werden"

Zu jedem Kommentar gehört außerdem ein Freitextfeld, in dem der Annotator das Problem ausführlich erklärt, genau wie beim Schreiben eines echten PR-Kommentars.


Bewertungen pro Datei

Nachdem der Annotator das Diff einer Datei durchgesehen hat, bewertet er sie auf zwei Dimensionen:

Korrektheit (1-5):

  • 1: funktioniert nicht, führt neue Bugs ein
  • 2: funktioniert teilweise, hat erhebliche Probleme
  • 3: funktioniert auf dem Happy Path, übersieht Randfälle
  • 4: funktioniert korrekt, kleinere Probleme
  • 5: vollständig korrekt, behandelt Randfälle angemessen

Codequalität (1-5):

  • 1: nicht wartbar, keine Struktur
  • 2: schlechte Qualität, erhebliche Stil- und Designprobleme
  • 3: akzeptabel, folgt den grundlegenden Konventionen
  • 4: gute Qualität, sauber und lesbar
  • 5: hervorragend, idiomatisch, gut dokumentiert

Die Urteilsoptionen

Nach der Durchsicht aller Dateien wählt der Annotator eines von drei Urteilen:

  • Freigeben: Der Code ist so, wie er ist, oder mit minimalen Änderungen bereit zum Mergen
  • Änderungen anfordern: Der Code braucht substanzielle Überarbeitungen vor dem Merge
  • Nur kommentieren: Rückmeldung geben, ohne über den Merge zu entscheiden

Das entspricht direkt den drei Review-Zuständen eines GitHub-PR.


Einrichtung Schritt für Schritt

Schritt 1: Traces von Coding-Agenten konvertieren

Traces von Coding-Agenten kommen in vielen Formaten. Hier Beispiele für drei verbreitete Agenten.

Aus Claude Code (JSON-Export):

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

Aus Aider (Chatverlauf):

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

Aus SWE-Agent (Trajektorienverzeichnis):

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

Der Konverter erzeugt ein einheitliches JSONL-Format. Jede Zeile enthält einen Trace mit der Aufgabe, den Schritten des Agenten und den Datei-Diffs:

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, [])"
    }
  ]
}

Schritt 2: Das code_review-Schema konfigurieren

Die eigene config.yaml anlegen:

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"

Schritt 3: Den Annotationsserver starten

bash
potato start config.yaml -p 8000

http://localhost:8000 aufrufen. Dort erscheint der erste Trace eines Coding-Agenten mit der Aufgabenbeschreibung, den Denkschritten des Agenten und den Datei-Diffs samt Syntaxhervorhebung.

Schritt 4: Der Arbeitsablauf für Annotatoren

So läuft ein Review typischerweise ab:

  1. Aufgabe lesen: Verstehen, was der Agent tun sollte (etwa „Fix the TypeError in django/db/models/query.py")
  2. Trace durchsehen: Die Denkschritte des Agenten durchgehen, um sein Vorgehen nachzuvollziehen
  3. Jedes Datei-Diff prüfen:
    • Das Diff mit Syntaxhervorhebung durchlesen
    • Auf eine beliebige Zeile klicken, um einen Inline-Kommentar zu hinterlassen
    • Eine Kommentarkategorie wählen (Bug, Stil, Performance usw.)
    • Den Kommentartext schreiben, der das Problem erklärt
    • Die Datei auf Korrektheit (1-5) und Codequalität (1-5) bewerten
  4. Urteil fällen: freigeben, Änderungen anfordern oder nur kommentieren
  5. Absenden: Auf „Submit" klicken oder Strg+Enter drücken

Tastenkürzel beschleunigen die Arbeit:

KürzelAktion
j / kZwischen Dateien wechseln
cKommentar zur ausgewählten Zeile öffnen
1-5Bewertung für die aktuelle Dimension setzen
aUrteil auf Freigabe setzen
rUrteil auf Änderungen anfordern setzen
Ctrl+EnterReview absenden

Exportformat

Jedes abgeschickte Review ergibt ein strukturiertes JSON-Objekt:

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"
  }
}

Dieses strukturierte Format lässt sich direkt zum Training von Code-Review-Modellen und für aggregierte Auswertungen verwenden.


Auswertung: Mit den Review-Daten arbeiten

Reviews laden

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")

Verteilung der Kommentarkategorien

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}")

Durchschnittliche Bewertungen pro Datei

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()
)

Verteilung der Urteile

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}%)")

Bug-Rate nach Agent

Enthalten die Traces ein Feld agent, lassen sich die Bug-Raten verschiedener Agenten vergleichen:

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)})")

Anwendungsfälle

Code-Review-Modelle trainieren

Die strukturierten Inline-Kommentare, Dateibewertungen und Urteile aus der Code-Review-Annotation in Potato eignen sich gut als Trainingsdaten für automatische Code-Review-Modelle. Jedes Review liefert:

  • lokalisierte Rückmeldung, die an bestimmten Diff-Zeilen hängt
  • kategorisierte Probleme (Bug gegenüber Stil gegenüber Performance)
  • Qualitätssignale auf mehreren Granularitätsstufen (Zeile, Datei, Gesamtergebnis)

Es ist dasselbe Datenformat, mit dem Werkzeuge wie CodeRabbit oder der KI-Reviewer von Graphite arbeiten, nur von menschlichen Fachleuten erzeugt statt aus einem LLM destilliert.

Coding-Agenten auf SWE-bench evaluieren

SWE-bench sagt, ob der Agent das Issue gelöst hat (Tests laufen durch), aber nicht, ob der Code mergefähig ist. Wer Code-Review-Annotation auf SWE-bench-Lösungen anwendet, kann Agenten unterscheiden, die Issues mit sauberem Code lösen, von Agenten, die sie mit Hacks lösen. Das ergibt ein differenzierteres Leaderboard, das besser zur Erfahrung echter Entwickler passt.

Datensätze zur Codequalität aufbauen

Aggregiert man Code-Review-Daten über viele Traces, entstehen Datensätze über häufige Qualitätsprobleme in KI-generiertem Code. Solche Datensätze lassen sich nutzen für:

  • Feinabstimmung von Codegenerierungsmodellen, damit sie häufige Fehler vermeiden
  • Linter, die auf typische Muster KI-generierten Codes zugeschnitten sind
  • Klassifikatoren, die wahrscheinliche Probleme in Agent-Ausgaben schon vor dem menschlichen Review markieren

Fazit

Das code_review-Schema von Potato holt den Review-Workflow eines GitHub-PR in die Agent-Evaluation. Die Inline-Kommentare, Dateibewertungen und Urteile ergeben strukturierte Daten zur Codequalität, und das ist deutlich mehr, als ein Pass/Fail-Testergebnis hergibt. Genau diese Daten braucht man, ob man nun ein Code-Review-Modell trainiert, saubere SWE-bench-Lösungen von zusammengehackten trennt oder einfach eine Qualitätsbasis für den eigenen Agenten festlegt.