El texto empieza a aparecer en el navegador, pero el usuario todavía no sabe qué está pasando. El agente puede estar llamando una herramienta, esperando una aprobación, reintentando un paso o deteniéndose con un error. Si la interfaz solo recibe tokens, parece activa y sigue sin tener un estado confiable.

Para transmitir el progreso de un agente de IA, envía eventos de ejecución, no solo fragmentos de la respuesta. Cada evento debe identificar la ejecución, llevar una secuencia, declarar su tipo y contener únicamente datos que la interfaz pueda mostrar. La guía sobre ejecución durable de agentes de IA cubre la recuperación del trabajo; este artículo cubre la entrega del estado al navegador.

Este artículo usa SSE como punto de partida para un flujo de servidor a navegador y compara WebSockets con polling. El código de integración con el SDK es ilustrativo y no se ejecutó en este repositorio. Trátalo como un contrato que debes probar contra el runtime que elijas.

Diagrama de un agente de IA que envía eventos de inicio, tool, aprobación y finalización a una interfaz.

Respuesta corta

  • Transmite eventos normalizados de ejecución, etapa, tool, aprobación, fallo y finalización.
  • Usa SSE para actualizaciones unidireccionales en el navegador; usa WebSockets cuando el cliente debe controlar la ejecución en tiempo real.
  • Incluye runId, sequence, type y el estado terminal en cada evento.
  • Persiste el estado y una ventana de replay. La conexión es un canal de entrega, no la fuente de la verdad.

¿Por qué transmitir tokens no muestra el progreso real?

Los tokens describen texto parcial. No indican si el agente inició una tool, si la tool terminó, si alguien debe aprobar una acción o si el runtime todavía tiene trabajo interno después del último token. La documentación del OpenAI Agents SDK, "Streaming" separa eventos crudos del modelo, elementos de ejecución y cambios del agente. La interfaz debe conservar esa diferencia.

Una barra de progreso tampoco debe calcularse a partir del tamaño de la respuesta. Un agente puede producir texto mientras espera una herramienta. Puede llamar una segunda herramienta sin producir texto entre ambas. También puede terminar sin una respuesta útil. Los eventos deben representar lo que el runtime sabe, no lo que parece convincente en pantalla.

Una división práctica es mantener dos flujos. Uno lleva texto parcial para la experiencia de conversación. El otro lleva hechos de ejecución para el panel de actividad. Pueden compartir una conexión, pero sus tipos, permisos y reglas de retención son distintos. No expongas razonamiento privado ni argumentos sensibles solo porque el SDK los incluya en el stream.

El contrato más útil no finge que cada tarea tiene un porcentaje. Muestra el último hecho confirmado: "la búsqueda terminó", "la aprobación está pendiente" o "la ejecución falló". Una secuencia honesta de estados ayuda más que un porcentaje inventado para un trabajo cuyo tamaño el sistema no conoce.

¿Qué transporte conviene elegir para los eventos del agente?

SSE es un buen punto de partida cuando el navegador recibe eventos y el servidor controla el flujo. El navegador reconecta un EventSource después de una caída, y el formato permite nombrar el evento, enviar datos y asociar un ID. La referencia de MDN, "Using server-sent events" también documenta comentarios de keep-alive y el campo retry.

Usa WebSockets cuando la interfaz debe enviar comandos durante la ejecución, como cancelar, responder una aprobación o añadir una entrada sin abrir otra petición. El coste es mayor: ahora administras mensajes en ambos sentidos, autorización por conexión, cierre y reconexiones. Google Cloud, "Host AI agents on Cloud Run resources" enumera el streaming HTTP y WebSockets como opciones para interacciones con agentes.

El polling sigue siendo válido cuando la ejecución es durable y la interfaz solo necesita leer su estado. Simplifica proxies, reconexiones y escalado, pero puede retrasar las actualizaciones visibles y producir lecturas repetidas. Elige el transporte por el control que necesitas, no por el deseo de animar una barra.

Necesidad de la interfaz Elección inicial El contrato todavía necesita
Recibir eventos del servidor SSE IDs, replay, autorización y estado terminal
Recibir y enviar comandos en vivo WebSockets Mensajes bidireccionales, heartbeat y cierre explícito
Consultar un estado durable Polling Versión del estado, intervalo y respuesta idempotente

