Zum Inhalt springen
EN DE

Challenge 7.4: Error Handling

Was passiert in Deiner App, wenn der LLM-Provider mitten im Stream einen Fehler wirft? Der User hat schon die halbe Antwort gelesen — und dann? Weisse Seite? Abbruch ohne Erklärung? Oder eine hilfreiche Fehlermeldung?

Overview: streamText() Stream verzweigt bei Erfolg zu finish Event, bei Fehler zu onError Callback mit Logging, User-Meldung und Retry-Option

Fehler passieren: Provider-Timeouts, Rate Limits, unbekannte Tools, Netzwerkabbrueche. Mit Error Handling faengst Du diese Fehler ab und gibst dem User eine sinnvolle Rueckmeldung, statt die App crashen zu lassen.

Ohne Error Handling: Deine App crasht, wenn der Provider einen Fehler wirft. Der User sieht eine weisse Seite, eine kryptische Fehlermeldung oder die halbe Antwort bricht einfach ab. In Production ist das inakzeptabel — User verlieren Vertrauen und Daten gehen verloren.

Mit Error Handling: Fehler werden abgefangen und in verstaendliche Meldungen uebersetzt. Der User weiss, was passiert ist, und kann es nochmal versuchen. Dein Logging erfasst den Fehler für Debugging. Und mit Retry-Strategien kann Deine App bestimmte Fehler sogar selbst loesen.

Die einfachste Form: Der onError Callback in streamText wird aufgerufen, wenn waehrend der Generierung ein Fehler auftritt:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
prompt: 'Erklaere Error Handling.',
onError({ error }) { // ← Wird bei jedem Fehler aufgerufen
console.error('Stream-Fehler:', error);
// Hier: Logging, Alerting, Metriken
},
});

onError wird aufgerufen bei:

  • Provider-Fehlern: API nicht erreichbar, Rate Limit, Authentication
  • Stream-Fehlern: Verbindungsabbruch, Timeout
  • Tool-Fehlern: Tool wirft Exception (ab Level 3)

Wichtig: onError verhindert nicht, dass der Fehler zum User durchschlaegt. Es ist ein Hook für Logging und Monitoring — nicht für User-Facing Error Messages.

Web-App Kontext: Der folgende Code zeigt Error Handling in einer Next.js API Route. Deine TRY-Uebung weiter unten arbeitet im Terminal (CLI).

Für Web-APIs kontrollierst Du mit onError in toUIMessageStreamResponse, welche Fehlermeldung der Client sieht:

export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
messages,
onError({ error }) {
// Server-seitiges Logging
console.error('[Stream Error]', error);
},
});
return result.toUIMessageStreamResponse({
onError(error) { // ← Kontrolliert Client-Fehlermeldung
// WICHTIG: Gibt den String zurück, den der Client sieht
// Keine internen Details leaken!
return 'Es ist ein Fehler aufgetreten. Bitte versuche es erneut.';
},
});
}

Die Trennung ist entscheidend:

  • onError in streamText: Server-Seite. Logge den vollen Fehler für Debugging.
  • onError in toUIMessageStreamResponse: Client-Seite. Gib eine kurze, verstaendliche Meldung zurück.

Das AI SDK exportiert typisierte Error-Klassen. Du kannst Fehler gezielt unterscheiden:

import { streamText, NoSuchToolError } from 'ai';
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
messages,
tools: { /* ... */ },
onError({ error }) {
if (NoSuchToolError.isInstance(error)) { // ← Typisierte Pruefung
console.error(`Unbekanntes Tool: ${error.toolName}`);
console.error(`Verfuegbare Tools: ${error.availableToolNames.join(', ')}`);
} else {
console.error('Unbekannter Fehler:', error);
}
},
});
return result.toUIMessageStreamResponse({
onError(error) {
if (NoSuchToolError.isInstance(error)) {
return `Das Tool "${error.toolName}" existiert nicht. Verfuegbar: ${error.availableToolNames.join(', ')}`;
}
return 'Ein unerwarteter Fehler ist aufgetreten.';
},
});

Wichtige Error-Typen im AI SDK:

Error-KlasseWannNuetzliche Properties
NoSuchToolErrorLLM ruft ein Tool auf, das nicht existierttoolName, availableToolNames
InvalidToolArgumentsErrorTool-Argumente matchen nicht das SchematoolName, toolArgs
APICallErrorProvider-API antwortet mit FehlerstatusCode, message

