Radar IA 19 de outubro de 2026
✨ IA MÁGICA Agentes · Modelos · Automação
ÚLTIMAS
Tutoriais e Guias

Passo a passo para configurar streaming de respostas com Server-Sent Events (SSE) em FastAPI e React

Guia prático e código completo para implementar streaming de tokens via Server-Sent Events (SSE) integrando backend FastAPI e frontend React.

Passo a passo para configurar streaming de respostas com Server-Sent Events (SSE) em FastAPI e React
Imagem: capa editorial — Tutoriais e Guias

Em aplicações web corporativas baseadas em modelos de inteligência artificial, aguardar a geração completa de uma resposta longa de dois mil tokens pode demorar de dez a vinte segundos. Esse tempo de tela congelada transmite a falsa impressão de que o sistema travou, elevando o abandono e a insatisfação do usuário. A arquitetura de transmissão contínua com Server-Sent Events (SSE), unindo um backend robusto em FastAPI a uma interface responsiva em React, é o padrão moderno para entregar respostas token a token com agilidade perceptiva imediata.

Diferente de websockets pesados que exigem protocolos bidirecionais complexos de conexão e balanceamento, o SSE utiliza conexões HTTP padrão unidirecionais, facilitando a passagem por firewalls corporativos, simplificando o tratamento de cancelamentos de requisição e economizando recursos computacionais do servidor.

Resumo rápido

  • Streaming de respostas token a token com Server-Sent Events (SSE) sobre protocolo HTTP/1.1 ou HTTP/2.
  • Tempo percebido de resposta reduzido de quinze segundos para menos de meio segundo na interface.
  • Implementação assíncrona escalável em Python com FastAPI e Starlette EventSourceResponse.
  • Consumo limpo no frontend React com hooks customizados e controle de cancelamento via AbortController.
  • Manutenção de integridade visual com renderização dinâmica de markdown e blocos de código sem travamento.

Por que adotar Server-Sent Events em vez de WebSockets ou Polling

O desenvolvimento de interfaces conversacionais exige escolher a estratégia de transporte ideal entre o cliente e o servidor. A abordagem ingênua de requisições síncronas bloqueia a interface do navegador enquanto o modelo processa toda a saída textual. Já o long-polling tradicional sobrecarrega a rede com chamadas repetidas e headers desnecessários.

Muitas equipes cogitam WebSockets por hábito, mas essa tecnologia traz complexidades operacionais que não se justificam em cenários de inteligência artificial generativa. A comunicação com o modelo é intrinsecamente assimétrica: o usuário envia uma mensagem curta com a pergunta e, a partir desse instante, o fluxo é exclusivamente de recebimento contínuo de tokens de texto vindos do servidor.

O Server-Sent Events encaixa-se perfeitamente nessa dinâmica. Trata-se de uma especificação nativa do padrão HTML5 baseada em conexões HTTP persistentes com o cabeçalho Content-Type: text/event-stream. O servidor empurra dados formatados como eventos individuais, e o navegador utiliza a API nativa ou leitores de fluxo assíncronos (Fetch ReadableStream) para consumir o conteúdo de forma transparente.

Para aprofundar sua esteira de desenvolvimento, sugerimos ler nosso guia sobre ferramentas de IA para programar com produtividade e a documentação sobre saídas estruturadas e chamadas de funções.

Arquitetura do backend com FastAPI assíncrono

No lado do servidor, o FastAPI destaca-se pela sua natureza totalmente assíncrona baseada em ASGI (Asynchronous Server Gateway Interface) e tipagem estrita com Pydantic. Para transmitir tokens sem travar a thread de execução, o endpoint deve operar como um gerador assíncrono (async generator).

Abaixo apresentamos a estrutura de um endpoint em FastAPI utilizando a biblioteca oficial de inferência:

from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import asyncio
import json

app = FastAPI()

async def generate_token_stream(prompt: str):
    # Chamada real ao SDK de inferencia com stream=True
    tokens = ["A ", "inteligência ", "artificial ", "em ", "São Paulo ", "acelera ", "empresas."]
    for token in tokens:
        await asyncio.sleep(0.05) # Simula latencia de geracao
        yield f"data: {json.dumps({'text': token})}

