Token streaming (LLM-output)
Wat is token streaming?
Token streaming wil zeggen dat een taalmodel zijn antwoord doorstuurt terwijl het nog aan het schrijven is. Telkens als het een paar tokens klaar heeft, duwt de API ze over een verbinding die openblijft naar jouw toepassing, en die toont ze meteen. Zonder streaming wacht de API op de laatste token en stuurt dan alles in één keer. Het model werkt in beide gevallen hetzelfde en maakt zijn antwoord token per token, zoals in het lemma over tokens staat. Streaming bepaalt alleen wanneer je ze te zien krijgt.
Het gebruikelijke transport is server-sent events: een gewoon HTTP-antwoord met content type text/event-stream dat openblijft en telkens een klein event aflevert. Anthropic, OpenAI en Google werken alle drie zo. Je zet een stream-vlag op je aanvraag en krijgt een reeks events in plaats van een JSON-document. De documentatie van Google zegt het zonder omwegen: standaard komt het antwoord pas als de hele generatie afgelopen is, en streaming bestaat om de interactie vlotter te laten verlopen.
Dit is niet hetzelfde als streaming data. Die term slaat op een pipeline die bedrijfsgebeurtenissen zoals bestellingen of betalingen doorlopend verwerkt zodra ze binnenkomen. Token streaming gaat over het afleveren van één modelantwoord aan één aanvrager.
Streaming tegenover een antwoord in één keer
Neem een antwoord van 500 tokens, ruwweg 375 woorden, van een model dat 50 tokens per seconde maakt. Het genereren duurt in beide gevallen een tiental seconden. Zonder streaming kijkt de gebruiker tien seconden naar een draaiend icoontje en begint hij daarna pas te lezen. Met streaming staan de eerste woorden er zodra het model de prompt gelezen heeft, en komt de rest binnen terwijl hij al leest. Tegen dat het antwoord af is, heeft hij het grootste deel gehad.
Daar horen twee cijfers bij. Time to first token is hoelang de gebruiker wacht voor er iets verschijnt. Tokens per seconde is hoe snel de tekst daarna blijft komen. Een chatproduct leeft van het eerste cijfer; het tweede moet gewoon boven leessnelheid blijven. De latency-gids van OpenAI noemt streaming de meest doeltreffende ingreep om gebruikers minder te laten wachten, omdat er een groot verschil zit tussen wachten en zien hoe iets vooruitgaat.
Voor de ontwikkelaar ligt het omgekeerd. Een antwoord in één keer is een enkele HTTP-oproep en een JSON-object. Een stream is een reeks events die je leest, samenvoegt en toont terwijl ze blijven binnenkomen.
Wat verandert er voor de ontwikkelaar?
Je verwerkt events, geen resultaat. De API van Anthropic stuurt een message_start, dan per content block een start, een reeks deltas en een stop, daarna een message_delta met de stopreden en een opgeteld tokenaantal, en tot slot message_stop. De Responses API van OpenAI heeft dezelfde opbouw onder andere namen.
Structured output komt binnen als halve JSON. Elke delta is een brokje van een JSON-string en op zichzelf geen geldige JSON. Ofwel wacht je op het afsluitende event en parse je één keer, wat de documentatie van de Java-SDK van Anthropic ook aanraadt, ofwel gebruik je een parser die met gedeeltelijke JSON overweg kan.
Tool calls komen midden in de stream. De input van een tool komt ook als gedeeltelijke JSON binnen. Anthropic vermeldt dat de huidige modellen telkens een volledige sleutel met waarde uitsturen, waardoor er wachttijd tussen de events kan vallen terwijl het model verder werkt. De tool uitvoeren kan pas als dat blok gesloten is, wat OpenAI aangeeft met een response.function_call_arguments.done event.
Stoppen en herstarten moet je zelf bouwen. De gebruiker kan halverwege op stop drukken, en de tokens die tot dan gegenereerd zijn staan wel op de factuur. Een weggevallen verbinding is erger: na 300 tokens kan je niet stilletjes hernemen, want die 300 tokens zijn al gelezen, dus ofwel herstart je en vervang je wat op het scherm staat, ofwel vraag je een vervolg. Fouten komen bovendien binnen als event, waar Anthropic een overloaded_error stuurt in plaats van de HTTP 529 die een gewone oproep zou teruggeven.
Wanneer streaming loont
Overal waar een mens op tekst zit te wachten, denk aan een chatbot, een schrijfassistent in je mailprogramma of een AI die een rapport toelicht, voelt het product zonder streaming traag aan en met streaming vlot. Een voice agent kan er helemaal niet zonder, want de tekst-naar-spraakstap heeft de eerste zin nodig om te beginnen praten. De Live API van Google gaat verder en streamt audio in beide richtingen over een WebSocket, voor gesprekken met weinig vertraging.
Een batchjob heeft er niets aan. Classificeer je een nacht lang 20.000 facturen, dan kijkt niemand mee en telt enkel de totale doorvoer. De SDK's van Anthropic vereisen wel streaming bij een grote max_tokens, om HTTP-timeouts te vermijden, maar geven je daarna gewoon het samengestelde bericht.
Aan wat je betaalt verandert streaming niets. De factuur is dezelfde of de tokens nu in één blok of in vijftig events aankomen, want het zijn dezelfde tokens. Wat wel verandert is de wachttijd die de gebruiker ervaart, en dat weegt zwaar met een mens aan de andere kant en helemaal niet zonder.
Waar moet je op letten bij token streaming?
Moderatie komt achteraf. De documentatie van OpenAI zegt dat de output streamen in een productietoepassing de inhoud moeilijker te modereren maakt, en dat moderatiescores pas na de volledige output komen in plaats van bij de losse deltas. Moet een guardrail een antwoord goedkeuren voor de klant het ziet, dan buffer je de stream en verlies je het effect, of je aanvaardt dat een fout antwoord woord per woord verschijnt en achteraf teruggetrokken wordt.
Mensen lezen een half antwoord en handelen ernaar. Een model begint vaak met een cijfer en nuanceert dat twee zinnen later. In een stream leest de gebruiker het cijfer, stopt en neemt de telefoon. Markeer het einde van een antwoord duidelijk, en laat het model zijn voorbehoud geven voor zijn besluit, niet erna.
Gedeeltelijke output in je logs. Annuleert de gebruiker of valt de verbinding weg, beslis dan wat er in je logs en in de gesprekshistoriek belandt. Bewaar de tekst met een vlag dat hij afgebroken is, anders behandelt de volgende beurt een afgekapt antwoord als een afgewerkt antwoord.