Boss Fight: Real-Time Dashboard
Das Szenario
Abschnitt betitelt „Das Szenario“Du baust ein Real-Time Dashboard — ein Streaming-System, das einem User in Echtzeit eine Analyse liefert. Der Stream transportiert neben dem Analyse-Text auch strukturierte Status-Updates, nutzt Message Metadata für Session-Tracking, glaettet die Ausgabe und faengt Fehler graceful ab.
Dein Dashboard soll sich so anfuehlen:
[Status: searching] Recherchiere Daten...[Fortschritt: 1/3] Marktdaten geladen.
Die Analyse zeigt folgende Trends: Im ersten Quartal stiegder Umsatz um 12%. Der wichtigste Treiber war...
[Fortschritt: 2/3] Wettbewerbsanalyse abgeschlossen.
Im Vergleich zum Wettbewerb liegt das Unternehmen bei...
[Fortschritt: 3/3] Prognose berechnet.[Status: done] Analyse abgeschlossen.[Session: user-42 | Dauer: 4.2s | Tokens: 847]Dieses Projekt verbindet alle vier Bausteine:
Anforderungen
Abschnitt betitelt „Anforderungen“-
Custom Data Parts (Challenge 7.1) — Sende strukturierte Status-Updates im Stream: “searching”, “analyzing”, “done”. Sende mindestens 3 Fortschritts-Updates mit aktuellem Schritt und Gesamtzahl (z.B.
{ step: 1, total: 3, label: "Marktdaten geladen" }). -
Message Metadata (Challenge 7.2) — Jede User-Message traegt Metadata:
userId,timestamp,sessionId. Die Assistant-Antwort traegt Metadata:modelId,generatedAt,tokenCount. Logge die User-Metadata serverseitig, BEVOR der LLM-Call startet. -
Stream Transforms (Challenge 7.3) — Nutze
smoothStream()für fluessige Textausgabe. Schreibe einen Custom Transform, der vertrauliche Daten maskiert (z.B. E-Mail-Adressen durch “[REDACTED]” ersetzt). -
Error Handling (Challenge 7.4) — Implementiere
onErrorinstreamTextfür serverseitiges Logging. ImplementiereonErrorintoUIMessageStreamResponsefür User-freundliche Fehlermeldungen. Wrapp den Stream-Consumer in try/catch als letzten Fallback. -
Session-Statistik — Am Ende des Streams: Berechne die Dauer (aus Metadata-Timestamps) und sende eine finale Statistik als Data Part:
{ type: 'stats', duration, tokenCount, userId }.
Starter-Code
Abschnitt betitelt „Starter-Code“import { createDataStream, smoothStream, streamText } from 'ai';import { anthropic } from '@ai-sdk/anthropic';
// TODO: Message mit Metadata erstellen// TODO: Custom Transform für Daten-Maskierung// TODO: createDataStream mit Status-Updates// TODO: streamText mit smoothStream + Custom Transform// TODO: Error Handling (onError + try/catch)// TODO: Session-Statistik am EndeBewertungskriterien
Abschnitt betitelt „Bewertungskriterien“Dein Boss Fight ist bestanden, wenn:
- Mindestens 3 Custom Data Parts mit Fortschritts-Updates werden im Stream gesendet
- User-Messages tragen Metadata (userId, timestamp, sessionId) die das LLM nicht sieht
- Die Assistant-Antwort traegt Metadata (modelId, generatedAt)
-
smoothStream()ist als Transform konfiguriert - Ein Custom Transform maskiert mindestens ein Datenmuster (E-Mail, Telefonnummer, o.ae.)
-
onErrorist sowohl instreamTextals auch intoUIMessageStreamResponseimplementiert - Ein try/catch um den Stream-Consumer faengt unerwartete Fehler ab
- Am Ende wird eine Session-Statistik als Data Part gesendet (Dauer + Tokens)
Hinweise
Abschnitt betitelt „Hinweise“Hinweis 1: createDataStream Struktur
createDataStream erwartet eine execute Funktion, die den dataStream Controller als Parameter bekommt. In dieser Funktion rufst Du dataStream.writeData() für Custom Data Parts auf und result.mergeIntoDataStream(dataStream) für den LLM-Stream. Die Reihenfolge bestimmt, wann Data Parts im Stream erscheinen.
Hinweis 2: Timing für Fortschritts-Updates
Du kannst setTimeout oder setInterval nutzen, um Fortschritts-Updates zeitversetzt zu senden. Oder nutze die onStepFinish bzw. onChunk Callbacks von streamText, um Data Parts basierend auf dem Stream-Fortschritt zu senden. Wichtig: Beende Intervalle in onFinish, sonst laeuft der Stream nie zu Ende.
Hinweis 3: Custom Transform + smoothStream kombinieren
experimental_transform akzeptiert ein Array. smoothStream() sollte zuerst kommen (glaettet den Raw-Stream), danach Dein Custom Transform (arbeitet mit den geglätteten Chunks). Denke daran: Dein Transform muss text-delta Chunks modifizieren und alle anderen Typen unveraendert durchlassen.
Hinweis 4: Session-Dauer berechnen
Speichere Date.now() am Anfang des execute Callbacks. In onFinish von streamText berechnest Du die Differenz. Sende das Ergebnis als Data Part: dataStream.writeData({ type: 'stats', durationMs: Date.now() - startTime, ... }).