"
    yield "data: [DONE]

"

@app.post("/api/chat/stream")
async def chat_stream(request: Request):
    body = await request.json()
    prompt = body.get("prompt", "")
    return StreamingResponse(
        generate_token_stream(prompt),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no" # Essencial para Nginx nao reter o fluxo
        }
    )

Implementação no frontend React com AbortController

No ecossistema React, o desafio central do streaming consiste em atualizar o estado da interface com alta frequência sem disparar renderizações redundantes que degradem a performance da página. Além disso, o usuário precisa ter o poder de interromper a geração a qualquer momento clicando em um botão “Parar geração”.

A ferramenta ideal para esse cancelamento é o AbortController nativo do JavaScript moderno. Ao acoplar o sinal de aborto à requisição fetch, a conexão TCP é finalizada instantaneamente quando o usuário clica em parar ou navega para outra aba, economizando custos de tokens no servidor.

Veja o padrão recomendado de hook no React:

import React, { useState, useRef } from 'react';

export function ChatComponent() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [isStreaming, setIsStreaming] = useState(false);
  const abortControllerRef = useRef(null);

  const sendMessage = async () => {
    if (!input.trim() || isStreaming) return;
    setIsStreaming(true);
    abortControllerRef.current = new AbortController();

    const userMessage = { role: 'user', content: input };
    setMessages(prev => [...prev, userMessage, { role: 'assistant', content: '' }]);
    setInput('');

    try {
      const response = await fetch('/api/chat/stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt: userMessage.content }),
        signal: abortControllerRef.current.signal
      });

      const reader = response.body.getReader();
      const decoder = new TextDecoder('utf-8');
      let done = false;

      while (!done) {
        const { value, done: readerDone } = await reader.read();
        done = readerDone;
        if (value) {
          const chunk = decoder.decode(value);
          const lines = chunk.split('
');
          for (const line of lines) {
            if (line.startsWith('data: ') && !line.includes('[DONE]')) {
              const data = JSON.parse(line.replace('data: ', ''));
              setMessages(prev => {
                const next = [...prev];
                const last = next[next.length - 1];
                last.content += data.text;
                return next;
              });
            }
          }
        }
      }
    } catch (err) {
      if (err.name !== 'AbortError') console.error('Erro no streaming:', err);
    } finally {
      setIsStreaming(false);
    }
  };

  const stopGeneration = () => {
    if (abortControllerRef.current) abortControllerRef.current.abort();
    setIsStreaming(false);
  };

  return (
    <div>
      {/* Interface renderizada */}
    </div>
  );
}

Comparativo de arquitetura de rede: SSE vs WebSockets vs Polling

A tabela a seguir consolida os prós e contras das abordagens técnicas de transporte em aplicações com IA generativa:

Característica Técnica Server-Sent Events (SSE) WebSockets Polling Convencional
Direção da comunicação Unidirecional (servidor para cliente) Bidirecional simultâneo Bidirecional por requisições pontuais
Protocolo de transporte HTTP padrão (80 / 443) Protocolo WS / WSS dedicado Múltiplos ciclos HTTP independentes
Passagem por proxies corporativos Transparente e sem bloqueios Frequentemente bloqueado em redes restritas Totalmente compatível
Reconexão automática Nativa no navegador via EventSource Exige código de reconexão customizado Controlada manualmente por timeouts
Consumo de memória no servidor Muito baixo (threads assíncronas simples) Moderado a alto (manutenção de sockets) Elevado (picos de requisições repetidas)
Adequação para streaming de LLM Ótima (padrão ouro de engenharia) Superdimensionado para fluxo simples Inadequado (gera atrasos e flickering)

Exemplo: LegalTech em Campinas acelera pareceres de contratos

Uma empresa de tecnologia jurídica em Campinas desenvolveu um assistente web para advogados de departamentos corporativos. O software recebe minutas de contratos complexos e gera relatórios com apontamentos de riscos trabalhistas e cláusulas de indenização.

