Ticket Triage: Nichtdeterminismus die Stirn bieten

Ein Spektakel in drei Akten

„Zwei Seelen wohnen, ach! in meiner Brust“

Das mit dem Nichtdeterminismus beunruhigt uns Devs und Theoretiker:innen schon sehr. Wir wollen alles schön ordentlich und vorhersehbar haben. LLMs sind uns dabei ein unangenehmer Dorn im Auge. Ich verstehe das. Ich fühle den Schmerz.

Der praktische Produktmensch in mir muss aber sagen, Nichtdeterminismus ist nicht das Problem. Unsere Lebensrealitäten sind nichtdeterministisch und wir Menschen kommen im Allgemeinen ganz passabel damit zurecht.1

Wir verwechseln Determinismus mit Steuerungsfähigkeit. Wenn ich morgens zur Arbeit fahre, weiß ich auch nicht ganz genau, wie lange ich brauchen werde. Vielleicht sind einige Ampeln rot, vielleicht ist viel Verkehr, vielleicht läuft jemand seeeehr langsam über den Zebrastreifen. Wichtig ist mir, dass ich pünktlich ankomme. Wenn es heute zu knapp wird, weil es eine neue Baustelle gibt, dann fahre ich morgen eine andere Strecke oder mache mich früher auf den Weg.

Pendeln ist nervig, ja. Zur Katastrophe wird es, wenn ich mich den grausamen Göttern des Straßenverkehrs wehrlos ausgesetzt fühle. Erlebe ich mich hingegen als steuerungsfähig, kann ich damit umgehen.

Um steuerungsfähig zu sein, brauche ich drei Dinge:

  1. Erfolgsbedingungen: Ich muss wissen, was es heißt, pünktlich zu sein und wie ich das feststelle.
  2. Nachvollziehbarkeit: Ich muss wissen, wo ich lang gefahren bin und welche Faktoren meinen Weg beeinflusst haben.
  3. Einflussnahme: Ich muss wenigstens einige dieser Faktoren meinerseits beeinflussen können.

Genau diese Dinge haben Tom und mich auch bei unserer Arbeit an Ticket Triage beschäftigt. Ticket Triage ist eine Anwendung zur KI-gestützten Analyse von Bug-Tickets. Der Nichtdeterminismus ist aus unserer Anwendung nicht wegzudenken und Steuerungsfähigkeit ist notwendig, um die Entwicklung und Verbesserung voranzutreiben.

Für Ticket Triage stellen wir uns folgende Fragen:

  1. Sind die Analyseergebnisse nützlich? Helfen sie Teams und Organisationen, bessere Entscheidungen zu treffen?
  2. Welche Analyse hat welches Ergebnis erzielt? Was waren die Parameter?
  3. Welche Parameter sind wie konfigurierbar?

Nützlichkeit und Konfigurierbarkeit liegen an der Oberfläche. Sie sind Kernaspekte der Funktionalität. Was nicht heißt, dass es einfache Fragen sind.2 Aber es waren zumindest für uns die offensichtlicheren Fragen. Fragen, die uns kontinuierlich beschäftigt haben und weiter beschäftigen werden.

Das Problem der Nachvollziehbarkeit hingegen hat uns auf besondere Weise herausgefordert.

Erster Akt: Wir loggen, dass etwas passiert.

Am Anfang war der Analysis Run noch kein komplexes Objekt. Er war eher Buchhaltung. Eine Analyse wurde angestoßen, sie hatte einen Status, vielleicht mehrere Versuche, vielleicht ist sie fehlgeschlagen, vielleicht hat sie Tokens gekostet. Praktisch. Notwendig.

Das ist so, als würde ich für den Arbeitsweg nur notieren: „Bin losgefahren. Bin angekommen. Hat 42 Minuten gedauert.“ Das ist besser als nichts. Aber wenn ich morgen wieder zu spät bin, weiß ich trotzdem nicht, was zu tun ist.

Für Ticket Triage hieß das: Wir konnten sehen, dass Analysen liefen. Das war’s. Und das ist zu wenig, weil es nicht genügt, um Drift transparent zu machen.

Ein Analyseergebnis kann auf mindestens zwei Arten altern.

  1. Das Ticket selbst verändert sich. Neue Kommentare, neue Labels, neue Beschreibungen, neue Erkenntnisse. Dann ist nicht mehr aktuell, was analysiert wurde.
  2. Die Analyse verändert sich. Der Prompt wird angepasst. Das Modell wird gewechselt. Eine Kontextbeschreibung wird präzisiert. Eine Output-Restriction wird enger gefasst. Dann ist nicht mehr aktuell, wie analysiert wurde.