Para un despliegue en Cloud Run, la comparación entre Service, Jobs y worker pools explica la frontera de ejecución. El endpoint que transmite eventos no tiene que ser el proceso que hace todo el trabajo. Un servicio puede seguir eventos persistidos mientras un worker ejecuta la tarea.

¿Qué debe contener el sobre de un evento del agente?

Un sobre de evento debe ser pequeño, versionado y suficiente para renderizar un cambio de estado sin conocer los objetos internos del SDK. El runtime debe definir runId y sequence; el navegador puede descartar un evento repetido y pedir los que falten. La forma siguiente es una sugerencia de dominio, no un formato oficial del 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>;
};

El cliente necesita conocer una máquina de estados pequeña. run.started puede llevar a step.started, tool.called, approval.required, run.failed o run.completed. Una aprobación pendiente no es un fallo ni una finalización. Un timeout de red tampoco demuestra que la tool no se ejecutó. El backend debe consultar el estado de la ejecución antes de emitir una transición final. Si el flujo tiene muchas transiciones, la máquina de estados para agentes de IA ayuda a decidir qué estados son legítimos.

Los eventos públicos deben contener nombres de herramientas que el usuario pueda ver, resúmenes cortos e identificadores no sensibles. Elimina tokens, prompts completos, credenciales, argumentos privados y resultados grandes. La guía de observabilidad de agentes de código en CI detalla cómo separar una traza de auditoría restringida del resumen que llega al consumidor.

¿Cómo adaptar el stream del runtime para la interfaz?

El adaptador debe traducir eventos del SDK al contrato de la aplicación. En el OpenAI Agents SDK para JavaScript, run_item_stream_event puede representar una tool llamada, una salida de tool, una aprobación o un cambio de agente. El adaptador no necesita copiar cada campo. Elige un resumen público y conserva los detalles completos en el almacenamiento de ejecución.

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", {});
}

Este fragmento omite persistencia, aprobación, errores y la implementación de toPublicEvent a propósito. En un servidor real, guarda el evento antes de publicarlo, trata stream.completed como la frontera de finalización y emite run.failed desde una salida controlada. La documentación del OpenAI Agents SDK, "Running Agents" describe el resultado transmitido y la necesidad de esperar el ciclo de vida de la ejecución.

Una ruta SSE puede serializar el mismo sobre:

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

El navegador usa addEventListener para cada tipo que le interesa. El panel puede mostrar tool.called como "Consultando pedidos" y tool.completed como "Pedidos consultados", mientras el texto final continúa en otra zona. Esa separación evita que una actualización de lenguaje cambie el estado operativo.

¿Cómo reconectar sin perder eventos?

La reconexión exige dos cosas: un ID monotónico y un lugar desde el que el servidor pueda repetir eventos. El campo id de SSE ayuda al navegador a informar cuál fue el último evento recibido. El servidor aún debe decidir si guarda los eventos en un log, en una tabla de ejecución o solo en una ventana corta de memoria.

Cuando vuelve la conexión, compara el último ID con el historial de runId. Reenvía los eventos posteriores en el mismo orden. Si el evento ya expiró, devuelve el snapshot actual y un marcador que indique que hubo una brecha. La UI debe redibujar la línea de tiempo desde el snapshot, sin fingir que recibió cada paso.

No uses la reconexión como mecanismo de replay de efectos externos. Reenviar tool.completed a la pantalla es seguro cuando el consumidor deduplica por ID. Ejecutar la tool otra vez es otra operación. La guía de AWS sobre estado y recuperación mediante checkpoints relaciona checkpoints con idempotencia, escrituras condicionales y ciclo de vida del estado.

El estado durable también necesita un ámbito. Un runId no debe exponer eventos de otro usuario, proyecto o entorno. La documentación de Microsoft sobre el estado de agentes de larga duración separa metadatos pequeños, como watermarks y claves de idempotencia, del estado voluminoso de los checkpoints. La misma separación reduce lo que el endpoint debe cargar para responder a una reconexión.

Una regla que uso al diseñar este contrato es pedir que cada evento responda a una pregunta del panel: "¿qué cambió?", "¿qué ejecución cambió?" y "¿puedo confiar en que terminó?". Si no responde a las tres, suele ser un log interno, no un mensaje para la UI.