Für transiente Fehler (Netzwerk, Rate Limits) kannst Du einen Retry-Wrapper bauen:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
async function streamWithRetry(
params: Parameters<typeof streamText>[0],
maxRetries = 3,
) {
let lastError: unknown;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = streamText(params);
// Stream testen: Das text-Promise awaiten
// Wenn der Provider antwortet, ist der Stream OK
const fullText = await result.text; // ← Wartet auf kompletten Text
return { result, fullText }; // ← Erfolg: Ergebnis zurueckgeben
} catch (error) {
lastError = error;
console.error(`Versuch ${attempt}/${maxRetries} fehlgeschlagen:`, error);
if (attempt < maxRetries) {
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); // Exponential Backoff
console.log(`Warte ${delay}ms vor Versuch ${attempt + 1}...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw new Error(`Alle ${maxRetries} Versuche fehlgeschlagen: ${lastError}`);
}

Die Retry-Strategie nutzt Exponential Backoff: 1s, 2s, 4s, … Das gibt dem Provider Zeit, sich zu erholen (z.B. nach Rate Limiting).

Hinweis: Dieses Pattern wartet auf den vollstaendigen Text (result.text). Für Streaming-Retries (wo Du den Text schon waehrend der Generierung anzeigen willst) brauchst Du eine komplexere Lösung — z.B. den for await-Loop in der Retry-Schleife selbst.

Vergiss nicht: Fehler können auch beim Konsumieren des Streams auftreten. Wrap den for await Loop in einen try/catch:

const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
prompt: 'Erklaere Error Handling.',
});
try {
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
console.log('\n--- Stream erfolgreich beendet ---');
} catch (error) {
console.error('\n--- Stream abgebrochen ---');
console.error('Fehler:', error);
// Fallback: Gespeicherte Antwort zeigen, User informieren, etc.
console.log('Die Antwort konnte nicht vollständig geladen werden.');
}

Aufgabe: Simuliere einen Fehler und fange ihn mit onError ab. Zeige eine User-freundliche Meldung.

Erstelle die Datei error-handling.ts:

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
// TODO 1: Starte streamText mit einem absichtlich falschen Modellnamen
// const result = streamText({
// model: anthropic('claude-nonexistent-model'),
// prompt: 'Dieser Call wird fehlschlagen.',
// onError({ error }) {
// // TODO 2: Logge den Fehler serverseitig
// },
// });
// TODO 3: Konsumiere den Stream in einem try/catch
// try {
// for await (const chunk of result.textStream) {
// process.stdout.write(chunk);
// }
// } catch (error) {
// // TODO 4: Zeige eine User-freundliche Fehlermeldung
// }

Checkliste:

  • streamText mit einem ungueltigem Modell aufgerufen (oder anderem Fehler-Trigger)
  • onError Callback implementiert mit Server-seitigem Logging
  • for await Loop in try/catch gewrapped
  • User-freundliche Fehlermeldung im catch-Block

Fuehre aus: npx tsx error-handling.ts

Lösung anzeigen
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
console.log('Starte Stream mit absichtlichem Fehler...\n');
const result = streamText({
model: anthropic('claude-nonexistent-model-99'),
prompt: 'Dieser Call wird fehlschlagen.',
onError({ error }) {
console.error('[Server Log] Stream-Fehler aufgetreten:');
console.error('[Server Log]', error);
},
});
try {
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
} catch (error) {
console.log('\n--- Fehler abgefangen ---');
console.log('Dem User anzeigen: "Die Antwort konnte nicht geladen werden. Bitte versuche es in einigen Sekunden erneut."');
// In einer echten App:
// - User-freundliche Meldung in der UI anzeigen
// - Retry-Button anbieten
// - Fehler an Error-Tracking senden (Sentry, etc.)
}

Erklärung: Der ungueltige Modellname fuehrt zu einem API-Fehler. onError loggt den Fehler serverseitig (mit allen Details für Debugging). Der try/catch um den Stream-Consumer faengt den Fehler ab und zeigt eine verstaendliche Meldung. In Production wuerdest Du hier eine UI-Komponente rendern statt console.log.

Erwarteter Output (ungefaehr):

Starte Stream mit absichtlichem Fehler...
[Server Log] Stream-Fehler aufgetreten:
[Server Log] APICallError: 404 model_not_found ...
--- Fehler abgefangen ---
Dem User anzeigen: "Die Antwort konnte nicht geladen werden.
Bitte versuche es in einigen Sekunden erneut."

Der genaue Fehlertext variiert je nach Provider. Entscheidend ist: onError loggt den vollen Fehler, der try/catch zeigt die User-freundliche Meldung.

Combine: streamText() durch smoothStream(), Fehlercheck leitet bei Fehler zu onError Handler mit User-Meldung und Retry, bei Erfolg direkt ans Frontend

Uebung: Kombiniere Error Handling mit Stream Transforms. Baue einen Stream, der:

  1. smoothStream() als Transform nutzt
  2. Einen Custom Transform hat, der prueoft ob ein Chunk ein bestimmtes “verbotenes” Muster enthaelt (z.B. “FEHLER_SIMULATION”) und in dem Fall einen Error wirft
  3. onError loggt den Fehler serverseitig
  4. Ein try/catch um den Stream-Consumer zeigt eine User-freundliche Meldung

Optional Stretch Goal: Implementiere streamWithRetry mit Exponential Backoff. Provoziere einen Fehler (z.B. mit einem falschen API-Key) und beobachte, wie die Retry-Logik 3 Versuche unternimmt, bevor sie aufgibt. Logge jeden Versuch mit Timestamp.

Part of AI Learning — free courses from prompt to production. Jan on LinkedIn