Architektur
Architektur der Beobachtbarkeit
Wie eine zusammengesetzte KI-Anwendung Laufzeit, Retrieval, Modellverhalten und Engineering-Entscheidungen untersuchbar macht: Traces nur mit Metadaten, über eine ID mit einem eigenen Anfrageprotokoll verbunden, aufgezeichnete Prüfungen, Offline-Evaluation auf eingefrorenen Daten und die verbleibenden blinden Flecken.
Veröffentlicht am 10. Oktober 2026
Mein Portfolio beantwortet Fragen zu meinen Fotos und dazu, wie ich Software baue. Wenn eine dieser Antworten falsch ist, zu lange dauert oder ausbleibt, kann die Ursache an sehr unterschiedlichen Stellen liegen. Es kann gewöhnlicher Anwendungscode sein, eine Retrieval-Regel, ein Modellanbieter, ein Modell, das etwas geschrieben hat, was seine Belege nicht tragen, ein Validator, der es zurückgewiesen hat, ein Sprachmodell auf dem Laptop der Besucherin oder des Besuchers, oder eine Entscheidung, die ich vor Monaten aus Gründen getroffen habe, die niemand aufgeschrieben hat. Diese Fallstudie handelt davon, wie das System jede dieser Stellen untersuchbar macht, und wo es das noch nicht kann.
Sie ist kein Rundgang durch Monitoring-Werkzeuge. Die Anwendung hat keine Dashboards, kein Alerting und keinen Dienst für Fehlerberichte. Was sie hat, sind wenige bewusst gebaute Beobachtungskanäle, jeder für eine bestimmte Art von Fehler, verbunden durch eine einzige Kennung dort, wo die Verbindung nützt. Einige ihrer blinden Flecken sind so wichtig wie alles, was sie sieht.
Das Problem
Eine klassische Webanwendung scheitert auf wenige erkennbare Arten: eine Exception, eine langsame Abfrage, ein fehlerhaftes Deployment. Ein Request-Log mit Statuscodes und Laufzeiten deckt die meisten davon ab.
Eine zusammengesetzte KI-Anwendung scheitert auf mehr Arten, und die meisten davon liefern HTTP 200:
- Retrieval liefert nichts oder die falschen Fotos, während jede Komponente genau wie vorgesehen arbeitet. Eine strenge Regel, die ihre Arbeit tut, sieht von außen genauso aus wie ein Fehler.
- Ein Modellanbieter ist nicht erreichbar, und ein Ausweichmodell antwortet stattdessen. Die Besucher sehen eine Antwort, und nichts verrät, dass sie von der zweiten Wahl stammt.
- Die Generierung liefert gültiges JSON mit einer Aussage, die die Belege nicht tragen. Ein Validator weist sie zurück, das System versucht es erneut, und die endgültige Antwort ist in Ordnung. Die Zurückweisung bleibt unsichtbar, wenn sie niemand aufzeichnet.
- Die Validierung verweigert die Antwort ganz. Eine Verweigerung sieht aus wie eine funktionierende Funktion.
- Die Spracherkennung läuft in einem Web Worker im Browser der Besucher. Fällt sie von der GPU auf die CPU zurück oder scheitert sie, erfährt der Server davon nie.
- Die Qualität verschlechtert sich langsam. Eine Ranking-Änderung kann einen Durchschnitt verbessern und genau die Anfragen verschlechtern, auf die es ankommt, und kein Laufzeitsignal kann sagen, welche Fotos ein Mensch sehen wollte.
Die Frage, die diese Architektur beantwortet, ist also enger als „läuft das System?“. Sie lautet: Wenn eine Antwort falsch ist, kann ich herausfinden, welche Schicht sie falsch gemacht hat, ohne in dem Moment zugeschaut zu haben?
Warum Request-Logs nicht reichen
Jede tRPC-Prozedur schreibt eine Zeitmesszeile: Name der Prozedur, Dauer, Erfolg oder Fehler. Daraus erfahre ich, dass eine Anfrage an den Fotografie-Assistenten neun Sekunden gedauert hat und erfolgreich war. Ich erfahre nicht, ob die neun Sekunden in Embeddings, im Reranker oder in einem zweiten Generierungsversuch steckten. Ich erfahre nicht, ob „erfolgreich“ eine Antwort oder eine Verweigerung bedeutet, oder wie viele Kandidatenfotos es gab, bevor ein Filter sie alle entfernt hat.
Mehr Logzeilen lösen das nicht, denn die fehlende Information hat Struktur. Eine Retrieval-Pipeline ist ein Baum von Stufen, von denen einige parallel laufen. Eine Generierung kann zwei Versuche haben, und jeder besteht vier getrennte Prüfungen oder scheitert daran. Eine brauchbare Aufzeichnung muss diese Struktur erhalten und einer einzelnen Anfrage zuordnen. Sie muss das außerdem tun, ohne die Frage der Besucher, den Prompt oder den Text des Modells in ein Drittsystem zu kopieren.
Fünf Ebenen statt einer Pipeline
Das Repository hat nicht eine Telemetrie-Pipeline. Es hat fünf Orte, an denen Belege landen, jeder für eine andere Frage, und einen wichtigen Ort, an dem gar keine Belege landen.
Drei Ebenen beobachten eine Anfrage, während sie läuft, und eine ID verbindet sie:
- Öffentliche KI-Anfrageeine zufällige ID pro Anfrage
- LaufzeitzeilenZeiten · Modellverbrauch · Retrieval-Audit · jede Anfrage
- KI-TraceSpans · Prüfereignisse · Trichter · nur Metadaten · EU-Region · 14 Tage
- AnfrageprotokollFrage · Ergebnis · Dauer · eigene Datenbank · 90 Tage
- Entwickler, im NachhineinAnfrage-Artefakt, nur für Admins
Die übrigen drei stehen außerhalb jeder einzelnen Anfrage:
- Offline-Evaluation: eingefrorene Datenbank-Snapshots, bewertete Benchmarks und ein bindungsneutrales Protokoll, ausgeführt, bevor eine Änderung live geht.
- Engineering-Protokoll: Berichte, Entscheidungsdokumente und Validator-Ausgaben in der CI; sie erklären, warum eine Änderung live ging.
- Der Browser: das Sprachmodell, sein GPU- oder CPU-Backend, Fallbacks und Wiedergabe. All das bleibt auf dem Gerät und wird nirgends gemeldet.
Die ersten drei Ebenen sind Laufzeit-Beobachtbarkeit. Evaluation und Engineering-Protokoll betreffen Entscheidungen: Sie erklären, warum das System so ist, wie es ist, nicht was bei einer bestimmten Anfrage passiert ist. Der Browser ist die bewusste Lücke.
Laufzeitsignale
Die Laufzeitebene ist schlicht und teilweise unvollständig, und so behandelt dieser Artikel sie auch.
Was es gibt:
- Eine Zeitmesszeile pro tRPC-Aufruf, aus einer Middleware, die jede öffentliche und jede Admin-Prozedur durchläuft, dazu eine Zeile pro Pipeline-Stufe, die den gemeinsamen Zeitmess-Helfer nutzt. Jede Zeile enthält den Namen der Stufe, die Dauer, Erfolg oder Fehler, die Art des externen Aufrufs (Claude, OpenAI, Cohere, die Datenbank), gegebenenfalls einen Cache-Status und die Trace-ID der Anfrage.
- Eine Verbrauchszeile pro Modellaufruf aus der gemeinsamen Schicht für strukturierte Generierung: welche Funktion aufgerufen hat, Prompt-Version, Modell, Anbieter, Ein- und Ausgabe-Tokens, Prompt-Cache-Tokens, Kosten, sofern der Anbieter sie meldet, und die Fallback-Route, falls eine genommen wurde.
- Prozessmetriken alle 60 Sekunden: Event-Loop-Verzögerung und Speicher, aus der Node-Laufzeit selbst.
- Eine Liveness-Probe, die die Hosting-Plattform alle 30 Sekunden aufruft. Sie berührt absichtlich nichts (keine Datenbank, kein Rendering, keinen Anbieter) und bleibt deshalb ansprechbar, während die Event-Loop mit einer langen KI-Anfrage beschäftigt ist. Sie beantwortet „lebt der Prozess?“, nicht „funktioniert die Anwendung?“.
All das geht auf die Standardausgabe. Die Hosting-Plattform hält es eine Weile vor. Nichts aggregiert es, und kein Alarm liest es.
Was es nicht gibt:
- Keinen Dienst für Fehlerberichte und keinen strukturierten Logger. Logzeilen sind Text mit Präfix, kein JSON.
- In Produktion wird ein Fehler aus einer tRPC-Prozedur nicht zentral protokolliert: Der Fehler-Hook der Route ist nur in der Entwicklung aktiv. Die Zeitmesszeile hält fest, dass der Aufruf gescheitert ist, aber nicht warum.
- Keine eigene Fehlerseite und nur eine React Error Boundary, um den 3D-Avatar, die im Fehlerfall nichts rendert und nichts meldet.
- Der Smoke-Test nach dem Deployment ruft die Startseite ab und darf scheitern, ohne das Deployment scheitern zu lassen.
Für ein Portfolio auf einer einzelnen Instanz ist das eine vertretbare Grundlage, aber eben nur eine Grundlage. Die reichere Beobachtbarkeit, um die es im Folgenden geht, gibt es nur dort, wo Fehler schwer zu sehen sind: in den KI-Pfaden.
Eine Kennung, drei Orte
Jede tRPC-Anfrage erhält beim Eintritt in die Middleware eine zufällige UUID. Die ID liegt im asynchronen Kontext von Node, sodass jeder Code innerhalb dieser Anfrage sie lesen kann, ohne sie als Parameter zu bekommen. Sie ist aus nichts abgeleitet, was die Besucher betrifft (weder IP-Adresse noch Cookie noch Frage), sodass zwei Anfragen derselben Person nichts gemeinsam haben.
Für die beiden öffentlichen KI-Oberflächen, den Engineering-Ask und den Fotografie-Assistenten, wird derselbe Wert an drei Orten verwendet:
- Eine öffentliche KI-Anfrageeine zufällige ID, beim Eintritt erzeugt
- Laufzeitzeilenjede Zeitmess- und Verbrauchszeile trägt sie
- Eigenes AnfrageprotokollFrage, Ergebnis, Dauer, die ID · 90 Tage
- KI-Trace nur mit Metadatendie ID ist Trace-ID und ID des Wurzel-Runs · 14 Tage
- Anfrage-Artefaktnur für Admins, bei Bedarf aus Protokoll und Trace gebaut
Der Wurzel-Span jedes KI-Traces übernimmt die Anfrage-ID als eigene ID, statt eine neue zu erzeugen. Diese kleine Entscheidung macht die Verknüpfung möglich: Die Trace-ID in einer Logzeile, die Trace-ID-Spalte in der Datenbank und der Wurzel-Run des exportierten Traces sind ein einziger Wert, nicht drei Werte mit einer Zuordnungstabelle, die auseinanderlaufen könnte.
Eine Ansicht nur für Admins listet die letzten öffentlichen Fragen und kann für jede davon bei Bedarf ein Anfrage-Artefakt bauen: den eigenen Datensatz, verknüpft mit dem exportierten Trace, als ein versioniertes JSON-Dokument. Dafür wird während der Anfrage nichts zusätzlich geschrieben. Details der Generierung (Modell, Tokens, Fallback-Route, Kosten) werden aus dem Trace gelesen, statt doppelt gespeichert zu werden.
Das ist der stärkste Teil der Architektur, und er hat klare Grenzen:
- Er deckt zwei Prozeduren ab. Die kleineren Fragen an den Kurator, die Sprachausgabe, die Generierung von Anschreiben und Lebenslauf und die Fotoanreicherung rufen ebenfalls Modelle auf. Sie erzeugen Laufzeitzeilen, aber keinen Trace und keinen Protokolleintrag.
- Die Aufbewahrungsfristen unterscheiden sich. Der Protokolleintrag bleibt 90 Tage, exportierte Traces verfallen nach 14. Für eine ältere Frage liefert das Artefakt die Hälfte der Anwendung und meldet als typisierten Status, dass der Trace nicht mehr existiert.
- Er endet am Server. Die ID wird nicht an den Browser zurückgegeben, also lässt sich eine Fehlermeldung von Besuchern keinem Trace zuordnen.
Was eine KI-Anfrage aufzeichnet
Innerhalb einer getracten Anfrage wird jede Stufe, die schon eine Zeitmessgrenze hatte, zu einem Span: einer benannten, zeitlich gemessenen, verschachtelten Arbeitseinheit. Ereignisse markieren Zeitpunkte innerhalb eines Spans. Die Schnittstelle kennt drei Operationen (Span beginnen, Span beenden, Ereignis aufzeichnen), und Attributwerte dürfen nur Zeichenketten, Zahlen oder Wahrheitswerte sein.
- Frageim eigenen System gespeichert; nie Teil des Traces
- Retrievalein Span pro Stufe · Trichter-Zählungen · Ausschlussgründe
- Evidence PackAnzahl der Fotos · Anbieter und Modell des Rerankers, Fail-open-Flag
- GenerierungModell · Anbieter · Prompt-Version · Tokens · Fallback-Route
- Prüfungenbestanden oder nicht, je Prüfung und Versuch · Zählungen · Grund für Wiederholung
- AntwortErgebnis · Verweigerungsgrund · Streaming-Meilensteine
Ein echter Trace des Fotografie-Assistenten, aufgenommen während der Entwicklung, hat etwa zwanzig Runs. Er zeigt Konzeptauflösung und Anfrageerweiterung mit ihren Aufrufen an die Embedding-Anbieter, lexikalisches und semantisches Retrieval nebeneinander, den Rerank-Aufruf, den Aufbau des Evidence Pack und zwei Generierungsversuche. Dazwischen zeigen die Ereignisse, dass die Aussagenprüfung beim ersten Versuch eine Aussage zurückgewiesen hat und beim zweiten jede Prüfung bestanden wurde. Vor dem Tracing hieß dieselbe Diagnose, Warnzeilen im Log über Zeitstempel zusammenzusuchen.
Token-Zählungen, Prompt-Cache-Zählungen und Kosten kommen von einer einzigen Stelle: der Schicht für strukturierte Generierung, die jeder Modellaufruf durchläuft. Keine Funktion kann also vergessen, sie zu melden. Wenn ein Anbieter nicht erreichbar ist und die Fallback-Kette zur nächsten Stufe wechselt, wird die Route als Attribut festgehalten: welche Stufe versucht wurde, warum gewechselt wurde, welcher Anbieter schließlich geantwortet hat.
Was ein Trace bewusst nicht zeigt, ist Text. Darum geht es in den nächsten beiden Abschnitten.
Retrieval: Zählungen und Gründe statt Kandidaten
Die Retrieval-Frage, die in der Praxis am meisten zählt, lautet: Warum kam hier nichts zurück? Fotosuche und Assistent übersetzen eine Frage in lexikalische Filter, semantische Anfragen, Orts- und Zeitbereiche und verlangte Tags und reduzieren dann einen zusammengeführten Kandidatenpool über mehrere Stufen, bevor das Modell ihn überhaupt sieht.
Im Assistenten meldet jede dieser Stufen sich selbst als Eintrag im Kandidatentrichter: ihre Art (Quelle, Zusammenführung, Filter, Kürzung), wie viele Kandidaten hinein- und herausgingen, und einen Grund aus einer geschlossenen Liste, die die tatsächlichen Verzweigungen im Code abbildet. Beispiele sind „jede verlangte Tag-Facette muss passen“, „Obergrenze für Ortsvielfalt“ und „Größe des Evidence Pack“. Jede Zählung wird aus einem Array gelesen, das es ohnehin gab. Keine Stufe berechnet etwas Neues, um sich zu melden. Vor der Auslieferung wurden acht echte Anfragen gegen einen eingefrorenen Snapshot mit und ohne Trichter ausgeführt, und Ergebnis-IDs, Reihenfolge und Scores waren Byte für Byte identisch.
Die normale Fotosuche durchläuft dieselben Stufen, ist aber keine getracte Oberfläche; ihr Trichter wird berechnet und nie exportiert.
Der Trichter hat bereits echte Fälle erklärt. Drei Fragen aus der Produktion nach Booten, Bergen und Flüssen lieferten nichts. Das lexikalische Retrieval hatte etwa 47 Kandidaten gefunden, das semantische etwa 23. Der Trace zeigte, wie der zusammengeführte Pool von 64 bis 68 Kandidaten an einer einzigen Stufe auf null fiel: an der Regel, dass ein Kandidat jedes unabhängig verlangte, harte Tag erfüllen muss. Kein veröffentlichtes Foto zeigte Fluss, Boot und Berg zusammen. Das Ergebnis war richtig, die Regel funktionierte, und der Trace sagte das in einer Zeile. Ohne ihn wäre die ehrliche Antwort eine Reproduktionssitzung gegen einen Datenbank-Snapshot gewesen.
Zwei Entscheidungen bestimmen, was Retrieval-Beobachtbarkeit nicht ist:
- Aggregiert statt pro Kandidat. Identität und Schicksal jedes einzelnen Kandidaten aufzuzeichnen wurde erwogen und verworfen. Der Attributtyp kann absichtlich keine Listen tragen. Und im auslösenden Fall hatte dieselbe Stufe aus demselben Grund jeden Kandidaten entfernt; eine Liste pro Kandidat hätte eine Zeile 64-mal wiederholt.
- Keine Scores pro Foto in Produktion. Der Trace enthält keine Ranking-Scores, keine Fusionsanteile und keine Verschiebungen des Rerankers pro Foto. „Warum landete dieses Foto auf Platz vier?“ oder „warum erschien jenes nicht?“ ist für ein bestimmtes Foto eine Offline-Frage. Beantwortet wird sie, indem man die Anfrage gegen einen eingefrorenen Datenbank-Snapshot wiederholt, in dem jeder interne Wert verfügbar ist.
Besucher bekommen davon getrennt ihre eigene Erklärung: Jedes Suchergebnis zeigt einen wahren Grund, warum es passt, abgeleitet aus den Feldern, die tatsächlich gepasst haben. Das ist eine Produktfunktion, keine Diagnose, und sie kann weder Rangfolgen noch Ausschlüsse ausdrücken.
Validierung ist nur dann ein Beleg, wenn sie aufgezeichnet wird
Der Fotografie-Assistent prüft jede Antwort, bevor Besucher sie sehen. Die Ausgabe muss ihren Vertrag erfüllen. Jede Aussage muss vom Evidence Pack gedeckt sein. Jedes zitierte Foto muss eines sein, das das Modell bekommen hat. Jede Bewertung eines Fotos muss sich auf ein echtes Foto beziehen. Text, der nach durchgesickerten Anweisungen aussieht, wird geschwärzt. Eine gescheiterte Aussagenprüfung löst genau einen begrenzten Wiederholungsversuch aus, bei dem die Fotoauswahl festgehalten wird.
Diese Prüfungen verhindern falsche Ausgaben. Für sich allein machen sie Fehler nicht sichtbar. Ein System, das stillschweigend verweigert oder stillschweigend wiederholt, hat eine schlechte Antwort verhindert und keinen Beleg hinterlassen, warum. Deshalb sendet jede Prüfung ein Ereignis mit der Nummer des Versuchs, ob sie bestanden wurde und wie viele Elemente sie geprüft oder zurückgewiesen hat. Die Anfrage hält fest, ob eine Wiederholung ausgelöst wurde und warum, und das Endergebnis (beantwortet oder verweigert) trägt einen Verweigerungsgrund.
Die Belege sind nach Sensibilität getrennt:
| Was gescheitert ist | Wo es sichtbar ist | |---|---| | Welche Prüfung, welcher Versuch | Trace-Ereignis | | Wie viele Aussagen, Zitate oder Bewertungen scheiterten | Trace-Ereignis | | Ob eine Wiederholung lief, ihr Grund und ihr Ergebnis | Trace-Attribute | | Die zurückgewiesenen Aussagen und Foto-IDs selbst | nur Warnzeile zur Laufzeit, mit Trace-ID | | Die Stufe eines Vertragsfehlers (leer, nicht parsebar, Transformation, Vertrag) | Trace-Attribut und Name der Fehlerklasse | | Die eigene Meldung des Validators | nur Warnzeile zur Laufzeit |
Dieselbe Trennung gilt für den Engineering-Ask, dessen Antwort festhält, wie viele Quellen das Modell beansprucht hat, wie viele es behalten durfte und wie viele verworfen wurden.
Die Inhaltsgrenze
Beobachtbarkeit in einem KI-System ist ein Datenfluss, und dieser ist zuerst als Vertrauensgrenze entworfen.
Die Frage der Besucher wird nur in der eigenen Datenbank der Anwendung gespeichert, 90 Tage aufbewahrt und beim Schreiben bereinigt. Die Tabelle hat keine Spalte für IP-Adresse, Cookie, Session, Konto oder User-Agent, und die Trace-ID, die sie mit allem anderen verbindet, ist pro Anfrage zufällig. Anfragen, die am Rate-Limit scheitern, werden gar nicht aufgezeichnet, weil ihr Zählen der Verfolgung einzelner Besucher zu nahe käme.
Der exportierte Trace enthält nur Metadaten, und die Garantie ist strukturell, keine Frage der Disziplin:
- Die Beobachtbarkeits-Schnittstelle hat keinen Eingabe- oder Ausgabeparameter, und Attributwerte sind Skalare. Domänencode hat keinen Weg, ein Dokument zu übergeben.
- Der exportierte Run-Datensatz hat überhaupt keine Felder für Ein- oder Ausgaben, es gibt also nichts zu schwärzen.
- Fehler werden als bereinigter Name der Fehlerklasse exportiert, nie als Meldung, denn eine Meldung kann enthalten, was auch immer ihr Autor hineininterpoliert hat.
Die Traces gehen über einen kleinen, selbst geschriebenen Adapter an LangSmith, nicht über das SDK des Anbieters, sodass kein Domänencode etwas Anbieterspezifisches importiert. Der Exporter ist aus, solange die Deployment-Konfiguration ihn nicht ausdrücklich einschaltet, und er akzeptiert nur den Ingest-Endpunkt von LangSmith in der EU-Region. Jeder andere Host schaltet ihn mit einer Meldung beim Start ab, statt stillschweigend zu exportieren. Für die ausgelieferte Anwendung ist er eingeschaltet worden, und Traces echter Fragen aus der Produktion wurden während der Entwicklung wieder ausgelesen.
Eine Grenze ist schwächer als die anderen. Warnzeilen zur Laufzeit sind freier Text, und einige enthalten zurückgewiesene Aussagen, also Modellausgaben. Sie tragen die Trace-ID und bleiben in den eigenen Logs der Anwendung. Sie fallen nicht unter dieselbe strukturelle Garantie, und es gibt keine schriftliche Regel dafür, was Laufzeit-Logs enthalten dürfen. Das ist eine echte Einschränkung.
Wenn die Beobachtbarkeit versagt
Die Regel lautet: Ein Fehler der Beobachtbarkeit darf nie zum Fehler einer Besucheranfrage werden. Jeder Aufruf an den Exporter geht durch einen einzigen schützenden Wrapper, der alles abfängt, was geworfen wird, und nur eine begrenzte Zahl von Warnungen schreibt, damit ein defekter Exporter die Logs nicht flutet, die er verbessern sollte. Der Upload läuft über einen verzögerten Timer, nachdem die Anfrage beendet ist, nie auf dem Anfragepfad. Ein gescheiterter Upload wird verworfen, nicht wiederholt. In einem erzwungenen Test mit einem Exporter, der aus jeder Methode eine Exception warf, lieferten beide öffentlichen KI-Oberflächen weiterhin vollständige, geprüfte Antworten. Der gemessene Mehraufwand des Tracings lag bei der schwersten Anfrageform im Median bei etwa 0,017 ms.
Der lehrreichste Fehler in diesem Teilsystem war einer, den jedes Signal innerhalb des Prozesses übersah. Als der Exporter zum ersten Mal eingeschaltet wurde, meldete der Server, dass Tracing aktiv sei, jede Logzeile trug ihre Trace-ID, und beide KI-Oberflächen antworteten normal. Kein einziger Trace kam beim Exporter an. Der Bundler hatte das Beobachtbarkeitsmodul für die Startschicht und die Routenschicht getrennt kompiliert. Beim Start wurde der echte Exporter in der einen Kopie registriert, und jede Anfrage las die andere, die noch den wirkungslosen Standard enthielt. Nichts sah falsch aus. Der Start wurde von der Schicht gemeldet, in der er funktioniert hatte, die Korrelation überquerte nie eine Schicht, Anfragen gelangen, weil der Exporter absichtlich fail-open ist, und Unit-Tests laufen in einem einzigen Modulgraphen, in dem es den Fehler nicht geben kann. Die einzige Prüfung, die „funktioniert“ von „verwirft alles“ unterscheiden konnte, war, beim Trace-Speicher nach einer Run-ID zu fragen, die der Server gerade protokolliert hatte, und eine 404 zu bekommen. Die Korrektur legte den gemeinsamen Zustand in eine prozessweite Registry, und die Verifikation fragt seitdem das Ziel, nicht die Quelle.
Die Admin-Ansicht folgt demselben Prinzip. Als der Trace-Speicher während der Verifikation Lesezugriffe drosselte, wurden drei von sechs Anfrage-Artefakten trotzdem korrekt gebaut. Jedes enthielt die vollständige Hälfte der Anwendung und meldete „Anfrage gescheitert“ statt eines leeren Traces.
Evaluation: Qualität beobachten, bevor sie live geht
Laufzeit-Traces sagen, was bei einer Anfrage passiert ist. Sie können nicht sagen, ob das System gut ist, ob die Fotos die waren, die ein Mensch sehen wollte. Dafür braucht es Bewertungen, und Bewertungen gehören offline.
Der Benchmark der Fotosuche ist eine eingefrorene, von Menschen bewertete Sammlung: Die aktuelle Version hat 106 Anfragen, 381 Fotos und 4.266 abgestufte Bewertungen, mit festgehaltenem Datei-Hash und Einfrierdatum. Die Evaluation läuft gegen einen Snapshot der Produktionsdatenbank, berechnet NDCG, Precision, Recall und MRR und kann gegen eine gespeicherte Baseline vergleichen und bei einer Verschlechterung scheitern. Der Assistent hat eigene Suiten für Beleg-Recall, Zuverlässigkeit der Validierung und Gesprächsverhalten. Das Retrieval des Engineering-Ask und das Kontext-Routing des Repositorys wurden jeweils an bewerteten Aufgabensammlungen gemessen.
Nichts davon läuft kontinuierlich oder in der CI. Es wird von Hand ausgeführt, pro Änderung, und die Berichte halten die Ergebnisse fest. Der Wert liegt darin, was die Messungen aufgedeckt haben:
- Ein Gleichstands-Artefakt. Die Hälfte der zulässigen Kandidaten lag in Score-Gleichständen. Gleichstände wurden nach Foto-ID aufgelöst, und jedes bewertete Foto hatte zufällig eine niedrigere ID als jedes unbewertete. Der Benchmark belohnte also unbemerkt jede Reihenfolge, die niedrige IDs oben hielt. Die Produktion schlug alle 200 zufälligen Gleichstands-Reihenfolgen. Eine Ranking-Variante, die nach +4,16 Punkten NDCG ausgesehen hatte, lag bei neutraler Auflösung der Gleichstände bei −1,70 Punkten und war nur in einem von 200 Seeds besser als die Produktion. Jede absolute Zahl vor diesem Zeitpunkt musste neu kalibriert werden.
- Ein Durchschnitt, der Schaden verbarg. Eine Wiederholungsstrategie wurde verworfen, weil die mittlere Precision keine klare Richtung zeigte. Zählte man stattdessen relevante Fotos, vernichtete die Wiederholung zehn und gewann eines. Die Strategie, die am Durchschnitt gescheitert war, ging später auf Grundlage der Zählung live.
- Eine Schwelle, die Schaden durchließ. Die Schwelle, die entscheidet, wann visuelle Fusion läuft, ließ 42 der 49 Anfragen durch, denen sie schadete. Mit dieser Schwelle lag der Nettoeffekt bei −1,9 Punkten pro Anfrage gegenüber gar keiner Fusion.
- Ein Modell als Gutachter, das nicht sicher war. Der Versuch, Modelle Benchmark-Paare als offensichtlich irrelevant vorsortieren zu lassen, wurde verworfen. Kein Ansatz erreichte 99 % Präzision bei sicheren Negativen mit nützlicher Abdeckung, und der Bild-Gutachter nannte einen Radfahrer mit Helm „offensichtlich irrelevant“ für „Fahrräder und Radfahrer“, mit Konfidenz 0,90. Dieselbe Übung brachte einige alte menschliche Bewertungen ans Licht, die falsch aussahen, was eine eigene Art von Beobachtung ist.
Auch die Evaluation hat ein Kostenmodell. Ein naives bindungsneutrales Protokoll hätte für einen einzigen Lauf fast zweitausend Aufrufe an Anbieter gebraucht und trotzdem seine eigene Konvergenzregel verfehlt. Das verwendete Protokoll rechnet 200 Seeds ohne einen einzigen Aufruf an einen Anbieter und bestätigt nur auf Anforderung mit dem echten Reranker.
Der Browser ist für den Server dunkel
Die Spracheingabe lässt Whisper in einem Web Worker auf dem Gerät der Besucher laufen, mit WebGPU, wo verfügbar, sonst mit WebAssembly. Die Architektur der Sprachinteraktion erklärt, warum. Für die Beobachtbarkeit ist die Folge einfach: Kein Server-Trace sieht irgendetwas davon.
Der Worker meldet der Seite einiges. Er sendet den Fortschritt des Modell-Downloads in Bytes und das Backend, das er tatsächlich gewählt hat. Scheitert WebGPU, sendet er ein Fallback-Ereignis mit einem Grund aus einer geschlossenen Liste und markiert die GPU für den Rest seiner Lebensdauer als ungesund. Er sendet einen typisierten Fehler, wenn das Laden des Modells oder die Transkription scheitert. Der Zustand der Sprachsitzung hält Backend und Fallback-Verlauf als Diagnose fest.
Nichts davon verlässt den Browser. Es wird nicht protokolliert, nicht angezeigt und nicht an den Server gesendet, und der Code sagt den Besuchern absichtlich nie, welches Backend sie nutzen. Was die Besucher bekommen, ist Wiederherstellung: eine Berechtigungsabfrage vor jedem Download, ein Fallback, der ohne Rückfrage geschieht, eine kurze Fehlermeldung und ein Textfeld, das immer funktioniert. Es gibt keinen Handler für einen Worker, der ganz abstürzt, und die Inferenzzeit wird in Produktion nicht gemessen.
Wo das Verhalten im Browser wichtig war, wurde es in lokalen Experimenten gemessen: mit Messaufbauten, die Performance-Marken in einem kontrollierten Browser für die Transkriptionsdauer setzen, und mit Vergleichsmessungen zwischen WebGPU und WebAssembly. Diese Ergebnisse sind datierte Messungen, kein laufendes Signal.
Das ist eine echte Einschränkung. Browser-Diagnosen zu sammeln hieße aber, Daten über die Geräte der Besucher an meinen Server zu senden, und die ganze Prämisse des Sprachdesigns ist, dass der Weg vom Mikrofon lokal bleibt. Wenn ich das ergänze, dann aggregiert, freiwillig und beschränkt auf die Fehlerklasse (gewähltes Backend, Fallback-Grund, Art des Fehlers), niemals Audio oder Text.
Frontend-Performance: gemessen, nicht überwacht
Das Frontend hat kein Real-User-Monitoring: keine Web-Vitals-Meldungen, keine Performance-Observer in Produktion, keine Bundle-Analyse. Seine Performance-Belege sind Engineering-Messungen:
- React Doctor läuft nach einer Änderung über die geänderte React-Oberfläche und vergleicht das Ergebnis mit einem Basis-Snapshot. Er gehört zum Arbeitsablauf von Agenten und Entwicklern, bewusst weder Hook noch CI-Prüfung.
- Die Prüfung gerenderter Zustände steuert einen echten Browser durch eine Route und prüft Bereitschaft, Überlauf, Verdeckung, Barrierefreiheit und kaputte Bilder; die Belege landen auf der Festplatte. Sie wird lokal ausgeführt.
- Tests auf Quelltextebene halten bestimmte Performance-Entscheidungen fest (ein verzögerter Weichzeichner, ein schrittweiser Aufbau der Galerie), damit eine spätere Änderung sie nicht stillschweigend zurückdreht. Diese laufen in der CI.
- Einmalige Browser-Messungen haben für bestimmte Experimente Layoutverschiebungen, Animationsframes und Render-Zählungen gemessen. Ihre Ergebnisse sind datierte Experimente, keine laufenden Signale.
Beobachtbarkeit von Engineering-Entscheidungen
Die letzte Ebene beobachtet, wie sich die Software verändert hat, nicht wie sie läuft. Das ist hier wichtig, weil ein großer Teil dieses Codes mit KI-Coding-Agenten entstanden ist und die Frage „warum ist das so?“ beantwortbar bleiben muss.
- Eine vorgeschlagene Änderungeine Frage, mit vorab festgelegter Schwelle
- Messungeingefrorener Snapshot · bewerteter Benchmark · Browser-Belege
- Deterministische PrüfungenTypen · Lint · Tests · Architektur- und Wissensvalidatoren
- Menschliche Prüfungder Bericht wird gelesen und ein Urteil gefällt
- Behalten, eingrenzen oder verwerfenfestgehalten als Bericht und Entscheidungsdokument
Was sich aus dem Repository rekonstruieren lässt:
- Warum eine Änderung live ging oder nicht. Änderungen mit einer echten Entscheidung haben einen Bericht mit ausdrücklichem Urteil, den Belegen dafür und den Befehlen, die sie erzeugt haben. Es gibt etwa 180 Engineering-Berichte, fast alle mit ausdrücklichem Urteil, und zwei Dutzend Entscheidungsdokumente, viele nach ihrem Ergebnis benannt („…-rejected“, „…-adopted“).
- Welche Regeln galten. Architekturgrenzen, das Routing-Manifest für Kontext, die Wissensschicht und die Anweisungen für Agenten sind mit dem Code versioniert, sodass die Regeln zu jedem Commit aus der Historie lesbar sind.
- Welche Prüfungen einen Merge absichern. Statische Analyse, Unit-Tests und vier Repository-Validatoren laufen in der CI bei jedem Pull Request und erneut vor dem Deployment. Ihre Ausgabe ist CI-Ausgabe.
Was sich nicht rekonstruieren lässt:
- Welchen Kontext ein Agent tatsächlich bekommen hat. Das Routing-Manifest sagt, was ein Agent für eine Art von Aufgabe laden sollte. Nichts hält fest, was er tatsächlich geladen hat. Sitzungsprotokolle liegen außerhalb des Repositorys, und der lokale Checkpoint hält fest, welche Dateien sich geändert haben, nicht, was der Agent gelesen hat.
- Ergebnisse lokaler Prüfungen. Git-Hooks führen mehr Prüfungen aus als die CI, und ihre Ausgabe wird nicht aufbewahrt. Ein umgangener Hook hinterlässt keine Spur.
- Welche Zeilen ein Agent geschrieben hat. Die Commits sind meine und tragen selten eine Agenten-Zuschreibung.
Das ist Beobachtbarkeit von Engineering-Entscheidungen, keine Produktions-Telemetrie: Sie erklärt, warum das System so ist, wie es ist, und hängt von der Disziplin ab, den Bericht zu schreiben. Auf diese Ebene verlasse ich mich am meisten, wenn ich nach einem Monat zu einem Teilsystem zurückkehre.
Reifegrad, nüchtern betrachtet
| Ebene | Erkennung | Diagnose | Korrelation | Speicherung | Reproduzierbarkeit | |---|---|---|---|---|---| | Anwendungslaufzeit | Teilweise: Zeitmesszeilen, nur Liveness | Schwach: Produktionsfehler nicht protokolliert | Teilweise: Trace-ID in Zeilen | Schwach: Log-Puffer | Schwach | | Retrieval | Stark im Assistenten, fehlt bei der normalen Suche | Stark auf aggregierter Ebene | Stark | Teilweise: Traces 14 Tage | Stark offline (eingefrorene Snapshots) | | LLM-Generierung | Stark in öffentlichen KI-Pfaden, schwach anderswo | Stark: Modell, Tokens, Route, Fehlerstufe | Stark in öffentlichen KI-Pfaden | Teilweise | Teilweise | | Validierung | Stark: je Prüfung, je Versuch | Teilweise: Zählungen im Trace, Einzelheiten nur in Logs | Stark | Teilweise | Stark offline | | Evaluation | entfällt | Stark | Pro Lauf: Snapshot, Benchmark-Hash | Stark: Berichte in Git | Stark | | Lokale Inferenz | Nur für Besucher sichtbar | Schwach: nur im Browserspeicher | Fehlt | Fehlt | Nur lokale Experimente | | Frontend-Performance | Fehlt in Produktion | Stark lokal | entfällt | Berichte | Teilweise | | Agenten-Arbeitsablauf | CI-Validatoren | Stark über Berichte | Schwach | Stark für Entscheidungen, fehlt für Kontext | Teilweise |
Was nicht beobachtet wird, absichtlich und nicht absichtlich
Bewusst nicht beobachtet: der Inhalt von Fragen, Prompts, Belegen und Antworten in jedem Drittsystem; Einzelheiten pro Kandidat im Retrieval in Produktion; alles, was Besucher über Anfragen hinweg identifiziert; Anfragen, die am Rate-Limit scheitern; welches Sprach-Backend das Gerät der Besucher verwendet hat.
Lücken, die es wert sind, geschlossen zu werden:
- Fehlererfassung für die allgemeine Laufzeit. Produktionsfehler aus tRPC sollten zumindest mit Trace-ID und Fehlerklasse protokolliert werden. Das ist eine kleine Änderung und die größte Lücke.
- Eine schriftliche Regel für den Inhalt von Laufzeit-Logs, passend zur strukturellen Regel, die der Trace schon hat.
- Trace-Abdeckung für die übrigen Modellaufrufe: zuerst die kleineren Fragen an den Kurator und die Sprachausgabe, weil sie öffentlich sind.
- Angeglichene Aufbewahrung, oder ein Snapshot des Artefakts für Fragen, die es wert sind, damit ein Protokolleintrag mit 90 Tagen nicht auf einen Trace zeigt, der nach 14 Tagen verfallen ist.
- Die Sprache von Fragen an den Engineering-Ask festhalten, die das Anfrageprotokoll derzeit leer lässt.
In diesem Maßstab nicht gerechtfertigt: Metrik-Backends, Dashboards, Alerting, SLOs, Sampling und verteiltes Tracing. Es gibt einen Serverprozess und eine Datenbank, und jeder interessante Fehler wurde bisher durch das Lesen der Belege einer einzelnen Anfrage gefunden, nicht durch eine Schwelle auf einem Aggregat.
Prinzipien
- Frage und Beleg trennen. Text der Besucher bleibt im eigenen System; exportierte Traces enthalten nur Metadaten, erzwungen durch Typen, nicht durch Sorgfalt.
- Eine Kennung, wiederverwendet. Eine zweite ID für dasselbe ist ein neuer Weg, wie Korrelation falsch werden kann.
- Die Prüfung aufzeichnen, nicht nur das Ergebnis. Ein Validator, der ohne Aufzeichnung verweigert, verhindert eine schlechte Antwort und verbirgt den Grund.
- In Produktion aggregieren, Details offline. Laufzeitaufzeichnungen sagen, wo Kandidaten verschwunden sind; eingefrorene Snapshots sagen, welche.
- Beobachtbarkeit muss fail-open sein. Sie liegt neben dem Anfragepfad, nie auf ihm.
- Am Ziel verifizieren. Jedes Signal im Prozess kann grün sein, während nichts aufgezeichnet wird.
- Messung als Behauptung behandeln. Benchmarks haben Artefakte, Durchschnitte verbergen Schaden, und Modelle als Gutachter brauchen ihre eigene Evaluation.
Fazit
Beobachtbarkeit ist in dieser Anwendung kein einzelnes System. Laufzeitzeilen zeigen, dass etwas passiert ist und wie lange es gedauert hat. Ein Trace nur mit Metadaten erklärt eine KI-Anfrage Stufe für Stufe, verbunden mit einem eigenen Protokoll der Frage durch eine ID, die nichts über die Besucher verrät. Evaluation auf eingefrorenen, von Menschen bewerteten Daten sagt, ob eine Änderung besser ist, bevor sie live geht. Engineering-Berichte sagen, warum das System so aussieht, wie es aussieht. Der Browser bleibt dunkel, teils mit Absicht, und die allgemeine Laufzeit ist dünner, als sie sein sollte.
Die Entscheidungen, die ich behalten würde, sind die Grenzen: Inhalte bleiben im eigenen System, der Trace bleibt strukturell, Beobachtbarkeit bleibt neben dem Anfragepfad, und die Verifikation fragt das Ziel. Die Schwächen liegen dort, wo der Artikel sie benennt: Fehler außerhalb der KI-Pfade, das Sprachmodell auf dem Gerät der Besucher und der Kontext, mit dem ein Agent gearbeitet hat.
Weiterlesen
Die hier beobachteten Teilsysteme haben eigene Fallstudien: die Architektur des Fotografie-Assistenten für das Evidence Pack und seine Validatoren, die Architektur der Fotosuche für Retrieval und Ranking, die Architektur der Sprachinteraktion für die lokale Spracherkennung und die Architektur des KI-nativen Repositorys für die Leitplanken und das Kontext-Routing, auf die sich die Engineering-Ebene stützt.