¿Qué hay que probar antes de dar por listo el stream?

Prueba la secuencia de eventos como comportamiento público. El test no necesita validar el orden de cada delta de texto, pero debe demostrar que una tool no aparece como completada antes de ser llamada, que una aprobación pausa la ejecución y que el estado terminal solo aparece después de la finalización real del runtime. La guía de pruebas del OpenAI Agents SDK ofrece un modelo para probar ejecuciones transmitidas y eventos controlados.

Una suite mínima debe cubrir:

  1. Una ejecución normal emite run.started y termina en run.completed.
  2. Una tool genera llamada y salida sin filtrar el argumento privado.
  3. Una aprobación genera approval.required y solo continúa después de la decisión.
  4. Un error genera run.failed con un motivo seguro y un estado consultable.
  5. Una reconexión recibe solo eventos posteriores al último sequence.
  6. Un evento repetido no duplica la línea de tiempo en la interfaz.
  7. Una cancelación no se muestra como éxito.

Para validar los datos que devuelve cada tool, consulta la validación de tool calls en TypeScript. La frontera del adaptador y la frontera del stream deben rechazar datos inválidos antes de presentarlos como progreso.

Los tests del adaptador deben incluir una conexión que cae después de publicar un evento, un replay con el mismo ID, un evento desconocido y un snapshot que ya está en estado terminal. También comprueba que los permisos se evalúan por runId y que el texto parcial no se confunde con la confirmación de una acción.

¿Qué errores hacen que la interfaz mienta?

El primer error es emitir "terminado" cuando acaba el texto. El runtime aún puede estar procesando una tool o esperando una aprobación. El segundo es enviar objetos internos del SDK directamente al navegador. Eso acopla la UI a una biblioteca y aumenta el riesgo de filtrar prompts o argumentos.

El tercero es confiar en la conexión como almacenamiento. Una caída, un deploy o una pestaña cerrada deja huecos. Persiste el estado que permite consultar la verdad y conserva una ventana de eventos suficiente para reconstruir la vista.

El cuarto es tratar un reintento de red como permiso para repetir un efecto externo. Usa claves de idempotencia e inspecciona el estado antes de ejecutar de nuevo. Si el trabajo cruza una frontera de duración o necesita recuperación, separa el endpoint de la interfaz del worker que ejecuta la tarea.

Conclusión

Una interfaz confiable no necesita mostrar cada token ni inventar un porcentaje. Necesita recibir hechos pequeños y ordenados: inicio, etapa, tool, aprobación, fallo y finalización. Elige SSE, WebSockets o polling según el control que el cliente necesita. Después prueba la reconexión, las aprobaciones, la cancelación, la eliminación de datos sensibles y la terminalidad.

El patrón también mantiene una frontera sana entre runtime y producto. El SDK puede cambiar sus eventos internos; tu aplicación conserva un contrato público, un estado durable y una línea de tiempo que el usuario puede entender. Si la interfaz solo dice que algo "está pensando", todavía no está mostrando el progreso que importa.

Para los flujos que atraviesan sesiones de desarrollo y necesitan continuidad de contexto, uso RemoteCode como herramienta propia. Esta mención trata sobre continuidad del trabajo, no sobre una medición de rendimiento del stream.

Preguntas frecuentes

¿SSE o WebSockets para transmitir el progreso de un agente de IA?

SSE suele ser suficiente cuando el navegador solo recibe actualizaciones. Elige WebSockets cuando la interfaz también debe enviar comandos durante la ejecución, como una cancelación o una respuesta de aprobación. En ambos casos necesitas autorización, IDs, reconexión y un estado terminal que no dependa del último token.

¿Debo transmitir los pensamientos del agente a la interfaz?

No. Transmite eventos públicos y resúmenes que el usuario pueda comprender. Prompts completos, credenciales, argumentos privados y razonamiento interno no son necesarios para mostrar que una tool fue llamada o que una aprobación está pendiente. Mantén los detalles de auditoría en un almacenamiento con acceso restringido.

¿La interfaz puede reconstruir el progreso solo con tokens?

No de forma confiable. Los tokens no prueban que una tool terminó, que una aprobación fue concedida ni que el runtime llegó a un estado terminal. Un contrato de eventos con runId, secuencia y estado explícito permite separar texto parcial de hechos de ejecución.

Fuentes consultadas