Ich habe schon immer Textabenteuer gemocht, fand aber den Parser fast immer das Schwächste am Erlebnis. Das Spiel versteht take key, aber nicht pick up the key, oder umgekehrt, und das Verhältnis zwischen Spielerin und Welt fühlt sich an, als wäre es vom Parser geschrieben. baseline-engine ist mein Versuch, diese Reibung zu beseitigen, indem das LLM ausschließlich als Parser eingesetzt wird, ohne dass es jemals an der Geschichte mitschreibt.
Es ist eine Engine für interaktive Fiktion, in der die Geschichte vollständig vorab strukturiert ist – als Knoten und Verbindungen mit klaren Bedingungen und Effekten – und der Spielereingabetext durch ein LLM auf einen dieser Knoten abgebildet wird. Das LLM erfindet keine Inhalte. Es ordnet zu. Es ist auf eine endliche Menge von Intents beschränkt, die der Story-Autor definiert hat.
Die Welt selbst ist deterministisch. Was die Spielerin sieht, was sie aufnehmen kann, welche Türen sich öffnen – das alles wird vom Story-Graph entschieden. Was das LLM tut, ist die Frage zu beantworten: welche der erlaubten Aktionen meint diese Spielerin?
Diese Trennung erspart einem die schmerzhaftesten Fehlermodi, die LLMs als Geschichtenerzähler haben. Sie halluzinieren keine Türen, die nicht existieren. Sie behaupten nicht, dass der Schlüssel im Inventar ist, wenn er es nicht ist. Sie widersprechen nicht etwas, das vor zwanzig Zügen geschehen ist. Die Geschichte ist Daten, das LLM ist eine Funktion, die Eingaben auf erlaubte Übergänge abbildet.
Es bedeutet auch, dass dieselbe Geschichte mit kleineren, billigeren Modellen läuft. Wenn die einzige Aufgabe darin besteht, Sätze auf eine Liste von Intents abzubilden, kann ein 7B- oder 8B-Modell das gut. Erzählung über viele Züge in Konsistenz zu halten, ist es, woran sie scheitern.
Die Engine kommt mit einem Node-Editor, in dem Autoren Räume, Übergänge, Ereignisse und Bedingungen visuell anlegen. Jeder Knoten hat eine Beschreibung, eine Liste erlaubter Spieler-Intents und eine Liste von Effekten pro Intent. Die Effekte sind klein: Inventar-Mutationen, Variablensetzung, Sprung zu einem anderen Knoten.
Die Intent-Liste ist das, womit das LLM zu tun hat. Sie ist explizit, sortiert, oft kurz. Während der Laufzeit nimmt die Engine die freie Eingabe der Spielerin und fragt das Modell: welcher dieser Intents passt am besten? Wenn es einen passenden gibt, wird der Effekt angewendet. Wenn nicht, sagt das Spiel höflich, dass es das nicht versteht, und schlägt nahegelegene Möglichkeiten vor.
Geschichten verzweigen sich auf Ebene der Knoten, nicht auf Ebene des Modells. Wenn ich will, dass die Spielerin den Schlüssel entweder nimmt oder zerstört, sind das zwei explizite Übergänge. Das Modell entscheidet, welche der beiden ihre Eingabe meint, aber es entscheidet nicht, ob Zerstören eine erlaubte Aktion ist – das tut die Story-Autorin.
Das hört sich restriktiv an, ist aber genau die Eigenschaft, die das Spiel überhaupt aufführbar macht. Eine Spielerin, die die Welt zerstört, indem sie etwas Unerwartetes eingibt, ist kein Bug, den die Story-Autorin fürchten muss. Es gibt nichts Unerwartetes, was sie tun könnte. Sie kann höchstens etwas eingeben, das auf nichts abgebildet wird, und das Spiel sagt freundlich nein.
Das Modell liegt manchmal falsch. Wenn das passiert, kann die Spielerin korrigieren: das Spiel bietet eine "das war nicht, was ich meinte"-Option mit einer Liste naheliegender Intents. Die Korrektur wird gegen den ursprünglichen Eingabe-String gecacht, im Scope der aktuellen Geschichte. Beim nächsten Mal, wenn jemand etwas Ähnliches tippt, kürzt der Cache den LLM-Aufruf ab und routet direkt auf den richtigen Intent.
Das hat zwei Funktionen gleichzeitig. Es ist eine Latenz-Optimierung, weil gecachte Eingaben den Modell-Roundtrip überspringen. Es ist auch eine Qualitätsschleife: der Parser wird über die Zeit besser für diese Geschichte – nicht weil das Modell lernt, sondern weil sich die Korrekturen zu einem Phrase-zu-Intent-Wörterbuch verdichten, das mit der Nutzung wächst. Der Cache erledigt am Ende dieselbe Arbeit, die ein menschlicher Autor mit dem Anlegen von Synonymen erledigt – nur getrieben von echtem Spielerverhalten statt von Vermutungen.
Mehrere KI-Backends, mit lokalem Default. Ich wollte keine Engine ausliefern, die einen OpenAI-Key voraussetzt. Die Engine spricht mit vier Arten von Backends: Ollama (lokal, Default, mein bevorzugter Pfad), OpenAI, Gemini, und einem "custom"-Fallback, der einfach ein generischer HTTP-Endpunkt mit einem Modellnamen ist. Auf den lokalen Pfad bin ich am stolzesten. Ein kleines instruction-tuned Modell wie Gemma reicht für Parser-Aufgaben mehr als aus: es bildet einen Satz auf einen von wenigen Intents ab, es schreibt keine Prosa. Du kannst die Engine starten, eine Geschichte hosten und freie Spielereingabe auf deiner eigenen Maschine parsen, ohne irgendwem etwas zu zahlen oder Spielertext an Dritte zu senden. Es ist nicht die schnellste Variante, aber sie funktioniert, sie ist privat, und sie kostet nichts.
LLMs als Parser sind eine deutlich kleinere und verlässlichere Anwendung als LLMs als Autoren. Einen freien Satz auf einen von N Optionen abzubilden, ist etwas, das aktuelle Modelle gut können. Konsistente Fiktion über viele Züge zu erzeugen, machen sie schlecht. Setze die Grenze auf der richtigen Seite dieser Linie und das System wird brauchbar.
Die Spielerkorrektur-Schleife hat sich als der interessanteste Teil des Projekts herausgestellt, und sie war fast nachträglich. Die ursprüngliche Spec war nur "lass die Spielerin mit anderer Formulierung erneut versuchen". Die Korrektur gegen den Eingabe-String zu cachen, ist daraus gefallen, dass ich nicht für denselben Fehler zweimal LLM-Kosten zahlen wollte. Dass es den Parser auch über die Zeit klüger macht, war ein Nebeneffekt.
