Interrupções de streaming e SSE: recuperação sem duplicatas

Diferencie quedas de SSE, preserve a saída parcial e repita com segurança sem duplicar efeitos colaterais.

Uma solicitação de API em streaming só termina quando chega o evento terminal previsto pelo protocolo. Socket fechado, timeout do cliente ou o último fragmento de texto não comprovam conclusão. Após uma queda, preserve os eventos recebidos, a Request ID e o estado da operação. Antes de repetir, verifique efeitos colaterais: não existe continuação universal a partir do último token, e um retry cego pode executar uma ferramenta duas vezes.

Encerramento normal ou queda real

Server-Sent Events envia uma sequência de eventos por uma conexão HTTP longa. O cliente lê até um destes resultados:

created -> receiving -> completed \-> failed \-> disconnected

completed confirma sucesso terminal; failed é um erro entregue no stream; disconnected indica que o transporte terminou sem final confirmado. Esse último estado precisa de diagnóstico. OpenAI Responses usa eventos como response.created, fragmentos de output e response.completed; Anthropic Messages usa message_start, eventos de bloco de conteúdo, message_delta e message_stop. Não misture esses nomes no mesmo parser.

Quer reproduzir uma queda de streaming em uma solicitação controlada? Crie sua própria conta BetterToken e API Key, abra a referência de API e comece com um stream curto sem Tool Calls. Compare hora, modelo e status com o Dashboard. Só então adicione um retry limitado.

Dados para guardar na queda

Um log mínimo separa problema do cliente de problema do servidor:

{ "started_at": "2026-08-03T12:00:00Z", "protocol": "openai-responses", "request_id": "req_placeholder", "http_status": 200, "last_event_type": "response.output_text.delta", "events_received": 42, "bytes_received": 8192, "terminal_event_received": false, "client_error": "socket closed" }

Não salve API Key, prompt completo, argumentos de ferramentas ou resposta sensível. Texto parcial só deve ser guardado se a política da aplicação permitir. Em produção, hash da operação, número de eventos e último marcador de sequência seguro costumam ser mais úteis. A Request ID pode chegar em cabeçalhos HTTP ou em um evento; registre-a logo no início.

Verifique o parser antes da rede

O cliente precisa lidar com várias linhas data: no mesmo evento, linhas vazias, fragmentos UTF-8 no limite de chunks, tipos desconhecidos sem encerrar o processo, evento de erro depois de HTTP 200, evento terminal sem o último delta e argumentos de ferramenta divididos. Um chunk TCP não é um evento SSE: monte primeiro uma frame SSE completa e depois faça parse do JSON.

state = receiving for event in parse_sse(response_body): log_safe_metadata(event) apply_event_to_partial_result(event) if is_terminal_success(event): state = completed break if is_terminal_failure(event): state = failed break if connection_closed and state == receiving: state = disconnected

Implemente is_terminal_success e is_terminal_failure separadamente para Responses, Chat Completions e Messages. Considerar evento desconhecido como sucesso esconde o estado necessário para decidir um retry.

Verifique timeout em cada camada

Uma conexão longa atravessa timeout do SDK ou cliente HTTP, timeout idle/read da aplicação, reverse proxy, load balancer ou ingress, proxy corporativo, rede móvel ou doméstica e limite de geração no servidor. Timeout geral e idle timeout são diferentes. Se o modelo envia eventos regularmente, um idle timeout curto não deveria disparar; se pausas longas são aceitáveis, ajuste o valor à carga esperada.

Verifique também o buffering do proxy. Se ele acumula chunks SSE, o usuário não vê texto por muito tempo e recebe um bloco grande ou timeout. Meça o tempo até o primeiro evento e os intervalos entre eventos localmente, atrás do reverse proxy e em produção.

Saída parcial: mostrar, salvar ou descartar

Texto parcial pode ser útil na interface, mas seu estado deve ser explícito. Não mostre uma resposta interrompida como concluída.

  • streaming: o texto ainda muda;
  • complete: chegou evento terminal de sucesso;
  • partial: a conexão caiu depois de vários eventos;
  • failed: o protocolo entregou um erro;
  • cancelled: usuário ou aplicação interrompeu a solicitação.

Para partial, mantenha o texto recebido separado da nova geração. Unir duas gerações automaticamente é perigoso: o modelo pode repetir conteúdo, mudar a redação ou chamar ferramentas em outra ordem.

Quando repetir uma solicitação

A segurança do retry depende da ação.

Texto sem ações externas

Uma solicitação curta de texto normalmente pode ser repetida com tentativas limitadas. Mostre o resultado antigo como partial e a resposta nova como geração separada, ou substitua apenas depois de confirmação explícita.

Tool Calls e transações

Antes de repetir, verifique se a ferramenta já foi executada. Se o stream caiu depois de enviar o comando, uma segunda solicitação pode recriar uma issue, e-mail ou transação de pagamento. Use Idempotency Key no nível da ferramenta, sua própria Operation ID e um log de ações concluídas.

Tarefa longa de agente

“Continuar do último token” raramente é confirmado pelo protocolo. Restaure a tarefa a partir do estado salvo da aplicação: mensagens confirmadas, resultados de ferramentas e última etapa concluída. Fragmentos brutos de texto não são um estado consistente de agente.

Retry limitado com backoff

Uma política de retry precisa ter número finito de tentativas:

attempts = 0 while attempts < MAX_ATTEMPTS: result = run_request(operation_id) if result.completed: return result if not result.retryable: raise result.error wait(base_delay * 2**attempts + random_jitter) attempts += 1

O erro ser retryable depende de erro de protocolo, HTTP status, evento terminal e efeitos colaterais. 401, Model ID incorreto e JSON inválido não são resolvidos com espera. 429, 5xx temporário ou falha de transporte às vezes permitem novo envio, mas apenas com limite e considerando os cabeçalhos do provider. Backoff não substitui classificação de erro.

Teste mínimo

  1. Envie uma solicitação curta de streaming sem ferramentas.
  2. Anote todos os tipos de evento e espere o evento terminal.
  3. Encerre artificialmente o cliente após alguns eventos.
  4. Verifique que o resultado é partial.
  5. Teste um retry limitado.
  6. Repita atrás do proxy de produção.
  7. Compare as duas solicitações por hora, modelo e status no Dashboard.

Consulte os tipos exatos em OpenAI Streaming Responses e Anthropic Messages Streaming.

FAQ

É possível continuar um stream do último token?

Não há mecanismo universal. Guarde resultado parcial e estado da aplicação, depois siga os recursos da API concreta. Uma nova solicitação pode repetir ou alterar o texto.

Por que termina com erro apesar de HTTP status 200?

Os cabeçalhos chegam antes da geração completa. O erro pode aparecer depois como evento de protocolo ou queda de transporte; status sozinho não basta.

É preciso repetir após toda desconexão?

Não. Primeiro verifique evento terminal, erro, Request ID e efeitos colaterais. Retry de Tool Call exige idempotência.

Onde procurar se funciona localmente?

Verifique reverse proxy, load balancer, idle timeout, buffering e rede corporativa. Compare intervalos entre eventos antes e depois de cada camada.

O que verificar no BetterToken?

Abra Dashboard e compare hora, modelo, status e uso. Confira parâmetros atuais na referência de API; nunca envie ao suporte a API Key completa ou prompt sensível.

Quer otimizar seu fluxo de trabalho com LLMs?

Conecte modelos por uma única API, gerencie chaves e controle os gastos com IA.