Aus beidem resultiert Drift. Das klingt erstmal schlecht. Aber Drift ist nur die eine Seite der Veränderungs-Medaille. Die andere ist Entwicklung: Wir wollen lernen. Wir wollen Prompts verbessern. Wir wollen Kontext schärfen und Tickets updaten. Wir wollen bessere Ergebnisse.

Ohne Nachvollziehbarkeit wird gezielte Entwicklung zu erratischer Veränderung.

Zweiter Akt: Wir versionieren, was passiert.

Wie schaffen wir also Nachvollziehbarkeit? Einfache Antwort: Wir versionieren den Analyseprozess – von „Das ist eine Root-Cause-Analyse.“ zu „Das ist diese Root-Cause-Analyse, mit dieser Konfiguration, in dieser Version.“ Unsere Ergebnisse bekamen damit eine erkennbare Herkunft.

Auch Abhängigkeiten wurden sichtbar. Wenn ein Prozess systemische Muster findet und ein anderer Prozess Tickets diesen Mustern zuordnet, dann ist das zweite Ergebnis abhängig vom ersten. Gibt es nun eine Änderung im Ticketset, ist es zwar möglich, die „alten“ Muster auf das aktualisierte Set anzuwenden, aber es sollte nicht einfach unbemerkt passieren, da die Muster nicht auf Grundlage des Sets bestimmt wurden. Es kann sinnvoll sein, das zu machen, weil das Bestimmen der Muster verhältnismäßig teuer ist. Ist aber die Differenz zwischen altem und neuem Ticketset groß, ist es gefährlich, weil die Muster ggf. nicht mehr viel mit den Tickets zu tun haben.

Klingt umständlich? Ist es auch. Der Punkt ist, auch das sollte erkennbar werden und es stellte sich heraus, dass unsere Prozessversionierung das nicht leisten konnte: Sie war zu grob.

Wir haben uns also die Frage stellen müssen, was ist „die Analyse“? Ist es der Prompt? Das Modell? Der Systemkontext? Die Outputstruktur? Makros, wie zuvor bestimmte Muster im obigen Beispiel? Die Tickets selbst?

… Alles zusammen? Ja … Leider.

Dritter Akt: Blueprint, Specification und Run betreten die Bühne.

Unser Durchbruch war das Trio aus Analysis Blueprint, Analysis Specification und Analysis Run.

Der Analysis Blueprint ist die bearbeitbare Vorlage: Was soll ein Analyseprozess tun, mit welchem Modell, welcher Instruction und welchem Ergebnisformat?

Die Analysis Specification ist der eingefrorene Snapshot: Welche konkrete Version dieser Vorlage, mit welchem Kontext, wurde tatsächlich verwendet?

Der Analysis Run ist die Ausführung: Diese Specification wird auf genau dieses Ticketset angewendet und produziert konkrete Ergebnisse.

Sie sind das Herzstück unserer Observability-Logik. Lasst mich also etwas genauer darauf eingehen.

Analysis Blueprint

Ticket Triage nutzt verschiedene Analyseprozesse. Sie sind in sich abgeschlossene Vorgänge. Sie können aufeinander aufbauen, laufen aber unabhängig voneinander.

Der Analysis Blueprint ist die Werkbank eines Analyseprozesses.

Für jeden Prozesstyp gibt es genau einen Blueprint. Darin wird beschrieben, was der Prozess tun soll und welches Ergebnis er liefern soll: Anweisung, Modell, Outputstruktur.

Wichtig ist: Der Blueprint ist veränderbar. Hier dürfen wir schrauben, verbessern und ausprobieren. Wenn eine Zusammenfassung zu vage ist oder eine Root-Cause-Analyse zu oberflächlich, arbeiten wir an einer neuen Version des Blueprints.

Änderungen landen zunächst in einem Blueprint-Draft. Erst wenn ein Draft tatsächlich in einem Run verwendet wird, entsteht daraus ein fixierter Stand – ein Konzept das man beispielsweise aus Git als Commit kennt. Das spart Versionslärm. Uns interessieren nicht alle Zwischenstände beim Prompt-Basteln, sondern die Konfigurationen, die tatsächlich Ergebnisse erzeugt haben.

Der Blueprint ist stabil in seiner Identität: ein Blueprint pro Prozess. Seine Inhalte sind bearbeitbar. Versioniert wird, was wirklich verwendet wird.

