Sua UI de LLM provavelmente demonstra lindamente no Wi‑Fi do escritório. Aí ela encontra o mundo real: redes móveis que oscilam, usuários que atualizam a página no meio do stream, alternância entre primeiro/segundo plano do app e gente trocando do laptop para o celular. De repente, você está pagando em dobro por tokens de saída e os chamados ao suporte dizem “fica carregando sem parar”.
Server‑Sent Events (SSE) ainda é a forma mais prática de entregar streams de tokens na maioria dos provedores. Ele se dá bem com proxies, funciona sobre HTTP/2 e não exige dúplex total como WebSockets. Mas o SSE “de prateleira” é frágil. A boa notícia: dá para fortalecê‑lo. A recente onda de posts sobre tornar streams SSE retomáveis, canceláveis e multidispositivo está certa — e chegou a hora de transformar esse conselho em um blueprint de nível de produção que você possa entregar à sua equipe.
Os modos de falha pelos quais você está pagando
- Reexecuções abandonadas: Um usuário atualiza a página no meio da geração. Seu backend inicia uma nova chamada ao provedor. Você acabou de dobrar o gasto para a mesma solicitação.
- Computação fantasma após cancelamento: O usuário aperta parar; sua UI fecha a conexão TCP. Seu servidor não propaga o abort upstream, então o provedor continua gerando — e cobrando.
- Derrapagem multidispositivo: Um usuário abre a mesma conversa no web e no mobile. Ambos disparam uma geração. Você paga duas vezes, o conteúdo diverge e reconciliar os deltas vira bagunça.
- Buffering em middleboxes: CDNs e proxies fazem buffer do seu stream “para ajudar”. O tempo até o primeiro token (TTFT) salta de 300–500 ms para vários segundos. Usuários desistem.
- Progresso perdido em redes instáveis: 3–7% das conexões SSE móveis caem em uma janela de 60 segundos. Sem retomada, você refaz a geração inteira.
Se o seu produto movimenta 300M de tokens de saída/mês a $15 por milhão (típico para saída classe GPT‑4o), 8–12% de duplicação/desperdício são $360–$540/mês — pequeno no papel, mas isso se correlaciona com churn e carga de suporte. Em escala (bilhões de tokens, ou modelos mais caros), os dólares crescem rápido. Mais importante, streams quebrados destroem a confiança.
Framework de decisão: quando usar SSE, WebSockets ou WebRTC
- SSE se você só precisa de streaming de tokens servidor→cliente, semântica simples de reconexão e ampla compatibilidade com proxy/CDN. A maioria das UIs de LLM estilo chat se encaixa nisso.
- WebSockets se você precisa de dúplex real durante a geração (por exemplo, progresso de ferramentas em tempo real, interrupções do usuário que devem ser entregues instantaneamente através de NATs) e você controla o caminho na borda. Mais difícil de operar em escala de Internet.
- WebRTC se você estiver fazendo streams de áudio/vídeo com baixa latência e travessia de NAT. Útil para recursos de voz em streaming recém‑lançados por grandes provedores, mas exagero para tokens de texto.
Este post assume SSE. Se você optar por WebSockets ou WebRTC, a maioria das ideias de plano de controle ainda se aplica: IDs de stream, números de sequência, fan‑out e cancelamento.
A arquitetura que impede pagar em dobro
Não deixe os clientes se conectarem diretamente ao seu provedor de modelos. Coloque um Stream Broker leve na sua stack:
- Cliente abre SSE para sua borda em
/v1/streams/:stream_id. - Edge/Ingress termina TLS, desabilita buffering e encaminha para o Stream Broker (sticky por
stream_id). - Stream Broker é um worker sem estado com um cache em memória de curta duração (ou Redis/NATS) indexado por
stream_id. Ele faz três trabalhos:- Multiplexação/fan‑out: Se já houver uma geração em andamento para esse
stream_id, anexe o novo assinante; não inicie outra chamada ao provedor. - Retomada: Faça buffer de uma janela deslizante de deltas recentes com
event.idmonotonicamente crescente. Na reconexão comLast-Event-ID, reproduza as partes ausentes. - Cancelamento: Ao comando de parar do usuário, cancele a requisição ao provedor via um cliente HTTP abortável. Se o último assinante desconectar, cancele automaticamente.
- Multiplexação/fan‑out: Se já houver uma geração em andamento para esse
- Model Worker mantém as credenciais do provedor, solicita completions em streaming e normaliza os deltas específicos de cada provedor para um formato comum de eventos.
Mantenha o estado do stream em memória por 60–120 segundos após concluir/cancelar para acomodar trocas rápidas de dispositivo ou refresh de aba sem refazer a geração.
Identidade e sequenciamento do stream
- Derive
stream_idde forma determinística a partir deconversation_id+turn_id+ umepochde 32 bits. Se o usuário editar o prompt ou o estado do sistema, incremente o epoch para evitar retomadas obsoletas. - Emita os campos
iddo SSE como inteiros estritamente crescentes por stream, começando em 1. Evite IDs de chunks do provedor; renumere do seu lado. - Envie eventos com tipos:
delta,tool,heartbeat,complete,error. Nunca sobrecarreguedatacom sinais de controle.
Lógica de reconexão no cliente (a única que você deve ter)
- Ao cair a conexão, reconecte com backoff exponencial limitado a 2 segundos.
- Inclua
Last-Event-IDcom oidmais recente que você aplicou. Se o broker tiver o buffer, ele reproduz; caso contrário, retorna410 Gonepara forçar uma nova execução completa. - Renderize de forma idempotente. Se você vir um
idantigo, descarte; se faltar um, solicite uma reprodução direcionada via query (?from_id=123) após reconectar.
Cancelamento que de fato para a cobrança
SSE é mão única. Fechar a aba do navegador não garante que o provedor pare de gerar. O cancelamento precisa fluir pelo lado do servidor:
- Botão Parar → POST /v1/streams/:stream_id/cancel com um token com escopo de stream e curta duração (TTL de 60s). Não confie apenas na detecção de fechamento de conexão.
- Comportamento de cancel do broker:
- Marque o stream como cancelling; notifique todos os assinantes com um
event: errortrazendo um motivo tipado (user_cancelled,superseded). - Aborte a requisição HTTP ao provedor (AbortController ou token de cancelamento específico do cliente). Se o provedor não suportar abort, como fallback feche o socket TCP.
- Mantenha os últimos N deltas em memória para que uma retomada rápida mostre conteúdo parcial sem reinvocar o modelo.
- Marque o stream como cancelling; notifique todos os assinantes com um
- Single‑flight por padrão: Faça cumprir uma geração ativa por conversa. Um novo
startcancela automaticamente a execução anterior com o motivosuperseded.
Na prática, um cancelamento robusto reduz em 15–35% a média de tokens de saída consumidos por turno de conversa porque as pessoas costumam “aparar” respostas. Isso é dinheiro real em modelos premium e — mais importante — uma UX que passa sensação de controle.
Multidispositivo sem cobrança em dobro
Um usuário trocando do laptop para o celular não deveria disparar uma nova chamada ao modelo. Você quer computar uma vez, entregar para muitos:
- Anexe vários assinantes a uma única chamada upstream. O broker trata cada conexão de cliente como um assinante. Novos assinantes recebem uma reprodução rápida dos deltas em buffer (com base em
Last-Event-ID) e depois tokens ao vivo. - Faça fan‑out sobre um log de eventos compartilhado. Persista apenas deltas estruturados e metadados (índice de token, chamadas de ferramentas). Armazene o texto completo montado apenas em
completeou remonte on the fly. - Faça cumprir a política de concorrência entre dispositivos. Por exemplo: permita múltiplos leitores e um único iniciador. Novos iniciadores em outro dispositivo autocancelam o stream em voo com
superseded. - Evite duplicatas acidentais. Faça debounce de ações de
startpor 300–500 ms por conversa para evitar cliques duplos/inícios em corrida.
Em auditorias que fizemos para apps de consumo nos EUA, 5–12% das chamadas de LLM foram duplicadas por refresh de aba ou trocas rápidas de dispositivo. Um fan‑out com broker reduz isso para <2%, e o p95 do time‑to‑first‑token normalmente cai 30–50% quando o buffering é corrigido.
Configuração de edge e proxy (onde a maioria tropeça)
Middleboxes adoram fazer buffer. Seu trabalho é tornar o streaming inequívoco dos headers HTTP até a origem. Configuração básica:
- Headers de resposta do broker:
Content-Type: text/event-stream,Cache-Control: no-cache, no-transform,Connection: keep-alive,X-Accel-Buffering: no(honrado pelo Nginx) e envie um comentário de heartbeat: ping\n\na cada 10–15 segundos. - Nginx/Envoy: desabilite o proxy buffering para
text/event-stream, faça flush de chunks pequenos, aumente oproxy_read_timeoutpara cobrir gerações longas (ex.: 120s+). Garanta HTTP/2 habilitado no cliente; mantenha a semântica de chunking intacta. - CDN/Edge: muitas CDNs fazem buffering por padrão. Use recursos rotulados como “origin streaming”, “no buffering” ou ignore o cache para
text/event-stream. Definano-transformpara evitar modificação de conteúdo. Se seu provedor de edge não suportar streaming real nos planos grátis, desvie o caminho para SSE. - Conexões com provedores: use HTTP/2 onde houver suporte; defina buffers de escrita pequenos para liberar tokens prontamente. Alguns provedores fazem batch de tokens a menos que
Accept: text/event-streamestream=trueestejam definidos exatamente.
Peculiaridades dos provedores que você deve normalizar
- Formato do delta de tokens: Provedores divergem entre frames parciais vs. texto completo. Normalize para
event: deltacom{ id, text_delta, tool_calls_delta, usage? }. Monte o texto no cliente. - Motivos de parada: Exponha um conjunto consistente:
length,user_cancelled,content_filter,error. Mapeie os motivos específicos de cada provedor para os seus. - Semântica de abort: Algumas APIs só param ao fechar o TCP; outras aceitam cancelamento com escopo de requisição. Padronize via seu cliente HTTP e mantenha um teste de integração que verifique parada em <1 segundo após o cancel entre provedores.
- Apuração de uso: Não confie apenas no webhook assíncrono de uso do provedor. Calcule sua própria contagem de tokens a partir dos deltas para medição em tempo real e detecção de fraude.
Segurança: restrinja escopos ao stream, não ao usuário
- Tokens com escopo de stream: Emita um JWT utilizável apenas para
/v1/streams/:stream_ide/cancel, TTL ≤ 5 minutos. Incluaconversation_id,epoch, ID do usuário e ações permitidas. - Nada de PII em logs: Nunca logue
event.data. Registre apenasid,event, tamanhos em bytes e tempos. - Chaves do provedor ficam no servidor: Clientes nunca detêm chaves de API do modelo. O broker faz a mediação. Isso também viabiliza cancelamento central e fan‑out.
- Rate limits por conversa: Limite gerações simultâneas em voo a 1 por padrão. Políticas de burst ficam no nível da organização ou projeto.
Resiliência e planos de contingência
- TTL da janela de retomada: Mantenha um ring buffer em memória de 60–120 segundos por stream (~64–256 KB bastam). Se o buffer tiver sumido, retorne
410 Gonepara que o cliente saiba refazer. - Chave de bypass do edge: Coloque um feature flag que roteie SSE direto para a origem se a CDN começar a fazer buffering durante um incidente.
- Failover de provedor: Failover no meio do stream raramente é limpo. Prefira: detecção rápida de falha (1–2 segundos), cancelamento e, então, reinício transparente em um provedor secundário com um indicador claro na UI “mudamos para o modelo de backup”.
- Teste de caos na rede: Injete 3–5% de perda de pacotes, RTT variável de 250–600 ms e desconexões aleatórias de 1–3 segundos. Seu SSE deve continuar legível e retomável.
Modelo de dados: streams orientados a eventos vencem strings ad hoc
Persista cada stream como um log somente‑anexos de eventos estruturados:
- delta:
{ id, ts, text_delta, tool_delta?, usage_partial? } - heartbeat:
{ id, ts } - complete:
{ id, ts, stop_reason, usage_final } - error:
{ id, ts, code, retriable }
Na leitura, você consegue reconstruir o texto final, auditar uso com precisão e rodar análises sobre padrões de interrupção. E, se você introduzir A/Bs com múltiplos provedores, vai querer esse histórico.
Observabilidade: KPIs que provam que está funcionando
- TTFT (time‑to‑first‑token): mire <600 ms p50, <1,5 s p95 na banda larga; no mobile pode ser maior.
- Taxa de conclusão do stream: porcentagem de streams que terminam em
completevs.error/cancel. Acompanhe por dispositivo e tipo de rede. - Taxa de sucesso de retomada: porcentagem de reconexões que reproduzem deltas ausentes sem refazer tudo. Objetivo >90% dentro da janela de retomada.
- Supressão de duplicatas: parcela de inícios duplicados tentados que foram multiplexados em um stream existente. Você quer isso alto.
- Tempo de propagação do cancel: tempo entre o clique do usuário e o reconhecimento de abort upstream. Mire <1 segundo.
- Incidentes de buffering no edge: conte picos em TTFT e quedas súbitas na entrega de heartbeats; alerte quando detectar.
Um produto de marketplace que apoiamos cortou a queima duplicada de tokens em 9,2% e melhorou o p95 de TTFT em 41% após migrar para SSE com broker, com configuração adequada de edge e retomada.
Plano de rollout que você consegue lançar neste trimestre
Semana 0–2: Prove o broker
- Introduza um Stream Broker mínimo na sua camada de API. Normalize os deltas do provedor, emita SSE com
ide adicione heartbeats. - Desabilite buffering de ponta a ponta em uma rota canário. Meça TTFT e taxas de queda.
- Adicione
/cancele a propagação de abort a uma integração de provedor.
Semana 3–5: Torne‑o durável
- Adicione um ring buffer por stream (primeiro em memória; Redis Streams se precisar de retomada entre instâncias).
- Implemente retomada por
Last-Event-IDe replay rápido. Faça debounce de inícios duplicados. - Faça fan‑out para múltiplos assinantes. Verifique a troca multidispositivo sem cobrança em dobro.
Semana 6–8: Coloque guardrails e métricas no lugar
- JWTs com escopo de stream, rate limits e single‑flight por conversa.
- Dashboards para TTFT, sucesso de retomada, latência de cancel e supressão de duplicatas.
- Testes de caos com perda de pacotes artificial e desconexões intermitentes. Corrija até as métricas se sustentarem.
Trade‑offs e quando não fazer isso
- Complexidade: Um broker adiciona peças móveis. Se seu app é apenas interno, em redes estáveis e com baixo volume de chamadas, você pode conviver com um SSE ingênuo.
- Latência vs. consistência: Janelas de replay e normalização adicionam alguns milissegundos. Vale pela durabilidade; meça para ter certeza.
- Gerenciamento de estado: Persistir deltas adiciona armazenamento e risco de PII. Mantenha TTLs curtos, criptografe em repouso e evite logar payloads.
- Restrições do provedor: As semânticas de streaming de alguns vendors são inconsistentes. Normalize de forma agressiva e construa testes de contrato por API de modelo.
Por que agora
Duas coisas mudaram em 2026. Primeiro, recursos de LLM estão indo para mobile e contextos de baixa largura de banda, não só para web desktop. Segundo, “inteligência de voz” do modelo e eventos de ferramentas mais ricos aumentam a superfície para entrega parcial e cancelamento. Você não precisa de mais agentes; você precisa de controle de fluxo na camada de stream. Acerte isso e você corta desperdício, ganha velocidade e — mais crucial — faz seus recursos de IA parecerem confiáveis.
Principais aprendizados
- Coloque um Stream Broker entre clientes e provedores para multiplexar, retomar e cancelar de forma centralizada.
- Use
stream_iddeterminístico +event.idmonotonicamente crescente; retome comLast-Event-ID. - Implemente
/cancelexplícito e propague o abort pelo servidor; não dependa de fechamento de socket. - Desabilite buffering do CDN à origem; envie heartbeats e os headers corretos de SSE.
- Faça fan‑out de uma geração upstream para múltiplos dispositivos; faça debounce de inícios duplicados.
- Acompanhe TTFT, sucesso de retomada, latência do cancel e supressão de duplicatas para comprovar impacto.
- Espere 15–35% menos tokens de saída por turno e 30–50% de latência percebida mais rápida quando tudo estiver certo.
Author: Diogo Hudson Dias