import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
messages,
system: 'Ești un asistent tehnic util. Răspunde scurt și la obiect.',
});
return result.toDataStreamResponse();
}Am refăcut recent interfața pentru un asistent AI intern pe care îl folosesc băieții din support la un proiect cu peste 12k interogări pe zi. Problema veche era clasică: utilizatorul punea o întrebare, aștepta 4-5 secunde în fața unui spinner obosit, după care primea un zid de text. Trecerea pe streaming prin Server-Sent Events (SSE) a schimbat radical aplicația, iar hook-ul useChat din Vercel AI SDK mi-a salvat cam 3 zile de Boilerplate Code.
De ce streaming și ce înseamnă pentru UX
Când lucrezi cu LLM-uri (OpenAI, Anthropic, Ollama), latența e inamicul numărul unu. Să generezi 500 de tokeni durează ceva. Dacă aștepți ca backend-ul să adune tot răspunsul înainte să trimită un 200 OK, experiența e oribilă.
Cu streaming prin SSE, conexiunea HTTP rămâne deschisă și primești bucăți de text (chunk-uri) pe măsură ce sunt calculate de model. Până când utilizatorul citește primele două cuvinte, restul frazei e deja generat. În metricele noastre, Time To First Byte (TTFB) perceput de user a scăzut de la 4.2 secunde la sub 180ms.
Cum rezolvă Vercel AI SDK mizeria din frontend
Acum 2 ani, când făceam streaming manual în React, trebuia să-mi scriu singur un Fetch custom cu ReadableStream, să gestionez reconectările, să parsez manual liniile care începeau cu data: și să combin tokenii în state. Un haos de cod unde mai scăpau memory leak-uri.
Vercel AI SDK a simplificat treaba la un nivel nesimțit de simplu. Pe frontend folosești hook-ul useChat, iar pe backend folosești streamText dintr-un Route Handler de Next.js App Router.
Hook-ul useChat îți oferă direct starea messages, funcția handleSubmit, input, starea de isLoading și chiar o funcție de stop în caz că userul vrea să oprească generarea la jumătate. Nu mai stai să faci update manual în useState pentru fiecare token primit.
Unde m-am lovit de pragul de sus (Trade-offs reale)
Nu e totul numai lapte și miere, evident. Când treci la streaming, apar niște probleme tehnice specifice de care te lovești rapid:
- Layout shift pe Markdown: Dacă LLM-ul trimite un bloc de cod sau un link, tokenii ajung sparți. De exemplu, prima dată vine
`apoi`apoi`js. Dacă folosești un renderer de markdown neinspirat, interfața ta va pâlpâi și va sări sus-jos ca nebuna pe măsură ce se închid și se deschid tag-urile HTML. - JSON Mode / Structured Outputs: Streaming-ul e genial pentru text liber. Dacă în schimb ai nevoie ca modelul să-ți returneze un JSON valid pentru a randa niște carduri dinamice în UI, e mult mai greu. Trebuie să folosești
streamObjectși un schema Zod, dar parsing-ul parțial pe un JSON incomplet poate genera erori dacă nu e tratat corect. - Vercel Edge Runtime vs Node.js: Dacă pui ruta pe Edge, streaming-ul pornește aproape instant (n-ai cold start de Lambda). Dar dacă în aceeași rută trebuie să apelezi o librărie mai veche care depinde de module native de Node (
fs,crypto), va trebui să rămâi pe Node.js runtime, unde poți avea un mic delay la pornire.
Concluzie
Dacă construiești ceva cu LLM-uri în Next.js azi, să scrii un parser manual de SSE e o pierdere de timp. Combo-ul streamText + useChat acoperă 90% din cazuri out-of-the-box.
Voi mai scrieți handlere custom de fetch pe stream-uri sau ați trecut toți pe AI SDK?