O texto começa a aparecer no navegador, mas o usuário ainda não sabe o que está acontecendo. O agente pode estar chamando uma ferramenta, esperando aprovação, repetindo uma tentativa ou encerrando com erro. Se a interface só recebe tokens, ela parece viva e continua sem um estado confiável.

Para transmitir o progresso de um agente de IA, envie eventos de execução, não somente pedaços da resposta. Cada evento precisa identificar a execução, ter uma sequência, declarar seu tipo e carregar apenas dados que a interface pode exibir. O guia sobre execução durável de agentes de IA cuida da recuperação do trabalho; este texto cuida da entrega do estado para o navegador.

Este artigo usa SSE como ponto de partida para um fluxo servidor para navegador e compara WebSockets e polling. O código de integração com o SDK é ilustrativo e não foi executado neste repositório. A proposta é um contrato que você pode testar contra o runtime escolhido.

Diagrama mostra um agente de IA enviando eventos de início, tool, aprovação e conclusão para uma interface.

Resposta curta

  • Transmita eventos normalizados de run, etapa, tool, aprovação, falha e conclusão.
  • Use SSE para atualizações unidirecionais no navegador; use WebSockets quando o cliente também precisa controlar o run em tempo real.
  • Inclua runId, sequence, type e estado terminal em cada evento.
  • Persista o estado e uma janela de replay. A conexão é um canal de entrega, não a fonte da verdade.

Por que transmitir tokens não mostra o progresso real?

Tokens descrevem texto parcial. Eles não dizem se o agente iniciou uma tool, se a tool terminou, se outra pessoa precisa aprovar uma ação ou se o runtime concluiu as tarefas internas depois do último token. A documentação do OpenAI Agents SDK, "Streaming" separa eventos brutos do modelo, itens de execução e mudanças do agente. A interface precisa preservar essa diferença.

Uma barra de progresso também não deve ser calculada pelo tamanho da resposta. Um agente pode produzir texto enquanto ainda espera uma ferramenta. Pode chamar uma segunda ferramenta sem gerar texto entre elas. Pode terminar sem uma resposta útil. O evento precisa representar o que o runtime sabe, e não o que parece convincente na tela.

Uma divisão prática é separar dois fluxos. O primeiro leva texto parcial para a experiência de conversa. O segundo leva fatos de execução para o painel de atividade. Ambos podem sair pela mesma conexão, mas têm tipos, permissões e regras de retenção diferentes. Nunca entregue raciocínio privado ou argumentos sensíveis apenas porque o SDK os colocou no stream.

O contrato mais útil não tenta transformar toda atividade em porcentagem. Ele mostra o último fato confirmado: "a busca terminou", "a aprovação está pendente" ou "a execução falhou". Uma sequência honesta de estados ajuda mais que um percentual inventado para um trabalho cujo tamanho o sistema não conhece.

Qual transporte escolher para eventos de um agente?

SSE é um bom padrão quando o navegador precisa acompanhar eventos e o servidor é quem controla o fluxo. O browser reconecta um EventSource quando a conexão cai, e o formato permite nomear o evento, enviar dados e associar um ID. A referência da MDN, "Using server-sent events" documenta também comentários de keep-alive e o campo retry.

Use WebSockets quando a interface precisa enviar comandos durante o run, como cancelar, responder uma aprovação ou adicionar uma entrada sem abrir outra requisição. O custo é maior: você passa a administrar mensagens nos dois sentidos, autorização por conexão, encerramento e reconexão. Google Cloud, "Host AI agents on Cloud Run resources" lista streaming HTTP e WebSockets como opções para interações com agentes.

Polling continua sendo uma escolha válida quando a execução é durável e a interface só precisa consultar o estado. Ele simplifica proxies, reconexão e escala, mas pode atrasar a visualização e gerar leituras repetidas. Escolha o transporte a partir do controle necessário, não do desejo de mostrar uma animação em tempo real.

Necessidade da interface Escolha inicial O contrato ainda precisa de
Receber eventos do servidor SSE IDs, replay, autorização e estado final
Receber e enviar comandos ao vivo WebSockets Mensagens nos dois sentidos, heartbeat e fechamento explícito
Consultar um estado durável Polling Versão do estado, intervalos e resposta idempotente

Para uma implantação em Cloud Run, o comparativo entre Service, Jobs e worker pools explica a fronteira de execução. O endpoint que transmite eventos não precisa ser o processo que faz todo o trabalho. Um serviço pode acompanhar eventos persistidos enquanto um worker executa a tarefa.

Como deve ser o envelope de um evento?

Um envelope de evento deve ser pequeno, versionado e suficiente para renderizar uma mudança de estado sem conhecer os objetos internos do SDK. O runtime deve definir runId e sequence; o navegador pode descartar um evento repetido e pedir os eventos que faltam. A forma abaixo é uma sugestão de domínio, não um formato oficial do OpenAI Agents SDK.

