import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
// Mărim timeout-ul pentru Vercel serverless
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'Ești un asistent tehnic pragmatice. Răspunzi scurt și la obiect.',
messages,
});
// Transformă stream-ul într-un Response compatibil cu SSE / Vercel AI SDK
return result.toDataStreamResponse();
}Am integrat recent un modul de AI pentru clientul nostru pe un SaaS cu vreo 12k useri activi lunar. Prima versiune trimitea request-ul clasic, aștepta ca modelul de la OpenAI să termine tot textul și abia apoi returna JSON-ul. Rezultatul? Userul stătea 4-5 secunde în fața unui spinner inert. O mizerie de UX. Soluția evidentă a fost streaming-ul prin Server-Sent Events (SSE), iar cu Vercel AI SDK am rezolvat totul în câteva ore.
De ce SSE și nu WebSockets?
Când zici streaming, mulți juniori sar direct la WebSockets. E o greșeală frecventă. Pentru o comunicare unidirecțională — unde doar serverul îți trimite bucăți de text pe măsură ce LLM-ul generează jetoane (tokens) — WebSockets aduc o complexitate inutilă. Trebuie să gestionezi conexiuni persistente, handshaking, stateful servers sau un broker gen Redis Pub/Sub dacă scalezi serverless.
SSE folosește HTTP standard. E practic o conexiune HTTP simplă care rămâne deschisă (Content-Type: text/event-stream), iar serverul „picură” date pe măsură ce le primește. Lucrează impecabil cu arhitecturile serverless și pe Vercel, atâta timp cât respecti limitele de timeout.
Backend-ul în App Router: scurt și la obiect
În Next.js App Router, implementarea unui Route Handler de streaming e extrem de curată. Folosim streamText din pachetul ai și librăria oficială de provider @ai-sdk/openai.
Cea mai mare capcană aici este durata de execuție. Pe planul Hobby de la Vercel, un Route Handler crapă după 10 secunde. Dacă LLM-ul tău generează un răspuns lung sau folosești un model mai lent gen GPT-4o, conexiunea va fi tăiată abrupt. Trebuie să setezi export const maxDuration = 30; (sau mai mult dacă ești pe Pro) ca să eviti asta.
Frontend-ul cu useChat: magia din spate
Vercel AI SDK oferă hook-ul useChat pe frontend. Acesta face câteva lucruri grele automat:
- Gestionează starea listei de mesaje (
messages). - Când apelezi
handleSubmit, trimite tot istoricul către endpoint-ul de API. - Ascultă stream-ul SSE și face
appendîn timp real la ultimul mesaj, declanșând re-render la fiecare bucată de text primită.
Unde apare trade-off-ul? În re-renderings. La un răspuns foarte lung, componenta ta de chat se va randa de zeci sau sute de ori pe secundă. Dacă ai o listă mare de mesaje fără virtualizare sau componente neseperate corect (de exemplu, dacă randezi Markdown greu la fiecare token), o să vezi că UI-ul începe să agațe pe telefoane mai vechi. Trucul e să izolezi mesajul care se află în stare de streaming sau să folosești React.memo pe componentele mesajelor anterioare care sunt deja „înghețate”.
O altă problemă reală e re-conectarea. Dacă conexiunea pică la tokenul 150 din cauza rețelei pe mobil, SSE-ul nativ încearcă să reia conexiunea, dar Vercel AI SDK va arunca o eroare în UI dacă nu pui un handler de onError. Eu am ales să afișez un buton discret de „Re-try” în loc să las aplicația blocată în stare de loading.
Voi ce folosiți pentru gestionarea erorilor când crapă stream-ul la jumătate?