Streaming responses bij LLM-API's

Hoe het werkt en wanneer je het moet gebruiken

Wanneer je een applicatie bouwt die gebruikmaakt van grote taalmodellen (LLM's) zoals Claude of DeepSeek, merk je al snel dat tekstgeneratie tijd kost. Omdat modellen tekst token voor token voorspellen, kan een lang antwoord secondenlang op zich laten wachten. Zonder streaming kijkt je gebruiker in die tijd naar een lege laad-indicator. Met streaming responses los je dit op.

Hoe werkt het? (Server-Sent Events)

Vrijwel alle moderne LLM-API's gebruiken Server-Sent Events (SSE) voor streaming. In plaats van de HTTP-verbinding te sluiten zodra één grote JSON-payload is berekend, houdt de server de verbinding open.

Terwijl het model tokens genereert, pusht de API deze direct naar de client als losse stukjes tekst. Zodra de stroom voltooid is, stuurt de server een specifieke terminator, vaak [DONE], waarna de verbinding sluit.

UX-voordeel: De belangrijkste metriek hier is Time-to-First-Token (TTFT). Omdat de gebruiker de eerste woorden direct op het scherm ziet verschijnen, voelt de applicatie razendsnel, zelfs als de totale generatietijd hetzelfde blijft.

Wanneer is streaming onmisbaar?

Bij simpele, korte datatransformaties (zoals JSON-extractie of classificatie) op de backend kun je vaak beter wachten op het volledige antwoord. Streaming is echter essentieel in de volgende scenario's:

Valkuilen bij implementatie

Het bouwen van een robuuste streaming-client kent enkele uitdagingen:

Pseudocode: SSE Stream Consumeren

Hieronder zie je de basislogica om een streaming endpoint te bevragen in de frontend. We gebruiken de standaard Fetch API om de ReadableStream te decoderen.

async function fetchLLMStream(prompt) {
const response = await fetch('https://api.example.com/v1/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: "jouw-gekozen-model",
messages: [{ role: "user", content: prompt }],
stream: true // Zet streaming aan
})
});

const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");

while (true) {
const { value, done } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(line => line.trim() !== '');

for (const line of lines) {
if (line === 'data: [DONE]') return;

if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
const token = data.choices[0]?.delta?.content || "";

// Update de UI incrementeel
appendToChatInterface(token);
}
}
}
}

Met deze opzet zorg je voor vloeiende, realtime interactie, ongeacht de totale denkkracht die het model nodig heeft.