Zum Inhalt springen
EN DE

Start und Finish Parts

In einem Multi-Step-Szenario (z.B. Tool-Call-Loop mit maxSteps) durchlaeuft streamText mehrere Generierungsschritte. Jeder Schritt wird durch step-start und step-finish Stream Parts begrenzt. Der step-start Part liefert die messageId, Request-Metadaten und Warnings. Der step-finish Part liefert finishReason, usage (Token-Verbrauch), isContinued, Response-Metadaten und optional Log Probabilities.

Zusätzlich gibt es zwei Callbacks: onStepFinish feuert nach jedem einzelnen Schritt und liefert per-Step-Daten. onFinish feuert einmalig nach Abschluss aller Schritte und liefert aggregierte Daten wie totalUsage und das steps-Array.

Hinweis zur Benennung: Im fullStream heissen die Part-Types step-start und step-finish (TextStreamPart). Im Stream-Protokoll (Data Stream Protocol) werden sie als f: (Start Step) und e: (Finish Step) Zeilen uebertragen. In der UIMessage existiert nur ein StepStartUIPart mit type: 'step-start' — ein entsprechendes step-finish Part gibt es in UIMessage nicht, da die UI nur die Schritt-Grenzen markiert, nicht deren Abschluss.

import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const result = streamText({
model: anthropic('claude-sonnet-4-5-20250514'),
prompt: 'Wie ist das Wetter in Berlin und Muenchen?',
tools: { weather: weatherTool },
maxSteps: 5,
// Feuert nach JEDEM Schritt
onStepFinish({ stepType, finishReason, usage, text, toolCalls, toolResults, isContinued }) {
console.log(`Step (${stepType}) finished: ${finishReason}`);
console.log(`Tokens: ${usage.totalTokens}`);
},
// Feuert nach ALLEN Schritten
onFinish({ text, totalUsage, steps, finishReason, sources }) {
console.log(`Fertig. Total tokens: ${totalUsage.totalTokens}`);
console.log(`Schritte: ${steps.length}`);
},
});
for await (const part of result.fullStream) {
switch (part.type) {
case 'step-start':
console.log(`--- Neuer Schritt (messageId: ${part.messageId}) ---`);
break;
case 'text-delta':
process.stdout.write(part.textDelta);
break;
case 'tool-call':
console.log(`Tool: ${part.toolName}(${JSON.stringify(part.args)})`);
break;
case 'tool-result':
console.log(`Ergebnis: ${JSON.stringify(part.result)}`);
break;
case 'step-finish':
console.log(`Schritt fertig: ${part.finishReason}`);
console.log(`Tokens: ${part.usage.totalTokens}`);
console.log(`Weiter: ${part.isContinued}`);
break;
}
}
WertBedeutung
'stop'LLM hat normal aufgehoert
'length'Max-Token-Limit erreicht
'content-filter'Content-Filter hat gestoppt
'tool-calls'LLM will Tools aufrufen (naechster Schritt folgt)
'error'Fehler aufgetreten
'other'Sonstiger Grund
EigenschaftonStepFinishonFinish
WannNach jedem SchrittNach allen Schritten
usageToken-Verbrauch dieses SchrittsToken-Verbrauch des letzten Schritts
totalUsageNicht verfügbarAggregiert über alle Schritte
stepsNicht verfügbarArray aller Schritte
stepType'initial', 'continue', 'tool-result'Nicht verfügbar
isContinuedtrue wenn der nächste Schritt folgtNicht verfügbar

In der UIMessage erscheint StepStartUIPart mit { type: 'step-start' } als Part. Ein step-finish UIMessage Part existiert nicht — nur der Beginn eines Schritts wird in der UI abgebildet. Damit kannst Du im Frontend visuelle Grenzen zwischen Schritten rendern — z.B. ein Trennzeichen oder eine Schritt-Anzeige.

{m.parts.map((part, i) => {
if (part.type === 'step-start') {
return <hr key={i} className="step-divider" />;
}
if (part.type === 'text') {
return <p key={i}>{part.text}</p>;
}
return null;
})}

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