Teacher: ein KI-Tutor mit Tool-Calling, abgestuften Hinweisen und Spaced Repetition
Projekt-Übersicht
Teacher ist eine Lernplattform mit einem KI-Tutor im Zentrum. Lehrkräfte stellen Lernmaterial ein, das System zerlegt es in Lernhäppchen und erzeugt dazu Übungen. Lernende arbeiten sich durch Kurse, beantworten Fragen in freier Form und bekommen ein begründetes Feedback. Das Projekt hat zwei Ausbaustufen: Zuerst entstand ein dialogischer Tutor-Agent, der per Tool-Calling Lernmaterial und Fragen lädt, bei falschen Antworten bis zu fünf zunehmend deutliche Hinweise gibt und am Ende eine Punktzahl von 1 bis 10 speichert. Darauf aufbauend kam eine Lern-Engine hinzu, die Antworten auf einer Skala von 0 bis 5 bewertet und daraus mit dem SM-2-Algorithmus plant, wann ein Lernhäppchen wiederholt werden sollte. Gebaut ist alles in TypeScript mit NestJS, Angular, LangChain.js und GPT-4o.
Ausgangslage
Wer Lernmaterial in Übungen übersetzt, macht immer wieder dieselben Schritte: Text in sinnvolle Abschnitte gliedern, Fragen formulieren, Musterlösungen schreiben, Antworten korrigieren, entscheiden, was wiederholt werden muss. Gerade das Korrigieren freier Antworten ist aufwendig, weil eine Antwort sinngemäß richtig sein kann, ohne wörtlich der Musterlösung zu entsprechen.
Klassische Lernsoftware umgeht das, indem sie auf Multiple Choice ausweicht. Damit prüft sie aber eher Wiedererkennen als Verstehen. Und sie hilft nicht, wenn jemand bei einer Frage hängt: Sie zeigt entweder nichts oder sofort die Lösung. Ein guter Tutor gibt dazwischen einen Anstoß – erst vorsichtig, dann deutlicher.
Mein Ansatz war deshalb, einem Sprachmodell die Rolle des geduldigen Tutors zu geben, es aber an echte Daten zu binden: Das Lernmaterial und die Fragen kommen aus der Datenbank, die Bewertung geht dorthin zurück. Das Modell soll unterrichten und bewerten, aber nicht selbst entscheiden, was gelernt wird.
Architektur
Das Backend besteht aus drei fachlichen Modulen: teacher für den Dialog-Agenten, course für Kurse, Lernhäppchen und Übungen und learning für Bewertung, Fortschritt und Wiederholungsplanung. Das Angular-Frontend hat getrennte Bereiche für Lehrkräfte (Kurse, Inhalte, beantwortete Fragen, Lernende) und Lernende (eigene Kurse, Lernansicht, Wiederholungs-Warteschlange, Chat mit dem Tutor).
flowchart TD
L["Lehrkraft stellt Lernmaterial ein"] --> CH["ChunkingService: Lernhäppchen mit Schwierigkeit und Voraussetzungen"]
CH --> EX["ExerciseGenerator: bis zu 11 Übungstypen"]
EX --> DB["Supabase: Kurse, Häppchen, Übungen, Fortschritt"]
S["Lernende Person"] --> NX["Nächstes Häppchen: fällige Wiederholung vor neuem Inhalt"]
NX --> UE["Übung beantworten"]
UE --> EV["KI-Bewertung auf Skala 0 bis 5"]
EV --> SM["SM-2: Intervall, Ease-Faktor, Status"]
SM --> DB
S --> TA["TeacherAgent: Dialog mit GPT-4o"]
TA --> T1["Tool getContent"]
TA --> T2["Tool getNextQuestion"]
TA --> T3["Tool saveQuestionRating"]
T1 --> DB
T2 --> DB
T3 --> DB
Beide Wege teilen sich die Datenbank, sind im aktuellen Stand aber noch getrennte Lernmodi: Der Tutor-Agent arbeitet mit Fragen zu einem Inhalt, die Lern-Engine mit Übungen zu Lernhäppchen. Das Zusammenführen – etwa dass der Agent seine Bewertungen in die Wiederholungsplanung einspeist – steht im Konzept als nächster Schritt.
Technische Umsetzung
Der Tutor-Agent und seine drei Tools
Der TeacherAgent ist ein Tool-Calling-Agent auf Basis von LangChain.js mit GPT-4o. Er hat genau drei Tools:
getContentlädt das Lernmaterial zum Inhalt der Sitzung und liefert Text und Thema. Es wird zu Beginn einmal aufgerufen; damit kennt das Modell die Grundlage, auf der es Hinweise formuliert und Antworten beurteilt.getNextQuestionholt die nächste Frage samt Musterlösung aus einer vorab festgelegten Liste von Fragen-IDs. Ist die Liste leer, liefert es eine Ende-Kennung, und der Tutor schließt die Sitzung ab.saveQuestionRatingspeichert nach jeder abgeschlossenen Frage Punktzahl, Begründung und Zahl der gegebenen Hinweise.
Die Parameter von saveQuestionRating sind per Zod-Schema eingegrenzt, sodass das Modell keine Punktzahl außerhalb der Skala und nicht mehr als fünf Hinweise melden kann:
const saveRatingSchema = z.object({
questionId: z.string().describe('The ID of the question being rated'),
score: z.number().min(1).max(10).describe('Score from 1-10'),
reasoning: z.string().describe('Detailed reasoning for the score'),
hintsGiven: z.number().min(0).max(5).describe('Number of hints given to the student'),
});Die Bewertung landet in einer eigenen Tabelle, verknüpft mit Frage und lernender Person. So sieht die Lehrkraft später nicht nur die Punktzahl, sondern auch, wie viel Hilfe nötig war und warum der Tutor so bewertet hat.
Die Schleife und der Sitzungszustand
Der Ablauf folgt dem klassischen Agent-Muster: Modell aufrufen, enthaltene Tool-Aufrufe ausführen, Ergebnisse als ToolMessage zurückgeben, so lange wiederholen, bis eine Antwort ohne Tool-Aufruf kommt. Ein fehlgeschlagener Tool-Aufruf beendet den Dialog nicht, sondern geht als Fehlermeldung an das Modell zurück, das darauf reagieren kann:
for (const toolCall of response.tool_calls) {
try {
const toolStartTime = new Date();
const toolResult = await this.executeToolCall(toolCall, session);
currentMessages.push(new ToolMessage({
content: JSON.stringify(toolResult),
tool_call_id: toolCall.id || ''
}));
const toolDurationMs = new Date().getTime() - toolStartTime.getTime();
session.addToolResultToHistory(toolCall.id || '', toolResult, toolDurationMs);
} catch (error) {
const errorMessage = `Error executing tool: ${error}`;
currentMessages.push(new ToolMessage({
content: errorMessage,
tool_call_id: toolCall.id || ''
}));
session.addToolResultToHistory(toolCall.id || '', errorMessage);
}
}Neben dem Chatverlauf führt die Sitzung einen kleinen eigenen Zustand mit, der an die Tool-Aufrufe gekoppelt ist: die aktuelle Frage und die Zahl der bereits beantworteten Fragen.
Ein Sitzungsstart wird über einen eigenen Endpunkt mit Inhalt, Fragenliste und lernender Person initialisiert; danach läuft der Dialog über einen Chat-Endpunkt im OpenAI-Antwortformat. Der vollständige Verlauf inklusive Tool-Aufrufen und Laufzeiten wird nach jeder Antwort asynchron gespeichert, ohne die Antwort zu verzögern. Inaktive Sitzungen werden nach 30 Minuten aus dem Speicher entfernt.
Pädagogik im Prompt: abgestufte Hinweise und ein fester Bewertungsrahmen
Das Verhalten des Tutors ist im Systemprompt festgelegt. Bei einer vollständig richtigen Antwort gibt es Lob und direkt die Bewertung. Bei einer teilweise richtigen Antwort würdigt der Tutor die richtigen Teile und weist auf das Fehlende hin. Bei einer falschen Antwort gibt er einen Hinweis – höchstens fünf pro Frage, beginnend subtil und zunehmend deutlicher, der fünfte verrät fast die Lösung. Danach, oder wenn die lernende Person aufgibt, löst er auf.
Die Bewertung ist an die Hilfe gekoppelt: 10 Punkte ohne Hilfe, 8 bis 9 mit einem Hinweis, 6 bis 7 mit zwei bis drei, 4 bis 5 mit vier bis fünf Hinweisen, darunter Teilpunkte für einzelne richtige Aspekte trotz maximaler Hilfe. Sinngemäß richtige Antworten zählen, mehrteilige Antworten werden Teil für Teil geprüft. Das Feedback folgt einer festen Struktur: Punktzahl mit Begründung, was gut war, was fehlte, ein Lernhinweis und die Überleitung zur nächsten Frage. Ein ausgearbeiteter Beispieldialog im Prompt zeigt das an einer konkreten Frage, einschließlich der Stelle, an der saveQuestionRating aufgerufen werden muss. Der Prompt beschreibt also nicht nur, was der Tutor tun soll, sondern auch, an welcher Stelle im Gespräch welcher Tool-Aufruf fällig ist.
Inhalte aufbereiten: Lernhäppchen und Übungen
Für die zweite Ausbaustufe wird Lernmaterial nicht mehr als Ganzes behandelt. Der ChunkingService lässt GPT-4o (Temperatur 0,3) den Text in Häppchen von etwa 200 bis 500 Wörtern zerlegen. Jedes Häppchen behandelt einen abgrenzbaren Teilaspekt, bekommt einen Titel, einen Schwierigkeitsgrad von 1 bis 5 und eine Liste von Voraussetzungen, also früheren Häppchen, die verstanden sein sollten. Der Originaltext soll dabei erhalten bleiben. Die Antwort wird geprüft: Einträge ohne Titel oder Inhalt fallen heraus, der Schwierigkeitsgrad wird auf 1 bis 5 begrenzt, Voraussetzungen müssen Zahlen sein.
Der ExerciseGenerator erzeugt pro Häppchen Übungen in bis zu elf Typen: Multiple Choice, Lückentext, offene Frage, Zusammenfassung, Karteikarte, Sortieraufgabe, Teach-Back, Fehlerfindung, Analogie, Mikro-Szenario und Begriffsdefinition. Jeder Typ hat ein eigenes JSON-Format mit typspezifischen Feldern, etwa die richtige Reihenfolge bei Sortieraufgaben oder die Liste der eingebauten Fehler. Eine Regel ist im Prompt ausdrücklich hervorgehoben: Das Fragefeld muss für sich allein verständlich sein, generische Aufforderungen wie „Fülle die Lücken aus“ ohne den eigentlichen Lückentext sind ausdrücklich verboten. Lehrkräfte können Übungen vorab als Vorschau erzeugen und mit einer freien Anweisung überarbeiten lassen, bevor sie gespeichert werden.
Bewertung und SM-2
In der Lernansicht beantwortet die lernende Person eine Übung. Ein Bewertungsaufruf an GPT-4o (Temperatur 0,3) bekommt das Lernhäppchen, den Übungstyp, die Frage, die erwartete Antwort und die Antwort der Person und liefert eine Qualität von 0 bis 5, ein Feedback und ob die Antwort als richtig gilt. Antwort und Bewertung werden gespeichert, und die Qualität fließt direkt in den SM-2-Algorithmus:
if (quality >= 3) {
if (repetitions === 0) interval = 1;
else if (repetitions === 1) interval = 3;
else interval = Math.round(interval * ease_factor);
repetitions += 1;
} else {
repetitions = 0;
interval = 1;
}
ease_factor = Math.max(1.3, ease_factor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)));Daraus ergeben sich das nächste Wiederholungsdatum und ein Status: „learning“ nach einem Fehlschlag, „review“ während der Wiederholungsphase und „mastered“, sobald das Intervall 21 Tage erreicht. Bei der Frage, was als Nächstes drankommt, haben fällige Wiederholungen Vorrang; erst danach folgt das nächste noch nicht begonnene Häppchen in Kursreihenfolge. Pro Kurs lässt sich der Fortschritt abrufen: neue, lernende, wiederholende und gemeisterte Häppchen sowie die Zahl der heute fälligen.
Fehlerbehandlung
Die Tools fangen Fehler ab und geben eine verwertbare Antwort zurück, statt den Dialog abzubrechen. Wenn keine Frage mehr verfügbar ist, liefert getNextQuestion eine Ende-Kennung, auf die der Tutor reagieren kann. Das Speichern der Sitzung ist bewusst „fire and forget“: Ein Fehler wird geloggt, beeinträchtigt aber nicht die Antwort an die lernende Person. Die JSON-Antworten der Generatoren und der Bewertung werden vor dem Speichern geparst; schlägt das fehl, gibt es einen klaren Fehler statt halb gespeicherter Daten.
Betrieb
Für die Entwicklung gab es einen Testrunner, mit dem ich den Tutor in der Konsole wie eine lernende Person befragen konnte, zunächst mit Beispielmaterial zum Thema Kaufvertrag. Das Datenmodell der Lern-Engine ist in SQL-Skripten für Kurse, Einschreibungen, Lernhäppchen, Übungen, Fortschritt, Antworten und Lernsitzungen festgehalten. Der Konzeptstand nennt als offene Punkte unter anderem PDF-Upload, Fortschrittsdiagramme, einen adaptiven Mix der Übungstypen und die Erweiterung der Tools des Tutor-Agenten. Das Projekt ist ein funktionsfähiger Prototyp; ein Einsatz mit echten Lerngruppen steht noch aus.
Ergebnis
Teacher bringt auf einer gemeinsamen Datenbasis zwei Bausteine zusammen, die in vielen Lernanwendungen fehlen: einen Tutor, der im Gespräch hilft, statt nur richtig oder falsch zu melden, und eine Wiederholungsplanung, die sich an der tatsächlichen Leistung orientiert. Beide zu einem durchgehenden Lernfluss zusammenzuführen, ist der nächste Schritt. Lehrkräfte bekommen aus einem Text in wenigen Schritten gegliederte Lernhäppchen und vielfältige Übungen, die sie vor dem Speichern prüfen und nachschärfen können. Für jede Bewertung ist nachvollziehbar, wie viel Hilfe nötig war und wie das Modell seine Punktzahl begründet hat. Das System übernimmt damit Arbeitsschritte, die sonst manuell beim Aufbereiten von Material, beim Korrigieren freier Antworten und beim Planen von Wiederholungen anfallen.
Was ich dabei gelernt habe
- Pflichtschritte gehören in den Code, nicht in den Prompt. Ein Prompt mit Beispieldialog kann sehr gut vorgeben, an welcher Stelle ein Tool-Aufruf fällig ist. Was zwingend passieren muss – etwa das Speichern einer Bewertung –, ist aber am verlässlichsten, wenn das Modell eine strukturierte Bewertung liefert und der Code das Speichern übernimmt.
- Harte Grenzen zählt der Code. Limits wie die maximale Zahl an Hinweisen lassen sich am sichersten durchsetzen, wenn jeder Hinweis als eigenes Ereignis durch den Code läuft, statt dass das Modell am Ende selbst berichtet, wie oft es geholfen hat.
- Strukturierte Ausgaben statt Freitextformaten. Ein Schema, das die Modell-API erzwingt, ist robuster als JSON, das nachträglich aus einer Freitextantwort gelesen wird. Beim Tutor-Agenten übernehmen das die Zod-Schemas der Tool-Parameter.
- Skalen früh vereinheitlichen. Der Dialog-Tutor bewertet von 1 bis 10, die Lern-Engine von 0 bis 5, weil SM-2 diese Skala erwartet. Für die Zusammenführung beider Wege braucht es eine eindeutige Abbildung zwischen beiden.
- Ein korrektes JSON-Format ist noch keine brauchbare Übung. Eine Übung kann formal stimmen und trotzdem unbrauchbar sein, wenn die Aufgabenstellung nur mit den Zusatzfeldern verständlich ist. Deshalb steht im Generator-Prompt eine eigene, ausdrücklich formulierte Regel, dass das Fragefeld immer für sich allein stehen muss – inhaltliche Anforderungen an das Ergebnis brauchen eigene Regeln neben dem Format.
Technologien
- TypeScript, NestJS (Backend), Angular (Oberflächen für Lehrkräfte und Lernende)
- LangChain.js mit Tool Calling und Zod-Schemas
- OpenAI GPT-4o für Tutor-Dialog, Chunking, Übungsgenerierung und Bewertung
- SM-2-Algorithmus für Spaced Repetition
- Supabase (PostgreSQL) für Inhalte, Fragen, Bewertungen, Fortschritt und Sitzungen
- Swagger für die API-Dokumentation