Uma solicitação de streaming só é segura para ser repetida quando três coisas são verdadeiras ao mesmo tempo. Nada chegou ao seu cliente. Nada observável foi contabilizado. E a solicitação não carrega estado no lado do servidor. Após o primeiro evento de saída, a atitude correta é relatar a falha em vez de repeti-la.
O TokenLab aplica essa regra em seu gateway para o streaming da Responses API, tanto via HTTP quanto via WebSocket. O caminho do WebSocket foi alterado em 28/09/2026 para corresponder ao HTTP.
Por que um stream é diferente de uma solicitação normal
Uma chamada sem streaming retorna um corpo ou um erro. Você pode repetir o erro porque não recebeu nada de volta.
Um stream entrega a saída para você antes que a solicitação seja concluída. O primeiro evento de saída é o ponto sem retorno. Se a conexão cair depois disso, você terá um texto parcial. Repetir a solicitação significa gerar a mesma resposta novamente e pagar por ela duas vezes. Você também pode duplicar uma chamada de ferramenta que seu agente já executou.
O guia de streaming do TokenLab afirma isso diretamente:
Após a chegada do primeiro evento, um stream interrompido está incompleto e não é reiniciado automaticamente.
Portanto, seu cliente precisa de um bit de estado local: saw_output. Ele muda para verdadeiro no momento em que qualquer saída chega ao seu código. Toda decisão de repetição lê esse bit primeiro.
Um stream que termina sem response.completed é uma falha. Não presuma que o texto que você tem está completo. Trate os eventos response.failed, response.incomplete e error.
A decisão de repetição, ponto a ponto
O TokenLab repete uma solicitação uma vez em outra rota disponível quando todas estas condições são atendidas: a solicitação é stateless (sem estado), nada chegou ao cliente, nenhum resultado ou uso foi observado para a tentativa falha, e a falha é um evento pré-saída repetível ou um erro de leitura upstream antes do primeiro evento. No máximo, uma repetição ocorre por solicitação. Se a substituição falhar antes da saída também, essa falha não é repetida novamente.
Fonte: Guia de streaming do TokenLab e comportamento do gateway, observado em 28/09/2026.
| Ponto de falha | Repetido pelo TokenLab? | Motivo |
|---|---|---|
Evento pré-saída repetível (response.failed ou um evento de error marcado como repetível, como um erro de upstream sobrecarregado ou interno) |
Sim, uma vez, se a solicitação for stateless | Nada chegou ao cliente e nenhum uso foi observado, portanto, uma segunda execução é invisível. |
| Quebra de stream de upstream (erro de leitura) antes do primeiro evento | Sim, uma vez, se a solicitação for stateless | Mesma janela. O cliente não possui saída nem cobrança. |
| Segunda falha antes da saída, após uma repetição | Não | O limite é de uma repetição por solicitação. |
| Qualquer falha após a saída chegar ao cliente | Não | O cliente já possui texto parcial. Uma repetição duplicaria a saída e o custo. |
Resposta armazenada (store), continuação (previous_response_id) ou solicitação vinculada à origem |
Não | Uma segunda execução poderia criar uma segunda resposta armazenada ou divergir o estado da conversa. |
| Timeout do primeiro evento | Não | O upstream pode ainda estar gerando. Uma repetição poderia executar o mesmo trabalho duas vezes enquanto a primeira tentativa continua. |
| Estouro de buffer pré-saída | Não | O limite é local ao gateway. O mesmo prefixo grande provavelmente atingiria o limite novamente na próxima rota. |
| Cliente desconectado | Não | O cliente parou de ouvir. |
| Falha determinística, por exemplo, uma solicitação inválida | Não | Repetir não pode mudar o resultado. Entregue sem alterações. |
| Falha que já carrega uso | Não | A tentativa foi contabilizada. Entregue sem alterações. |
| Nenhuma outra rota disponível | Não | Não há para onde enviar. O cliente recebe a falha com seu próprio código. |
Quando uma falha não é repetida, ou não resta nenhuma outra rota, você a recebe com seu próprio código de erro. Exemplos públicos: stream_read_error quando o stream de upstream quebra, e upstream_stream_buffer_limit em estouro de buffer. Se a seleção de rota falhar após uma decisão de repetição, o turno do WebSocket termina com websocket_response_failed (status 500) e a cobrança reservada é reembolsada.
O faturamento segue a mesma linha. Você paga apenas pela tentativa entregue. Uma solicitação repetida pode ter sido executada no upstream duas vezes, e esse custo extra de upstream é do TokenLab, porque nada chegou a você da primeira tentativa. Um turno falho que não entrega nada é reembolsado.
Um detalhe de tempo é importante para o tratamento de erros. Antes que a saída comece, o gateway mantém response.created e response.in_progress até que o primeiro evento de saída ou uma falha chegue, por no máximo 10 segundos. Esses eventos retidos chegam a você junto com a primeira saída ou com o evento terminal. A ordem e o conteúdo permanecem inalterados. Você apenas os vê um pouco mais tarde. Esses 10 segundos são um máximo, não um atraso típico.
O que mudou para WebSocket em 28/09/2026
O TokenLab fornece a Responses API via streaming HTTP ("stream": true, server-sent events) e via WebSocket em wss://api.tokenlab.sh/v1/responses, onde o cliente envia eventos response.create. As respostas via WebSocket são sempre em streaming. Elas não suportam background ou response.cancel. Cada conexão lida com uma resposta ativa por vez por até 60 minutos.
Antes da mudança, os dois caminhos discordavam. O HTTP retinha os eventos de ciclo de vida e repetia falhas pré-saída stateless. O WebSocket encaminhava response.created imediatamente e entregava falhas pré-saída ao cliente, reembolsando-as. O mesmo problema de upstream produzia uma resposta limpa no HTTP e um erro no WebSocket.
O caminho do WebSocket agora segue a regra do HTTP, incluindo a repetição de um stream que quebra antes que qualquer evento chegue. Internamente, a maioria das falhas de upstream vistas em turnos de WebSocket acontecia antes de qualquer saída. Essa é exatamente a janela onde uma repetição é segura.
O gateway melhora o caso de falha pré-saída. Ele não garante que um stream seja concluído.
Como a mudança foi implementada sem quebrar outros comportamentos
O trabalho seguiu um processo criado para detectar mudanças silenciosas de comportamento.
- Bloqueio de comportamento. Antes da mudança, cada cenário de turno de WebSocket era registrado como um fixture: os quadros que o cliente recebe, as chamadas de upstream feitas e o resultado do faturamento. A suíte cresceu para 63 cenários registrados durante este trabalho. Uma mudança de comportamento deve ser declarada antecipadamente. Apenas os fixtures nomeados nessa declaração podem mudar. Todos os outros fixtures devem permanecer byte-idênticos.
- Verificações de mutação. Cada nova regra de decisão foi testada alterando-a deliberadamente, como repetir um timeout de primeiro evento ou não repetir uma falha de leitura, e confirmando que o bloqueio falha.
- Uma revisão de segurança. A primeira versão também tornou o caso de estouro de buffer repetível, sob a alegação de paridade com o HTTP. A revisão mostrou que o HTTP nunca repete esse caso, pelo motivo na tabela. Um acompanhamento restaurou o comportamento antigo e adicionou cenários de limite: uma segunda falha de leitura não é repetida, nenhuma rota restante, uma falha após um
response.createdretido e um stream de substituição que então quebra.
Os logs de solicitação de um turno que teve sucesso após uma repetição agora também registram a tentativa falha anterior, como o HTTP já fazia.
Código do cliente que detém a decisão de repetição
Defina as repetições automáticas do SDK como 0 para chamadas de streaming. Isso mantém a decisão em seu código. Mantenha a decisão de repetição em um só lugar, não espalhada pelos manipuladores. Para erros HTTP, respeite retryable e retry_after conforme descrito no guia de tratamento de erros e mantenha os IDs de solicitação.
SSE via HTTP
import os
from openai import OpenAI
with OpenAI(
api_key=os.environ["TOKENLAB_API_KEY"],
base_url="https://api.tokenlab.sh/v1",
timeout=30.0,
max_retries=0, # detenha a decisão de repetição em vez de reenviar um stream lido pela metade
) as client:
completed, saw_output = False, False
with client.responses.create(
model="gpt-5.6-terra",
input="Responda com uma frase curta sobre repetições.",
stream=True,
) as stream:
for event in stream:
if event.type == "response.output_text.delta":
saw_output = True
print(event.delta, end="", flush=True)
elif event.type == "response.completed":
completed = True
elif event.type in {"response.failed", "response.incomplete", "error"}:
raise RuntimeError(f"{event.type} after_output={saw_output}")
if not completed:
raise RuntimeError(f"stream fechado antes de response.completed, after_output={saw_output}")
print()
O exemplo usa o OpenAI SDK 2.15.0 contra https://api.tokenlab.sh/v1 com max_retries=0. Ele rastreia saw_output e levanta erro nos eventos response.failed, response.incomplete e error, e em um stream que fecha antes de response.completed. Verificado em produção em 28/09/2026 com gpt-5.6-terra.
Se a falha chegar com saw_output == false e a solicitação for elegível (stateless, com uma falha repetível), o TokenLab já a repetiu uma vez; respostas armazenadas, continuações e timeouts de primeiro evento não foram repetidos de forma alguma. Decida no nível do aplicativo se uma nova solicitação é aceitável, pois uma nova solicitação é uma nova geração. Se saw_output == true, relate a falha e mostre o que você tem, ou descarte o texto parcial deliberadamente.
WebSocket
import asyncio
import json
import os
import websockets
URL = "wss://api.tokenlab.sh/v1/responses"
TERMINAL = {"response.completed", "response.failed", "response.incomplete", "error"}
async def run_turn(prompt: str) -> str:
headers = {"Authorization": f"Bearer {os.environ['TOKENLAB_API_KEY']}"}
async with websockets.connect(URL, additional_headers=headers, max_size=None) as ws:
await ws.send(json.dumps({
"type": "response.create",
"model": "gpt-5.6-terra",
"input": prompt,
"store": False,
}))
text, saw_output = [], False
async for raw in ws:
event = json.loads(raw)
kind = event.get("type")
if kind == "response.output_text.delta":
saw_output = True
text.append(event["delta"])
elif kind in TERMINAL:
if kind != "response.completed":
# Após o início da saída, uma falha é final para este turno.
# Reenvie apenas se seu aplicativo puder descartar o texto parcial.
raise RuntimeError(f"{kind} after_output={saw_output}: {json.dumps(event)[:300]}")
return "".join(text)
raise RuntimeError(f"socket fechado antes de um evento terminal, after_output={saw_output}")
print(asyncio.run(run_turn("Responda com uma frase curta sobre repetições.")))
O exemplo usa websockets 16.0, conecta-se a wss://api.tokenlab.sh/v1/responses com um cabeçalho Bearer, envia um response.create com store: false e coleta response.output_text.delta. Ele levanta erro com after_output em qualquer evento terminal não concluído ou fechamento antecipado. Verificado em produção em 28/09/2026 com gpt-5.6-terra.
O sinalizador after_output tem a mesma ideia que saw_output. Ele informa ao seu código de chamada se um novo turno é possível sem duplicar efeitos colaterais.
Checklist para sua própria lógica de repetição
- Trate um stream que termina sem
response.completedcomo uma falha, sempre. - Rastreie um booleano para saber se a saída chegou ao seu código. Mude-o no primeiro evento de saída, não no primeiro evento de ciclo de vida.
- Uma falha pré-saída em uma solicitação elegível já teve sua única repetição de gateway; uma tentativa adicional é sua decisão.
- Após a saída parcial, reenvie apenas se seu aplicativo puder descartar o texto parcial e aceitar pagar por duas gerações.
- Em loops de agente, verifique se o stream parcial já continha uma chamada de ferramenta na qual seu código agiu. Não repita um turno cujos efeitos colaterais você não pode desfazer.
- Para respostas armazenadas e continuações de
previous_response_id, inspecione qual estado existe antes de reenviar qualquer coisa. - Defina as repetições de streaming como 0 em seu SDK e mantenha a decisão de repetição em uma única função.
- Registre os IDs de solicitação para que você possa corresponder uma resposta entregue às tentativas por trás dela.
FAQ
O TokenLab reinicia um stream após a saída parcial?
Não. Uma vez que a saída chegou ao seu cliente, uma falha é relatada e nunca repetida. Você possui texto parcial, portanto, um reinício duplicaria a saída e o custo. Seu aplicativo decide se deve mostrar, truncar ou descartar o que tem.
Serei cobrado duas vezes se o gateway repetir minha solicitação?
Não. Você paga apenas pela tentativa entregue. Uma solicitação repetida pode ter sido executada no upstream duas vezes, mas nada chegou a você da primeira tentativa, e esse custo extra de upstream é do TokenLab. Um turno falho que não entrega nada é reembolsado.
Por que um timeout de primeiro evento não é repetido?
Porque o upstream pode ainda estar gerando. Uma repetição poderia executar o mesmo trabalho duas vezes enquanto a primeira tentativa continua. Um timeout de primeiro evento é tratado de forma diferente de um erro de leitura que quebra o stream antes do primeiro evento.
Posso repetir uma resposta armazenada ou uma continuação de previous_response_id?
Não automaticamente. O TokenLab nunca repete respostas armazenadas, continuações ou solicitações vinculadas à origem, porque uma segunda execução poderia criar uma segunda resposta armazenada ou divergir o estado da conversa. Verifique qual estado existe antes de reenviar qualquer coisa e apenas reenvie se seu aplicativo puder reconciliar esse estado.
Se você quiser observar o stream de eventos brutos por conta própria, crie uma chave de API e registre cada tipo de evento que seu cliente recebe. O guia de streaming e o guia de tratamento de erros cobrem todo o conjunto de eventos. Para obter informações sobre como o gateway roteia e recupera, consulte TokenLab AI API reliability infrastructure e Responses API vs Chat Completions for agents.
Fontes
- https://docs.tokenlab.sh/guides/streamingObservado em 2026-09-28
- https://docs.tokenlab.sh/guides/error-handlingObservado em 2026-09-28