type AgentEvent = {
  version: 1;
  runId: string;
  sequence: number;
  type:
    | "run.started"
    | "step.started"
    | "tool.called"
    | "tool.completed"
    | "approval.required"
    | "run.failed"
    | "run.completed";
  status: "running" | "waiting" | "failed" | "completed";
  occurredAt: string;
  payload: Record<string, unknown>;
};

O cliente precisa conhecer uma máquina de estados pequena. run.started pode levar a step.started, tool.called, approval.required, run.failed ou run.completed. Uma aprovação pendente não é falha nem conclusão. Um timeout de rede também não prova que a tool não executou. O backend deve consultar o estado da execução antes de emitir uma transição final. Se o fluxo tem muitas transições, a máquina de estados para agentes de IA ajuda a decidir quais estados são legítimos.

Os eventos públicos devem conter nomes de ferramentas que o usuário pode ver, resumos curtos e identificadores não sensíveis. Remova tokens, prompts completos, credenciais, argumentos privados e resultados grandes. O guia de observabilidade de agentes de código no CI detalha como separar uma trilha de auditoria restrita do resumo que chega ao consumidor.

Como adaptar o stream do runtime para a interface?

O adaptador deve traduzir eventos do SDK para o contrato da aplicação. No OpenAI Agents SDK para JavaScript, run_item_stream_event pode representar uma tool chamada, uma saída de tool, uma aprovação ou uma mudança de agente. O adaptador não precisa copiar cada campo. Ele escolhe um resumo público e mantém os detalhes completos no armazenamento de execução.

import { Agent, run } from "@openai/agents";

async function* streamPublicEvents(input: string, runId: string) {
  const agent = new Agent({
    name: "Support agent",
    instructions: "Use the available tools and report a safe final answer.",
  });

  const stream = await run(agent, input, { stream: true });
  let sequence = 0;

  yield event(runId, ++sequence, "run.started", "running", {});

  for await (const item of stream) {
    const publicEvent = toPublicEvent(item, runId, ++sequence);
    if (publicEvent) yield publicEvent;
  }

  await stream.completed;
  yield event(runId, ++sequence, "run.completed", "completed", {});
}

Esse trecho omite persistência, aprovação, erro e a implementação de toPublicEvent de propósito. Em um servidor real, grave o evento antes de publicá-lo, trate stream.completed como a fronteira de conclusão e emita run.failed em uma saída controlada. A documentação do OpenAI Agents SDK, "Running Agents" descreve o resultado transmitido e a necessidade de aguardar o ciclo de vida do run.

Uma rota SSE pode serializar o mesmo envelope:

function toSse(event: AgentEvent): string {
  return [
    `id: ${event.runId}:${event.sequence}`,
    `event: ${event.type}`,
    `data: ${JSON.stringify(event)}`,
    "",
    "",
  ].join("\n");
}

O navegador usa addEventListener para cada tipo que interessa. O painel pode mostrar tool.called como "Consultando pedidos" e tool.completed como "Pedidos consultados", enquanto o texto final continua em outra área. Essa separação impede que uma atualização de linguagem altere o estado operacional.

Como reconectar sem perder eventos?

Reconexão exige duas coisas: um ID monotônico e um lugar de onde o servidor possa repetir eventos. O campo id do SSE ajuda o navegador a informar qual evento recebeu por último. O servidor ainda precisa decidir se guarda os eventos em um log, em uma tabela de execução ou apenas em uma janela curta de memória.

Quando a conexão volta, compare o último ID com o histórico do runId. Reenvie os eventos posteriores na mesma ordem. Se o evento já expirou, retorne o snapshot atual e um marcador que diga que houve uma lacuna. A UI deve então redesenhar a linha do tempo a partir do snapshot, sem fingir que recebeu cada passo.

Não use a reconexão como mecanismo de replay de efeitos externos. Reenviar tool.completed para a tela é seguro quando o consumidor deduplica por ID. Executar a tool outra vez é outra operação. O guia da AWS sobre estado e recuperação por checkpoint relaciona checkpoints com idempotência, gravações condicionais e ciclo de vida do estado.

O estado durável também precisa de escopo. Um runId não deve expor eventos de outro usuário, projeto ou ambiente. A documentação da Microsoft sobre estado de agentes de longa duração separa metadados pequenos, como watermarks e chaves de idempotência, do estado volumoso de checkpoints. A mesma separação reduz o que o endpoint precisa carregar para responder a uma reconexão.

Uma regra que uso ao desenhar esse contrato é pedir que cada evento responda a uma pergunta do painel: "o que mudou?", "qual execução mudou?" e "posso confiar que isso terminou?". Se o evento não responde às três, ele costuma ser log interno, não mensagem de UI.

O que precisa ser testado antes de chamar o stream de pronto?