Analysis Specification

Der Blueprint allein ist allerdings noch nicht die Antwort auf die Frage: Was wurde damals genau ausgeführt? Die Analysis Specification friert den Blueprint zusammen mit zusätzlichen Parametern für eine konkrete Ausführung ein:

Sie enthält die Version des Blueprints und die verwendeten Kontext-Blöcke (wie etwa Metainformationen über die Organisation oder das Softwaresystem) und dynamische Bestandteile (wie die systemischen Muster, die für die Zuordnung gebraucht werden, aber selbst auch Analyseergebnisse sind). Daraus entsteht eine eindeutige Signatur.

Das ist wichtig, weil eine Analyse nicht nur aus einem Prompt besteht. Vielleicht bleibt beispielsweise die Anweisung an sich gleich, aber die Systembeschreibung im Kontext wurde überarbeitet. Dann ist die Analyse fachlich nicht mehr dieselbe, auch wenn der Prozessname und sogar die Blueprint-Version gleich geblieben sind.

Warum ist das nicht einfach alles Teil des Blueprints?

Weil diese Bestandteile unterschiedliche Lebenszyklen haben. Kontext-Blöcke können in mehreren Blueprints verwendet werden. Manche dynamischen Bestandteile werden erst beim Run sinnvoll aufgelöst.

Die Specification ist deshalb die Klammer um alles, was für eine konkrete Analysekonfiguration zusammenkommt. Sie ist unveränderlich und wiederverwendbar. Wenn dieselbe Kombination schon existiert, muss keine neue Specification erzeugt werden.

Gleiche Specification heißt: gleiche Analysebedingungen.

Analysis Run

Der Analysis Run ist der konkrete Job.

Er verbindet eine Analysis Specification mit einem eingefrorenen Ticketset: Genau diese Tickets in genau diesen Revisionen.

Das ist entscheidend, weil Tickets lebendig sind. Beschreibungen ändern sich, Kommentare kommen hinzu, Labels werden gesetzt oder entfernt.

Der Run sagt: Diese Specification wurde auf diese Ticket-Revisionen angewendet.

Er hat einen Status: gestartet, abgeschlossen, abgebrochen oder fehlgeschlagen. Innerhalb des Runs entstehen die einzelnen Analysis Calls, also die konkreten Modellaufrufe, typischerweise einer pro Ticket.

Auch hier hilft die Signatur- und Revisionslogik. Wurde dieselbe Ticket-Revision mit derselben Specification schon analysiert, muss kein neuer Call passieren. Das spart Tokens, aber noch wichtiger: Es macht Gleichheit sichtbar und verhindert unnötige Ergebnisvariation: Wenn sich fachlich nichts geändert hat, wollen wir keine neuen Unterschiede produzieren, nur weil wir denselben Input noch einmal durch ein nichtdeterministisches System schleifen.

Schluss

Am Ende des Spektakels blicken die edlen Held:innen dem Nichtdeterminismus furchtlos ins ach so unstete Antlitz. Der Vorhang fällt.

Wir haben ein System gebaut, das seine eigenen Bedingungen kennt.

Der Blueprint gibt uns die bearbeitbare Form des Analyseprozesses.

Die Specification friert die konkrete Analysekonfiguration ein.

Der Run verbindet diese Konfiguration mit konkreten Ticket-Revisionen.

Die Analysis Calls erzeugen die einzelnen Ergebnisse.

Voilà: Nachvollziehbarkeit. Wir wissen nicht nur, dass etwas passiert ist. Wir wissen, was analysiert wurde, wie es analysiert wurde, ob sich seitdem etwas verändert hat und wo wir eingreifen können. Wir werden steuerungsfähig.


  1. Ob die Welt tatsächlich deterministisch ist oder nicht, möchte ich hier nicht diskutieren. Es reicht an dieser Stelle, dass die Welt uns zumindest nicht deterministisch erscheint. Denn das ist unsere Lebensrealität. (Die Analogie ist übrigens gar nicht schlecht. Wenn ich eine frankensteinsche Code Base habe, die zwar auf dem Papier perfekt deterministisch, in der Praxis aber so komplex ist, dass niemand das Verhalten vorhersagen oder reproduzieren kann, ist auch keinem geholfen.) ↩︎

  2. Mein letzter Post zum Confidence Score und der Ground Truth illustriert, dass die Frage nach Erfolgsbedingungen und deren Überprüfbarkeit ein ganz eigenes Kapitel ist. ↩︎