Streaming e UX de LLMs
Server-Sent Events, streaming de tokens na UI, skeleton states e perceived performance.
Definição
LLMs geram token a token — streaming envia cada token ao cliente imediatamente em vez de esperar a resposta completa. Isso reduz o tempo até o primeiro token visível de segundos para milissegundos. Implementação: Server-Sent Events (SSE) ou ReadableStream. No React: Vercel AI SDK com useChat(), ou manualmente com EventSource. Skeleton states enquanto o primeiro chunk não chega.
Problema → Solução
Problema
Esperar 5-10 segundos por uma resposta completa parece "travado". Usuário acha que falhou. Sem feedback visual do progresso da geração.Solução
Streaming: mostrar tokens sendo "digitados" em tempo real. Skeleton loader antes do primeiro token. Indicador de loading (⋯ pulsando) enquanto gera. Botão "Parar geração" para interromper. Abort signal para cancelar o stream quando o usuário navega.Dica de entrevista
Tip
Perceived performance importa mais que performance real. Uma resposta que aparece sendo "digitada" em 5 segundos parece mais rápida que uma resposta que aparece completa em 3 segundos. Sempre implemente streaming em interfaces de chat — é expectativa do usuário em 2025.Código
// Vercel AI SDK — streaming no Next.js
// app/api/chat/route.ts
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o'),
system: 'Você é um assistente técnico especializado em React.',
messages,
onFinish({ text, usage }) {
console.log('Tokens usados:', usage.totalTokens)
},
})
return result.toDataStreamResponse()
}
// app/page.tsx — client component
'use client'
import { useChat } from 'ai/react'
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat()
return (
<div>
{messages.map(m => (
<div key={m.id}>
<b>{m.role}:</b> {m.content}
{/* Cursor piscando durante geração */}
{isLoading && m.role === 'assistant' && <span className="cursor">▊</span>}
</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} placeholder="Pergunte algo..." />
{isLoading
? <button onClick={stop} type="button">⏹ Parar</button>
: <button type="submit">Enviar</button>
}
</form>
</div>
)
}Perguntas de entrevista
Q.Como implementar streaming no Next.js App Router?▾
A.Route Handler com Vercel AI SDK: `const result = streamText({ model, messages }); return result.toDataStreamResponse()`. No cliente: `useChat({ api: '/api/chat' })` — o hook gerencia o estado de streaming automaticamente. Para streaming custom: `TransformStream` ou `ReadableStream` no servidor, `EventSource` ou fetch com `response.body.getReader()` no cliente.
Q.Como cancelar um stream quando o usuário navega?▾
A.AbortController: passar `signal` para o fetch/stream. No useEffect de cleanup: `controller.abort()`. Com Vercel AI SDK: `stop()` do useChat() para interromper a geração. Sem cancelamento, o backend continua gerando e consumindo tokens (e dinheiro) mesmo com o usuário já em outra página.
Q.Como lidar com erros durante o streaming?▾
A.Erros podem acontecer no meio do stream. Estratégias: (1) mostrar o que foi gerado até o ponto do erro + mensagem de erro, (2) retry automático para transient errors, (3) fallback para chamada não-streaming se o streaming falhar. Com Vercel AI SDK: `onError` callback. Sempre mostrar feedback claro ao usuário quando a geração falha.
94/122← →