PROJEKTEINBLICK ·
Er sollte auch beim nächsten Mal funktionieren
Mein Assistent bekam immer mehr Fähigkeiten. Aber eine Funktion einmal erfolgreich vorzuführen war etwas anderes, als sie regelmäßig benutzen zu können. Nach einem Abbruch, einem Neustart oder einer verlorenen Verbindung musste die Anwendung wieder in einen brauchbaren Zustand kommen. Damit bekam die Arbeit zwischen den sichtbaren Funktionen mehr Gewicht.

Im letzten Teil ging es um die Zeit bis zur ersten hörbaren Antwort. Dabei war bereits klar geworden, dass Geschwindigkeit allein nicht genügte. Wenn nach einer schnellen Antwort der nächste Mikrofonklick nicht mehr funktionierte, hatte ich wenig gewonnen.
Je mehr Teile zusammenspielten, desto mehr musste ich auch ihre Übergänge betrachten. Sprachaufnahme, Modell, Sprachausgabe, Browser und externe Dienste hatten eigene Zustände. Sie mussten ebenso mit einem Fehler umgehen können wie mit dem erwarteten Ergebnis.
Was passiert, wenn etwas abbricht?
Beim Entwickeln einer neuen Fähigkeit ist der erfolgreiche Weg schnell im Mittelpunkt. Eine Eingabe kommt an, wird verarbeitet und liefert ein Ergebnis. Für meinen Assistenten brauchte ich aber auch Antworten auf die weniger schönen Fälle.
- Was bleibt zurück, wenn ich einen Vorgang abbreche?
- Was passiert, wenn ein Dienst nicht antwortet?
- Wird ein gestarteter Prozess beim Beenden wieder aufgeräumt?
- Ist die Anwendung anschließend für die nächste Eingabe bereit?
Gerade bei der Sprachverarbeitung war das wichtig. Aufnahme und Wiedergabe sollten nicht weiterlaufen, nur weil an einer anderen Stelle schon ein Fehler aufgetreten war. Und ein Hilfsprozess sollte nicht unbemerkt zurückbleiben, nachdem sein Besitzer beendet worden war.
Das Repository bekam dafür Prüfungen rund um Prozesse und Lebenszyklen. Es ging nicht nur um das Ergebnis einer einzelnen Methode, sondern darum, ob die Anwendung ihre Arbeit auch sauber abschließen konnte.
Gleichzeitigkeit brauchte klare Zuständigkeiten
Mit Streaming hatte ich bewusst dafür gesorgt, dass Dinge überlappten. Während das Modell weitergenerierte, konnte die Sprachausgabe bereits arbeiten. Diese Gleichzeitigkeit machte das Gespräch schneller, verlangte aber eine saubere Koordination.
Wer durfte einen Puffer verändern? Welcher Vorgang gehörte zu welchem Gesprächsdurchlauf? Was musste beendet werden, wenn eine neue Eingabe kam oder ein Abbruch ausgelöst wurde?
Solche Fragen ließen sich nicht durch eine schönere Oberfläche lösen. Die Zuständigkeiten mussten im Code klar sein. Ein Teil sollte nicht nebenbei Daten verändern, während ein anderer gerade damit arbeitete.
Ein guter Ablauf musste auch dann verständlich bleiben, wenn er vorzeitig endete.
Damit bekamen die Grenzen zwischen den Komponenten noch einen weiteren Nutzen. Sie halfen mir, festzulegen, wer Ressourcen besaß und wer sie wieder freigeben musste.
Auch gespeicherte Daten gehörten dazu
Gesprächsverlauf und Erinnerungen sollten über einen Neustart hinweg erhalten bleiben können. Dafür genügte es nicht, nur den normalen Schreibvorgang zu betrachten. Eine unterbrochene Speicherung durfte möglichst nicht die bisherige Datei zerstören.
Beim lokalen Speicher wurde deshalb zunächst eine temporäre Datei geschrieben und anschließend die bisherige Datei ersetzt. Das verkleinerte das Risiko, beim Schreiben einen unvollständigen Stand anstelle des vorherigen zu hinterlassen.
| Bereich | Worauf ich achten musste |
|---|---|
| Laufende Vorgänge | Abbruch und Rückkehr in einen bedienbaren Zustand |
| Hilfsprozesse | Start, Zuständigkeit und Beenden |
| Gespeicherte Daten | Vorhandenen Stand beim Schreiben möglichst erhalten |
| Konfiguration | Wirksame Einstellungen auch nach Änderungen nachvollziehen |
Mit externen Integrationen kamen außerdem Zugangsdaten hinzu. Im Projekt entstand dafür ein eigener Secret Store, der unter Windows DPAPI nutzt. Auch diese Daten brauchten einen bewussten Platz, statt beiläufig im Quellcode oder einer geteilten Beispielkonfiguration zu landen.
Tests hielten die Erwartungen fest
Bei all diesen Übergängen wurden Tests wichtig. Ich wollte nicht bei jeder neuen Idee wieder von Hand prüfen müssen, ob ein bereits geklärter Ablauf noch genauso funktionierte.
Ein Test konnte eine konkrete Erwartung festhalten: Ein Befehl erreicht den passenden Handler. Ein Abbruch beendet die zugehörige Arbeit. Ein Zwischenstand der Spracherkennung wird noch nicht als vollständiger Auftrag abgeschickt.
Dabei brauchte ich beide Seiten der Webanwendung im Blick. Eine korrekte Verarbeitung in C# half wenig, wenn JavaScript dieselbe Eingabe doppelt sendete. Umgekehrt konnte die Oberfläche einen Fehler im Serverablauf nicht ausgleichen.
Die Tests waren für mich damit auch eine Erinnerung an Entscheidungen. Sie hielten fest, welche Eigenschaften bei der nächsten Änderung erhalten bleiben sollten. Das machte nicht jede neue Erweiterung automatisch richtig, gab mir aber einen konkreten Weg, unbeabsichtigte Änderungen zu entdecken.
Weiterbauen mit einem tragfähigen Unterbau
Diese Arbeit brachte selten eine spektakuläre neue Ansicht hervor. Trotzdem gehörte sie zu dem Assistenten, den ich benutzen und weiterentwickeln wollte. Ich brauchte ein System, das ich auch nach einer Pause wieder verstehen konnte.

Das bedeutete nicht, dass irgendwann jede offene Frage erledigt war. Bekannte Grenzen und aufgeschobene Arbeiten blieben Teil des Projekts. Entscheidend war, sie benennen zu können und Änderungen gezielt anzugehen.
Mit diesem Unterbau konnte ich mich wieder Fähigkeiten widmen, deren Wirkung über den Bildschirm hinausging. Bei ihnen würde dieselbe Frage noch greifbarer werden: Was wurde angefordert, was war erlaubt, und was war tatsächlich passiert?