Na primeira versão do produto, o sistema utilizava chamadas HTTP síncronas. Quando o advogado solicitava a análise de um contrato de vinte páginas, o botão de envio exibia um ícone de carregamento e o usuário ficava olhando para uma tela em branco durante dezessete segundos. Muitos advogados acreditavam que a página havia falhado, recarregando a aba do navegador repetidamente e disparando novas requisições duplicadas que encareciam a fatura do servidor.

A equipe reescreveu o backend em FastAPI e adaptou o frontend React com o hook de streaming por SSE. Com a mudança, o primeiro parágrafo de análise surge na tela em apenas quatrocentos milissegundos. Conforme o advogado lê as primeiras recomendações preliminares, o restante do parecer vai sendo digitado dinamicamente na tela. A sensação de lentidão desapareceu por completo, as queixas de instabilidade no suporte técnico caíram noventa e cinco por cento e o tempo de permanência ativa na plataforma dobrou.

Para assegurar acessibilidade universal em conformidade com as diretrizes do WCAG, a interface web deve anunciar atualizações dinâmicas de streaming para leitores de tela utilizando regiões ativas (aria-live polite). Isso garante que usuários com deficiência visual possam acompanhar o ritmo da geração sem que o leitor de tela seja sobrecarregado por anúncios frenéticos a cada caractere transmitido.

Erros comuns

  • Manter buffers ativos no Nginx ou Cloudflare: Esquecer de adicionar o header X-Accel-Buffering: no, fazendo com que o servidor proxy retenha os tokens em memória e envie a resposta inteira de uma vez só, matando o efeito do streaming.
  • Não tratar o fechamento de conexão no backend: Não monitorar quando o cliente fecha a aba, deixando o servidor gerando tokens e gastando dinheiro de API inutilmente.
  • Manipular o DOM diretamente sem biblioteca de sanitização: Renderizar markdown cru recebido em tempo real sem utilizar bibliotecas seguras (como DOMPurify), expondo a aplicação a riscos de injeção de script (XSS).
  • Excesso de chamadas a setStates no React: Atualizar estados complexos a cada caractere individual em vez de agrupar pequenos pacotes de tokens, gerando perda de taxa de quadros por segundo em computadores mais modestos.
  • Esquecer a limpeza de referências com AbortController: Criar novas instâncias de controle sem cancelar a requisição anterior, provocando sobreposição de textos na mesma janela de conversa.

Perguntas frequentes

O Server-Sent Events funciona em navegadores móveis?

Sim. O SSE e a API Fetch com Streams são suportados por todos os navegadores modernos para iOS (Safari) e Android (Chrome e Firefox) há muitos anos.

É possível enviar metadados ou tokens de custo junto com o texto?

Sim. O payload enviado em cada evento pode ser um objeto JSON serializado contendo o fragmento de texto, a contagem acumulada de tokens e até referências bibliográficas encontradas.

Como lidar com quebras de linha em blocos de código markdown durante o streaming?

Recomenda-se utilizar renderizadores de markdown incrementais (como react-markdown com remark-gfm), que toleram trechos incompletos de formatação sem quebrar o layout da página.

O SSE mantém a conexão aberta indefinidamente?

Não. Assim que o gerador do backend atinge o fim da geração ou envia a mensagem [DONE], o stream é encerrado formalmente pelo servidor, liberando os recursos de rede.

Posso usar autenticação via token JWT com Server-Sent Events?

Sim. Ao consumir o stream utilizando a API fetch nativa do JavaScript, você pode incluir cabeçalhos padrão como Authorization: Bearer <token> na requisição inicial normalmente.

Fontes e leitura recomendada

Sobre

A Redação IA Mágica é formada por especialistas e pesquisadores em inteligência artificial aplicada, agentes autônomos, automações no-code/pro-code e grandes modelos de linguagem (LLMs). Nossos conteúdos cobrem implementações reais, análises e tutoriais aprofundados para profissionais e empresas.