08 Build and Break a Grader: Evaluation von Codegenerierung selbst entdecken
June 2026 (2885 Words, 17 Minutes)
Ein Einstieg, keine Wiederholung. Dies ist die erste praktische Aktivität zu Vorlesung 8, und sie ist bewusst eine Entdeckungsübung: Wir versuchen, eine Evaluation für ein Codegenerierungs-Modell zu bauen, bevor die Vorlesung uns die Antworten des Fachgebiets liefert — damit wir, wenn die Kapitel 3–6 ein Problem benennen, schon eine wunde Stelle dort haben, wo es uns getroffen hat. Das Ganze läuft im Unterricht, in Gruppen von 3–4, in etwa einem 90-Minuten-Block — brainstormen → bauen → ein Modell auf den Prüfstand stellen → einen Grader knacken → präsentieren. Alles in Python. Es endet damit, dass jede Gruppe präsentiert und die Klasse gemeinsam die Punkte aller mitschreibt — zu einem geteilten „Evaluations-Playbook”; es gibt nichts abzugeben und nichts zu benoten. Es gibt hier keine einzige richtige Antwort — es geht um den Denkprozess, um den Wechsel in die Denkweise der LLM-Evaluation. Wir machen das kalt, vor dem Vorlesungsinhalt: Wir gehen mit mehr offenen Fragen als Antworten heraus, und das ist so gewollt. Die Vorlesung danach ist der Ort, an dem diese Fragen ihre Namen und ihre Auflösungen bekommen — diese Aktivität sorgt nur dafür, dass wir das Problem, das sie lösen, schon gespürt haben.
Ziel
Wir entwerfen von Hand einen winzigen Benchmark, der eine Frage mit einer Zahl statt mit einem Bauchgefühl beantwortet: „Kann dieses Modell korrektes Python schreiben?” Wir wählen Aufgaben, schreiben Grader (Prüfprogramme), stellen ein echtes Modell (via Copilot) auf den Prüfstand und entdecken dann — das Herzstück der Übung —, wie leicht ein Grader zu täuschen ist und was es braucht, um einen zu bauen, dem wir trauen. Am Ende haben wir aus eigener Erfahrung hergeleitet, warum das Evaluieren von Codegenerierung schwerer ist, als es aussieht, und was einen Grader, der Korrektheit misst, von einem unterscheidet, der bloß Selbstsicherheit misst.
Das Schlüsselwerkzeug dafür besitzen wir schon. Wir haben Unit-Tests geschrieben; wir kennen Anweisungs- und Zweigüberdeckung (Coverage); und wir wissen aus Erfahrung, dass ein grüner Testbalken nicht beweist, dass Code korrekt ist — nur, dass er korrekt ist, soweit unsere Tests es sehen können. Diese Übung macht aus genau diesem einen Wissensstück eine Denkweise für maschinell bewertete Evaluation. Behalten wir es vorne im Kopf: Das Bewerten von KI-Code ist nichts anderes als Testen — nur ist das Prüfobjekt jetzt ein unermüdlicher Autor plausibel wirkender falscher Antworten.
Lernziele
Nach dieser Übung können wir:
- den Entwurfsraum einer Codegenerierungs-Evaluation aufspannen — Aufgabenquelle, Spezifikation, Sampling, Antwort-Extraktion, den Grader und wie aus Ergebnissen eine einzige Zahl wird — und benennen, welche Entscheidung jeder Schritt erzwingt.
- den Entwurf „Funktion vervollständigen + ausführbare Tests” herleiten — nicht weil man es uns gesagt hat, sondern weil eine realistische Vorgabe (automatisch, wiederholbar, über Modelle hinweg vertrauenswürdig) die Alternativen ausschließt.
- Python erzeugen, das falsch ist, aber eine echte Testsuite besteht, und in Coverage-Begriffen erklären, warum es durchgerutscht ist.
- erkennen, dass „das Modell via Copilot” nicht das blanke Modell ist, und durchdenken, was das Werkzeug dazwischen mit unserer Messung macht.
- einen Grader verstärken und „stark genug” begründen — im Vokabular, das wir schon haben: Coverage, Randfälle und ein absichtlich eingebauter Fehler (eine Mutation), den unsere Tests fangen sollten.
- artikulieren, was einen Grader gut macht und was selbst dann schiefgehen kann, wenn jeder Test grün ist.
Format im Überblick
Alles Folgende findet im Unterricht statt. Die Zeiten sind ein realistischer Richtwert für einen einzelnen ~90-Minuten-Block; nehmen wir sie als weich — die Präsentation in Phase 6 ist der Teil, den wir schützen.
| Phase | Zeit | Ergebnis |
|---|---|---|
| 1. Die 50-€-Jury — Brainstorm | ~10 Min | Eine rohe, ungeordnete Liste, wie wir es messen würden |
| 2. Eine Vorgabe, zwei Aufgaben bauen | ~30 Min | Zwei Python-Aufgaben (je: Spezifikation + Grader + Referenz) |
| 3. Ein Modell auf dem Prüfstand (Copilot) | ~12 Min | 5 bewertete Vervollständigungen + die beantwortete Copilot-Frage |
| 4. Den Grader der Nachbargruppe knacken | ~18 Min | Eine falsch-aber-bestehende Lösung |
| 5. Verstärken + „gut genug" | ~8 Min | Ein stärkerer Grader + unsere Begründung dafür |
| 6. Präsentieren + gemeinsam das Playbook bauen | ~15 Min | Eine 3-Min-Präsentation; das gemeinsame Evaluations-Playbook der Klasse |
Zu den Phasen. Jede Phase unten endet mit einer fett gesetzten Ergebnis:-Zeile — dem einen konkreten Resultat, das unsere Gruppe in der Hand haben sollte, bevor wir weitergehen. Sie greifen ineinander: Das Ergebnis jeder Phase ist die Eingabe der nächsten.
Grundregel — nur Python. Jede Aufgabe ist eine einfache Python-Funktion; jeder Grader ist Python (assert/pytest); jede Referenzlösung ist Python. Eine Sprache hält die Grader vergleichbar, wenn wir sie tauschen, und eliminiert „welche Sprache” als Störgröße — es ist außerdem genau die Wahl, die der Benchmark getroffen hat, dem wir als Nächstes begegnen.
Phase 1 — Die 50-€-Jury (~10 Min)
Szenario. Im Team läuft eine Wette über 50 €. Eine Person sagt, das neue Coding-Modell „kann im Grunde programmieren”; eine andere sagt, „es vervollständigt nur Stack Overflow”. Wir sind die Jury. Wir haben einen Nachmittag Zeit und müssen die Wette mit einer Zahl und einer verteidigbaren Methode entscheiden, nicht mit einer Anekdote.
Brainstormen wir, wie wir es messen würden. Noch nicht konvergieren — bringen wir die Meinungsverschiedenheiten auf den Tisch. Diese nur, falls wir feststecken:
- Aufgaben. Was würden wir das Modell tun lassen? Woher kommen diese Aufgaben — und könnte das Modell sie schon gesehen haben?
- Korrektheit. Wenn das Modell antwortet, wer oder was entscheidet, dass sie richtig ist? Ein Mensch? Eine Referenzantwort? Etwas anderes?
- Versuche. Ein Versuch oder viele? Wenn es beim 3. von 5 Versuchen klappt — hat es „bestanden”?
- „Gut”. Gut worin — korrekt? schnell? günstig? Wählen wir, worauf unsere 50 € wirklich setzen.
Halten wir die roheste Version unserer Gruppe auf Papier fest. Wir werden auf interessante Weise leicht danebenliegen; genau das ist der Sinn.
Ergebnis: ein Blatt mit unseren Mess-Ideen und den Punkten, bei denen wir uneins waren. Noch muss nichts gelöst werden — Phase 2 erzwingt die Entscheidungen.
Phase 2 — Eine Vorgabe, zwei Aufgaben bauen (~30 Min)
Unsere Teamleitung ergänzt die Vorgabe, unter der echte Benchmarks leben:
Es muss automatisch und wiederholbar sein. Kein Mensch liest jede Antwort. Es muss unbeaufsichtigt über Nacht auf 1.000 Aufgaben laufen, und wir müssen die exakt gleiche Evaluation nächsten Monat auf einem anderen Modell erneut laufen lassen und dem Vergleich trauen können.
Verbringen wir zwei Minuten damit, was diese Vorgabe aus unseren Phase-1-Ideen streicht — und was sie erzwingt. Dann bauen wir zwei Python-Aufgaben:
Aufgabe A — ein vorgegebener Typ, unser Entwurf. Unsere Gruppe bekommt (oder zieht) einen Aufgabentyp aus dem Pool unten zugewiesen. Der Typ ist alles, was wir bekommen — wir entscheiden weiterhin die genaue Funktion, schreiben die Spezifikation (Signatur + Docstring, präzise genug, dass zwei Fremde bei jeder Ausgabe übereinstimmen), schreiben den Grader (die Tests, die Bestehen/Durchfallen definieren) und schreiben eine korrekte Referenzlösung. Das ist die Aufgabe, die wir tauschen und angreifen lassen — machen wir den Grader also so stark, wie wir es für nötig halten.
Aufgabe B — unsere eigene. Erfinden wir eine zweite Funktion ganz nach eigener Wahl und entwerfen sie genauso (Spezifikation + ein paar Tests + Referenz). Halten wir sie klein — diese ist dafür da, in Phase 3 das Modell auf den Prüfstand zu stellen.
Aufgabentyp-Pool (Python)
| # | Typ (mehr bekommen wir nicht) | Geschmacksrichtung, keine Spezifikation |
|---|---|---|
| A1 | Zeichenketten-Verarbeitung | eine Zeichenkette umformen oder analysieren |
| A2 | Zahlen-/Listenverarbeitung | etwas über eine Zahlenliste berechnen |
| A3 | Ein Validierer | entscheiden, ob eine Eingabe wohlgeformt ist (gibt einen bool zurück oder wirft eine Exception) |
| A4 | Ein kleiner Parser / Konverter | eine Darstellung in eine andere überführen |
| A5 | Eine realitätsnahe Berechnung | Arithmetik mit einer Tücke — Rundung, Schaltjahre, Einheiten |
| A6 | Eine Aggregation | eine Sammlung gruppieren, zählen oder zusammenfassen |
Bewahren wir die Referenzlösung von Aufgabe A sicher auf — wir werden sie brauchen, und wir werden merken, warum wir sie brauchen. Dann tauschen wir Aufgabe A (Spezifikation + Grader, aber NICHT unsere Referenzlösung) mit einer anderen Gruppe.
Ergebnis: zwei ausgearbeitete Python-Aufgaben — Aufgabe A (Spezifikation + Grader + Referenz, die sorgfältige) und Aufgabe B (Spezifikation + ein paar Tests + Referenz, leichter). Die Spezifikation + Tests unserer Aufgabe A liegen jetzt bei einer Nachbargruppe (die Referenz behalten wir), und deren liegen bei uns.
Phase 3 — Ein Modell auf den Prüfstand, via Copilot (~12 Min)
Wir alle haben GitHub Copilot. Wählen wir eines der Modelle, die Copilot anbietet, und notieren, welches. Lassen wir es unsere eigene Aufgabe B lösen und sammeln 5 Vervollständigungen (den Prompt leicht variieren oder neu generieren). Lassen wir jede durch unseren Grader laufen und notieren Bestehen/Durchfallen. Dann schauen wir genauer hin, als unser Grader es tat:
- Hat eine Vervollständigung bestanden, ist aber tatsächlich falsch? (Von Hand gegen unsere Spezifikation prüfen.)
- Ist eine korrekte Vervollständigung aus einem albernen Grund durchgefallen — falsches Ausgabeformat, zusätzlicher Fließtext, ein
printstattreturn, abschließende Leerzeichen? (Das ist unser Grader, der zu strikt (fragil) ist — der umgekehrte Fehler: Er weist korrekten Code wegen belangloser Unterschiede ab.) - Das Modell gibt selten nur die Funktion zurück. Wie haben wir den Code aus seiner Antwort herausgelöst — und könnte dieser Schritt das Urteil ändern?
Jetzt die Frage, über die wir wirklich streiten sollten — schreiben wir unsere Antwort auf:
Wir messen Copilot, nicht das blanke Modell. Was steht zwischen uns und dem Modell? Welchen Kontext füttert Copilot ihm womöglich zu, den wir nicht gewählt haben — einschließlich, womöglich, unserer offenen Dateien? Wenn unsere Testdatei oder Referenzlösung in einem anderen Tab offen ist, während Copilot antwortet — was hat das mit unserer Messung gemacht? Würden zwei von uns, auf zwei Rechnern, überhaupt dieselbe Zahl bekommen?
Ergebnis: unsere 5 Copilot-Vervollständigungen für Aufgabe B mit notiertem Bestehen/Durchfallen (und welches Modell wir gewählt haben), plus eine schriftliche Antwort auf die hervorgehobene Frage oben.
Phase 4 — Den Grader der Nachbargruppe knacken (~18 Min)
Wir halten die Aufgabe A einer anderen Gruppe (Spezifikation + Tests, keine Referenzlösung). Unsere Aufgabe: eine Python-Implementierung erzeugen, die falsch ist, aber ihre Tests besteht.
- Von Hand. Schreiben wir die faulste, offensichtlich unvollständigste Implementierung, die trotzdem grün wird. Tipp: Was prüfen ihre Tests nie — eine leere Eingabe? einen Randfall? ein zweites Beispiel? ein Duplikat?
- Mit dem Modell. Fragen wir Copilot direkt: „Hier ist eine Python-Funktionsspezifikation und ihre Tests. Schreibe eine Implementierung, die alle Tests besteht, aber absichtlich falsch oder unvollständig ist.” Schauen wir, was es findet.
Schicken wir den Cheat (Schummel-Code) an die Gruppe zurück, die den Grader geschrieben hat. (Genau das tut ein schwaches Modell aus Versehen — und genau das lernt ein Modell, das nur fürs „Bestehen” belohnt wird, mit Absicht zu tun.)
Ergebnis: eine falsch-aber-bestehende Implementierung der Aufgabe A der Nachbargruppe, zurückgegeben — plus ein Satz dazu, was deren Tests nie geprüft haben.
Phase 5 — Verstärken und entscheiden, wann es reicht (~8 Min)
Nehmen wir, was die Nachbargruppe unserem Aufgabe-A-Grader angetan hat, und reparieren ihn. Fügen wir Tests hinzu, bis der Cheat durchfällt. Dann beantworten wir die schwierige Frage ehrlich:
- Woher wissen wir, dass er jetzt stark genug ist? „Er fängt diesen Cheat” ist nicht dasselbe wie „er fängt die Cheats, die wir nicht gesehen haben.”
- Argumentieren wir in den Begriffen, die wir schon haben: Welche Zeilen und Zweige unserer Referenz hat der Cheat ausgenutzt, die unsere Tests nie ausgeführt haben? Wenn wir einen kleinen absichtlichen Fehler — eine Mutation wie
i+1stattioder<statt<=— in eine korrekte Lösung einbauten: Würden unsere Tests ihn fangen? - Bemerken wir den stillen Motor des Ganzen: Jeder neue Test braucht die richtige Antwort für seine Eingabe. Woher kam diese Antwort jedes Mal? (Schauen wir auf die Referenz, die wir in Phase 2 behalten haben.)
- Und der Versuche-Faden: In Phase 3 haben wir dem Modell 5 Versuche gegeben. Würden wir es als „bestanden, wenn ≥1 von 5 funktioniert” oder als „bestanden, nur wenn alle 5 funktionieren” werten? Wählen wir das, was zu unserer 50-€-Wette passt, und sagen wir, warum die beiden Zahlen enorm auseinanderliegen können.
Ergebnis: unser verstärkter Aufgabe-A-Grader, ein Satz dazu, warum er jetzt stark genug ist (in Begriffen von Coverage / Mutation), und unsere Wahl zwischen „≥1 von 5” und „alle 5”.
Phase 6 — Präsentieren und gemeinsam das Evaluations-Playbook der Klasse bauen (~15 Min)
Das ist der Sinn der ganzen Übung — geben wir ihm also den meisten Raum. Jede Gruppe gibt einen knappen 3-minütigen Kurzbericht — kein poliertes Ergebnis, sondern was wir gefunden haben und was wir nicht klären konnten:
- Unsere Aufgabe A und der Cheat, der einen Grader geschlagen hat (unseren oder den der Nachbargruppe) — warum hat er bestanden?
- Die Reparatur und wie wir entschieden haben, dass sie genügt — oder warum wir nicht sicher sind.
- Die Einschätzung unserer Gruppe zu den zwei großen Fragen: Was macht einen Grader gut? und Was kann selbst dann schiefgehen, wenn jeder Test grün ist?
- Was haben wir durch Copilot eigentlich gemessen?
- Eine Frage, die wir nicht beantworten konnten.
Während die Gruppen sprechen, baut die Klasse gemeinsam eine Tafel — die Lehrperson schreibt jeden Punkt mit, sobald er genannt wird: jedes neue Prinzip für einen guten Grader, jede neue Art, einen Grader zu täuschen, jeden Cheat, jede offene Frage. Das Ziel ist das kollektive Bild, das keine einzelne Gruppe allein erreicht hat; am Ende ist die Tafel das erste Evaluations-Playbook der Klasse, geschrieben vom ganzen Raum. Es gibt nichts abzugeben — diese Tafel und die Verschiebung im Denken sind das gesamte Ergebnis. Lassen wir die offenen Fragen unbeantwortet stehen: Sie sind der Lehrplan für das, was als Nächstes kommt, und der Sinn ist, nach der Vorlesung zurückzuschauen und zu zählen, wie viele sie davon abräumt.
Ergebnis (gemeinsam, nichts zum Abgeben): das gemeinsame Evaluations-Playbook der Klasse — die Good-Grader-Prinzipien, Fehlermodi und offenen Fragen aller Gruppen auf einer Tafel.
Hinweise für Lehrende
Empfohlene Einordnung — kalt durchführen
Dies ist eine Aktivität nach dem Prinzip des produktiven Scheiterns, also vor jeglichem Inhalt von Vorlesung 8 durchzuführen — idealerweise als eigenständiger Block in der Sitzung davor oder als Auftakt der Sitzung. Sie braucht kein Vorwissen aus der Vorlesung: Das 50-€-Szenario ist in sich geschlossen. Der ganze Wert liegt darin, dass die Studierenden zuerst mit dem Problem ringen und sich eigene (oft falsche) Modelle bilden, damit die Vorlesung später auf vorbereiteten Boden trifft. Die Aktivität nicht mit den Antworten auflösen. Jede Phase ist so angelegt, dass die Gruppen gegen eine Wand laufen, die ein späteres Kapitel benennt — die Aufgabe hier ist, die Wand spürbar zu machen und stehen zu lassen. Die Auflösungen gehören der Vorlesung.
Moderationshinweise
- Phase 1 darf leicht chaotisch wirken. Nicht zur „richtigen” Antwort lenken; die Meinungsverschiedenheit ernten. Erste Ideen — „eine App bauen und schauen, ob sie läuft”, „mit einer Referenzantwort vergleichen”, „ein Mensch bewertet”, „eine andere KI bewertet” — sind je ein eigener Faden.
- Die Vorgabe in Phase 2 ist der pädagogische Motor. „Automatisch + wiederholbar + über Modelle hinweg vertrauenswürdig” ist das, was ausführbare Tests und eine feste Aufgabenmenge mit einer Referenzantwort erzwingt. Landet eine Gruppe bei „eine andere KI bewertet”, ist das nicht falsch — für einen späteren Rückverweis als echte Technik (LLM-as-Judge) mit messbaren Verzerrungen notieren und „dem Vergleich nächsten Monat trauen” ihre Fragilität sofort aufzeigen lassen.
- Der vorgegebene Typ (Aufgabe A) ist der Vergleichbarkeits-Anker — weil die getauschte Aufgabe jeder Gruppe „ein Validierer”, „ein Parser” usw. ist, reimen sich die Brüche und die Rückverweise der Vorlesung sitzen sauber. Typen so zuteilen, dass jeder von mindestens zwei Gruppen gehalten wird.
- Die Copilot-Frage in Phase 3 ist der Schläfer. Die meisten Gruppen merken erst auf Nachfrage, dass sie ein System testen, nicht ein Modell. Auf das Leck achten: Eine Gruppe mit offener
test_*.pyoder Referenz im Editor, während Copilot die Lösung schreibt, hat gerade live den Lösungsschlüssel in den Kontext des Modells gefüttert — ein benannter Fehlermodus, dem sie später begegnen. Das ist ein Geschenk; für den Rückverweis aufheben. - Funktionen klein halten; eine grobe Aufgabe genügt. Der Bruch in Phase 4 braucht keinen polierten Benchmark.
- Beide Fehlerrichtungen lehren. Eine Gruppe, deren Grader zu strikt ist (er weist korrekte Lösungen ab), hat genauso viel gelernt wie eine, deren Grader zu schwach ist (er lässt falschen Code durch) — der zu-strikten Richtung in Phase 6 ebenfalls Raum geben; das ist die Hälfte, die Studierende vergessen.
- Es gibt nichts zu benoten. Dies ist ein Mindset-Aufbau, kein abzugebendes Produkt. Das Ergebnis ist die gemeinsame Tafel, die die Klasse in Phase 6 schreibt — Teilnahme würdigen, nicht Politur.
Rückverweis-Stichworte — hier nur Andeutungen, die genaue Zuordnung in den eigenen Notizen
Dies ist kein Debrief zum Vortragen und keine Vorschau zum Austeilen — es ist der Stichwortzettel, um nach dem Vorlesungsbeginn auf die Aktivität zurückzuverweisen („Erinnert ihr euch, als euer Grader den offensichtlich falschen Code bestehen ließ? Genau das ist es.”). Er ist bewusst entschärft: Die rechte Spalte deutet an, statt zu benennen, sodass Studierende, die in dieser Datei vorauslesen, Vorfreude bekommen, nicht die Pointen. Die genaue Zuordnung wunde Stelle → Kapitel in den eigenen Vorlesungsnotizen behalten — man weiß ja, wo jede landet — und die Zeile abrufen, wenn die Vorlesung sie erreicht. Die Artefakte der Gruppen (die Cheats, die kaputt-dann-reparierten Grader, die Fragen-Tafel) werden zu den laufenden Beispielen der Vorlesung.
| Worauf die Gruppe gestoßen ist | Was später daraus wird (Stichwort — Name erst im Vortrag) |
|---|---|
| „Unsere Tests ließen offensichtlich falschen Code bestehen" | Die mit Abstand wichtigste Lektion der Vorlesung — ihr ist ein ganzes Kapitel gewidmet. |
| Verstärken, bis ein eingebauter Fehler stirbt | Der Coverage-/eingebauter-Fehler-Instinkt ist genau das richtige Werkzeug; das Fachgebiet hat formale Namen dafür. |
| „Jeder neue Test brauchte die richtige Antwort → wir ließen unsere Referenz laufen" | Eine Standardtechnik wurde neu erfunden, ohne dass ihr Name genannt wurde. |
| „Wir haben Copilot getestet, nicht das Modell" | Ein Thema, das immer wiederkehrt: Die Hülle ist Teil der Messung. |
| Copilot liest die offene Testdatei / Referenz | Eine berüchtigte Art, wie Benchmark-Zahlen still aufgebläht werden. |
Korrekter Code fällt an Format / Fließtext / print statt return durch | Der unscheinbare Klempner-Schritt, der Scores still verschiebt. |
| „≥1 von 5" vs. „alle 5" | Zwei Metriken, die fast identisch aussehen und in entgegengesetzte Richtungen ziehen. |
| „Könnte das Modell diese Aufgabe schon gesehen haben?" | Die Krise, die die erste Hälfte der Vorlesung beschließt. |
| Der Entwurf „Funktion + Spezifikation + Tests" selbst | Hier wurde der Entwurf neu hergeleitet, mit dem das ganze Fachgebiet begann. |
| Absichtlich falsch-aber-bestehenden Code schreiben | Das mit Absicht zu tun, ist ein benanntes, reales Problem. |
Der eine Satz, den am Ende die Vorlesung landet — nicht die Lehrperson und nicht während dieser Aktivität: Ein Benchmark-Score ist nur so vertrauenswürdig wie sein Grader. Bis er ausgesprochen wird, haben die Studierenden ihn bereits erlebt, weil sie einen Grader kaputtgemacht und wieder aufgebaut haben.