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.
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:
Die 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:
- 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)
- Bewertungen pro Datei: Jede geänderte Datei bekommt unabhängige Bewertungen für Korrektheit (1-5) und Codequalität (1-5)
- 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:
CodingTraceDisplay 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:
| Kategorie | Farbe | Beschreibung | Beispiel |
|---|---|---|---|
bug | Rot | Der Code enthält einen funktionalen Fehler | „Das wirft eine NullPointerException, wenn user None ist" |
style | Blau | Problem mit Codestil oder Konventionen | „Das Projekt benutzt snake_case für Funktionen, nicht camelCase" |
performance | Orange | Ineffizienter Code | „Das fragt die Datenbank in einer Schleife ab; besser eine Batch-Abfrage" |
security | Violett | Sicherheitslücke | „Nutzereingaben landen ohne Bereinigung direkt in der SQL-Abfrage" |
logic | Gelb | Logikproblem, das nicht sofort zu einem Fehler führen muss | „Die Bedingung müsste >= statt > sein, Off-by-one an der Grenze" |
suggestion | Grün | Verbesserungsvorschlag, kein Fehler | „Ein Context Manager wäre hier sauberer für die Ressourcenverwaltung" |
question | Grau | Klä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):
python -m potato.trace_converter \
--input claude_code_sessions/ \
--output data/code_traces.jsonl \
--input-format claude_codeAus Aider (Chatverlauf):
python -m potato.trace_converter \
--input aider_logs/ \
--output data/code_traces.jsonl \
--input-format aiderAus SWE-Agent (Trajektorienverzeichnis):
python -m potato.trace_converter \
--input swe_agent_trajectories/ \
--output data/code_traces.jsonl \
--input-format swe_agent_trajectoryDer Konverter erzeugt ein einheitliches JSONL-Format. Jede Zeile enthält einen Trace mit der Aufgabe, den Schritten des Agenten und den Datei-Diffs:
{
"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:
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
potato start config.yaml -p 8000http://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:
- Aufgabe lesen: Verstehen, was der Agent tun sollte (etwa „Fix the TypeError in django/db/models/query.py")
- Trace durchsehen: Die Denkschritte des Agenten durchgehen, um sein Vorgehen nachzuvollziehen
- 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
- Urteil fällen: freigeben, Änderungen anfordern oder nur kommentieren
- Absenden: Auf „Submit" klicken oder Strg+Enter drücken
Tastenkürzel beschleunigen die Arbeit:
| Kürzel | Aktion |
|---|---|
j / k | Zwischen Dateien wechseln |
c | Kommentar zur ausgewählten Zeile öffnen |
1-5 | Bewertung für die aktuelle Dimension setzen |
a | Urteil auf Freigabe setzen |
r | Urteil auf Änderungen anfordern setzen |
Ctrl+Enter | Review absenden |
Exportformat
Jedes abgeschickte Review ergibt ein strukturiertes JSON-Objekt:
{
"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
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
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
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
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:
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.