Teste a sequência de eventos como comportamento público. O teste não precisa validar a ordem de cada delta de texto, mas deve provar que uma tool não aparece como concluída antes de ser chamada, que uma aprovação pausa o run e que o terminal só aparece depois da conclusão real do runtime. O guia de testes do OpenAI Agents SDK oferece um modelo para testar runs transmitidos e eventos controlados.

Uma suíte mínima deve cobrir:

  1. Um run normal emite run.started e termina em run.completed.
  2. Uma tool gera chamada e saída, sem vazar o argumento privado.
  3. Uma aprovação gera approval.required e só continua depois da decisão.
  4. Um erro gera run.failed com motivo seguro e estado consultável.
  5. Uma reconexão recebe apenas eventos posteriores ao último sequence.
  6. Um evento repetido não duplica a linha do tempo na interface.
  7. Um cancelamento não é renderizado como sucesso.
  8. Um evento desconhecido é ignorado ou enviado para uma versão compatível.

Para validar os dados que cada tool devolve, veja a validação de tool calls em TypeScript. A fronteira do adaptador e a fronteira do stream devem rejeitar dados inválidos antes de apresentá-los como progresso.

Também teste o transporte. Feche a conexão durante uma tool, abra duas abas para o mesmo run e remova a permissão de um usuário no meio da execução. Verifique que o servidor encerra a assinatura correta, que cada cliente recebe apenas seu escopo e que a execução continua ou para segundo uma política explícita.

O teste de UI deve conferir estados visíveis, não só a existência do texto. Uma asserção como "a tela mostra concluído" precisa depender do evento terminal e de um resultado salvo. Se a interface vira verde quando recebe o último token, o teste está validando a ilusão que o contrato deveria evitar.

Erros comuns ao transmitir progresso de agentes

O erro mais comum é publicar o stream bruto do provedor. Isso acopla a interface ao SDK, expõe detalhes que deveriam ficar privados e torna a troca de modelo uma mudança de frontend. Normalize no backend e mantenha o payload público menor que o evento interno.

Outro erro é usar run.completed como sinônimo de "o navegador recebeu a mensagem". O runtime pode ter terminado enquanto o cliente estava desconectado. O servidor precisa persistir o estado e oferecer consulta ou replay. A conexão serve para reduzir a espera visual; ela não confirma entrega permanente.

Também é fácil esquecer o caminho de aprovação. O Agents SDK expõe interrupções e um estado que pode ser retomado depois da decisão. A interface precisa mostrar que o run está esperando, não apresentar silêncio ou um spinner sem explicação. Para decidir quando pedir a aprovação, use a análise de gates humanos para agentes de IA, que trata da política de risco separadamente do transporte.

Quando o volume cresce, eventos de progresso podem virar uma segunda fonte de pressão. Limite tamanho de payload, descarte deltas de texto antigos quando o produto não precisa deles e mantenha heartbeat separado de mudanças de estado. Se o cliente ficar lento, prefira um snapshot novo a uma fila infinita de mensagens que ninguém consegue renderizar.

Conclusão: transmita fatos de execução

Uma interface de agente fica mais confiável quando o backend transmite fatos que o runtime confirmou. Texto parcial pode melhorar a sensação de resposta, mas tool.completed, approval.required, run.failed e run.completed explicam o que a execução está fazendo.

Comece com um envelope pequeno: runId, sequência, tipo, estado, horário e payload seguro. Use SSE quando o fluxo for servidor para navegador, WebSockets quando houver controle nos dois sentidos e polling quando uma consulta durável for suficiente. Depois teste reconexão, aprovação, cancelamento, redação de dados sensíveis e terminalidade com o mesmo rigor aplicado ao agente.

Para fluxos que atravessam sessões de desenvolvimento e exigem continuidade de contexto, eu uso o RemoteCode como uma ferramenta minha. Essa menção é sobre continuidade de trabalho, não uma medição de desempenho do stream.

Perguntas frequentes

SSE é melhor que WebSockets para um agente de IA?

Não existe uma escolha universal. SSE costuma ser suficiente quando o servidor envia eventos para o navegador e o cliente usa requisições separadas para agir. WebSockets fazem sentido quando cancelamento, aprovação ou novas entradas precisam viajar pelo mesmo canal. Em ambos, use IDs, autorização, estado durável e uma regra de reconexão.

Devo transmitir o raciocínio do agente para mostrar progresso?

Não. Mostre estados e resumos seguros, como uma tool iniciada, uma aprovação pendente ou uma etapa concluída. Raciocínio privado, prompt completo, credenciais e argumentos sensíveis não pertencem ao contrato público. O usuário precisa saber o que aconteceu e o que pode fazer, não receber cada detalhe interno do modelo.

O último token significa que o agente terminou?

Não necessariamente. O runtime pode ainda estar persistindo sessão, registrando aprovação ou executando callbacks depois do último delta visível. Aguarde a fronteira de conclusão documentada pelo runtime e emita o evento terminal a partir desse estado. Se a conexão caiu, consulte o estado salvo antes de mostrar sucesso.

Fontes consultadas