eduardweb.
AI & LLMsIntermediar#ai#nextjs#typescript#react#openai

Streaming de LLM în Next.js: De la timeout-uri pe serverless la useChat

De Liliana Ghiță, 2 sept. 2026 · 14 vizualizări · 2 like-uri

Postat 2 sept. 2026
typescript
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';

export const runtime = 'edge';

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = await streamText({
    model: openai('gpt-4o-mini'),
    system: 'Ești un asistent tehnic concis.',
    messages,
    abortSignal: req.signal,
    onFinish({ text, usage }) {
      // Salvăm în DB consumul de tokeni și mesajul final
      console.log(`Tokens consumați: ${usage.totalTokens}`);
    },
  });

  return result.toDataStreamResponse();
}

Dacă trimiți un request clasic de tip fetch către OpenAI într-un Serverless Function pe Vercel, o să ai două mari probleme: utilizatorul se uită la un spinner 10-15 secunde, iar funcția ta riscă să crape cu timeout dacă modelul e mai vorbăreț. Soluția standard este streaming-ul prin Server-Sent Events (SSE). Am trecut prin migrarea asta la un asistent intern pentru suport tehnic (cam 4.000 de query-uri pe zi), iar trecerea la streaming a redus timpul perceput de răspuns (Time to First Token) de la 8 secunde la sub 400ms.

De ce SSE și nu WebSockets?

Multă lume sare direct la WebSockets când aude de comunicare în timp real. Pentru LLM-uri, e aproape mereu o decizie greșită.

WebSockets cer conexiuni persistente cu stare (stateful), ceea ce e un coșmar pe infrastructură serverless unde instanțele se sting și se aprind constant. Server-Sent Events merg peste HTTP clasic, sunt unidirecționale (server -> client) și sunt suportate nativ de runtime-ul de Edge sau Node.js fără infrastructură adițională de tip Pusher sau Socket.io.

Vercel AI SDK: streamText și useChat

Până acum un an, scriam manual handlere de ReadableStream și parsam bucățile de text (chunks) pe frontend. Se putea face, dar te loveai de caractere UTF-8 tăiate la jumătate sau erori de parsare JSON pe tool calling.

Pachetul ai de la Vercel rezolvă asta elegant. Pe server folosești streamText împreună cu providerul dorit (OpenAI, Anthropic etc.), iar pe client hook-ul useChat preia automat gestionarea stării, inputul, mesajele anterioare și auto-scroll-ul.

Un detaliu important pe frontend: useChat expune direct messages, input, handleInputChange și handleSubmit. Nu mai ai nevoie de useState pentru input, nici de logică de append manual pe array-ul de mesaje pe măsură ce sosesc fragmentele de text.

Capcane și trade-off-uri din producție

  1. Edge Runtime vs Node.js: Dacă rulezi ruta pe export const runtime = 'edge', streaming-ul pornește instant fără overhead de cold start. Însă ai grijă dacă ai nevoie de biblioteci de Node (de exemplu, ORM-uri grele sau parsere de PDF) — acestea nu vor rula pe Edge. Soluția mea a fost să țin logica de streaming pe Edge și să apelez servicii separate pentru parsare de fișiere.

  2. Facturarea pe request-uri anulate (AbortController): Când un user închide tab-ul sau apasă pe „Stop generating”, frontend-ul taie conexiunea. Dacă nu transmiți semnalul de abort corect către API-ul OpenAI/Anthropic, modelul continuă să genereze pe server și plătești tokenii degeaba. useChat trimite automat AbortController.signal, dar asigură-te că ruta ta de backend îl pasează mai departe în streamText.

  3. Cachingu-ul intermediar: Dacă ai un reverse proxy (Cloudflare, Nginx) în fața aplicației, dezactivează buffering-ul HTTP (X-Accel-Buffering: no), altfel proxy-ul va ține răspunsul în buffer până când e gata tot textul, anulând complet efectul de streaming.

Voi cum gestionați persistența conversațiilor când folosiți streaming — salvați mesajul complet în Postgres pe callback-ul onFinish sau sincronizați totul pe client după ce s-a terminat stream-ul?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.