Pasar al contenido principal
OpenAI

4 de mayo de 2026

Ingeniería

Cómo OpenAI ofrece IA de voz de baja latencia a gran escala

Por Yi Zhang y William McDonald, miembros del personal técnico

La IA de voz solo se siente natural si la conversación avanza a la velocidad del habla. Cuando la red se interpone, las personas lo notan de inmediato en forma de pausas incómodas, interrupciones entrecortadas o demoras al interrumpir. Eso importa para ChatGPT Voz, para los desarrolladores que crean con la API Realtime, para los agentes que trabajan en flujos interactivos y para los modelos que necesitan procesar audio mientras el usuario aún está hablando.

A la escala de OpenAI, eso se traduce en tres requisitos concretos:

  • Alcance global para más de 900 millones de usuarios activos semanales
  • Configuración rápida de la conexión para que un usuario pueda empezar a hablar en cuanto inicia una sesión
  • Tiempo de ida y vuelta de medios bajo y estable, con poco jitter y pérdida de paquetes, para que la alternancia de turnos se sienta fluida

El equipo de OpenAI responsable de las interacciones de IA en tiempo real rediseñó recientemente nuestra pila de WebRTC para abordar tres restricciones que empezaban a chocar a gran escala: la terminación de medios con un puerto por sesión no se adapta bien a la infraestructura de OpenAI, las sesiones con estado de ICE (Interactive Connectivity Establishment) y DTLS (Datagram Transport Layer Security) necesitan una propiedad estable, y el enrutamiento global tiene que mantener baja la latencia del primer salto. En esta publicación, explicamos la arquitectura dividida de relay más transceptor que construimos para preservar el comportamiento estándar de WebRTC para los clientes mientras cambiamos cómo se enrutan los paquetes dentro de la infraestructura de OpenAI.

WebRTC nos permite crear productos de IA en tiempo real

WebRTC es un estándar abierto para enviar audio, video y datos de baja latencia entre navegadores, apps móviles y servidores. Suele asociarse con llamadas peer-to-peer, pero también es una base práctica para sistemas en tiempo real de cliente a servidor porque estandariza las partes difíciles de los medios interactivos: ICE para el establecimiento de conectividad y el cruce de NAT (Network Address Translation), DTLS y SRTP (Secure Real-time Transport Protocol) para transporte cifrado, negociación de códecs para comprimir y decodificar audio, RTCP (Real-time Transport Control Protocol) para control de calidad y funciones del lado del cliente como cancelación de eco y búfer de jitter.

Esa estandarización importa para los productos de IA. Sin WebRTC, cada cliente necesitaría una respuesta distinta sobre cómo establecer conectividad a través de NAT, cifrar medios, negociar códecs (los codificadores-decodificadores seleccionados para la transmisión y descompresión) y adaptarse a condiciones de red cambiantes. Con WebRTC, podemos construir sobre una pila de protocolos ya implementada en navegadores y plataformas móviles, y centrar nuestro trabajo en la infraestructura que conecta los medios en tiempo real con los modelos.

También nos apoyamos en el propio ecosistema de WebRTC, incluidas implementaciones maduras de código abierto y el trabajo de estandarización que mantiene interoperables a navegadores, apps móviles y servidores. El trabajo fundamental de Justin Uberti (uno de los arquitectos originales de WebRTC) y Sean DuBois (creador y mantenedor de Pion) hizo posible que equipos como el nuestro construyeran sobre una infraestructura de medios probada en batalla en lugar de reinventar el transporte de bajo nivel, el cifrado y el control de congestión. Tenemos la fortuna de que tanto Justin como Sean ahora sean colegas aquí en OpenAI, ayudando a guiar cómo acercamos WebRTC y la IA en tiempo real.

Para la IA, la propiedad más importante es que el audio llegue como un flujo continuo. Un agente de voz puede empezar a transcribir, razonar, llamar herramientas o generar voz mientras el usuario aún está hablando, en lugar de esperar una carga completa. Esa es la diferencia entre un sistema que se siente conversacional y uno que se siente como pulsar para hablar.

Elegir una arquitectura de medios

Una vez que elegimos WebRTC, la siguiente pregunta fue dónde terminarlo (dónde aceptaríamos y asumiríamos la conexión WebRTC, por ejemplo, en el edge) y cómo conectar esas sesiones al backend de inferencia. La terminación importa porque determina cómo manejamos el estado de la sesión en tiempo real, el transporte de medios, el enrutamiento, la latencia y el aislamiento de fallas.

Opción 1: El enfoque SFU incluye la IA como participante de WebRTC

Un SFU, o selective forwarding unit, es un servidor de medios que recibe un flujo WebRTC de cada participante y reenvía selectivamente los flujos a los demás. En este modelo, el SFU termina una conexión WebRTC independiente para cada participante, y la IA se une como otro participante de la sesión. Esto puede encajar bien con productos que son inherentemente multiparte, como llamadas grupales, aulas o reuniones colaborativas. Mantiene en un solo lugar los códecs de audio, los mensajes RTCP, los canales de datos, la grabación y la política por flujo.1

Incluso en productos de cliente a IA, un SFU suele ser el punto de partida predeterminado porque permite a los equipos reutilizar un sistema ya probado para señalización, enrutamiento de medios, grabación, observabilidad y futuras extensiones como la transferencia a un humano o la incorporación de más participantes.

Opción 2: El enfoque de transceptor termina WebRTC en el edge y lo convierte a un protocolo backend

Nuestra carga de trabajo es diferente. La mayoría de las sesiones son 1:1: un usuario hablando con un modelo, o una aplicación hablando con un agente en tiempo real, con sensibilidad a la latencia en cada turno. Para esa forma de tráfico, elegimos un modelo de transceptor: un servicio edge de WebRTC termina la conexión del cliente y luego convierte los medios y eventos en protocolos internos más simples para inferencia de modelos, transcripción, generación de voz, uso de herramientas y orquestación.

En este diseño, el transceptor es el único servicio que posee el estado de la sesión WebRTC, incluidas las verificaciones de conectividad ICE, el handshake de DTLS, las claves de cifrado SRTP y el ciclo de vida de la sesión. “Terminación” aquí significa que el transceptor es el punto de acceso que completa esos handshakes y cifra o descifra los medios. Mantener ese estado en un solo lugar hizo que la propiedad de la sesión fuera más fácil de razonar y permitió que los servicios backend escalaran como servicios ordinarios en lugar de actuar ellos mismos como pares WebRTC.

El problema central de despliegue: WebRTC se encuentra con Kubernetes

Después de elegir el modelo de transceptor, nuestra primera implementación fue un único servicio en Go construido sobre Pion que manejaba tanto la señalización como la terminación de medios. Impulsa ChatGPT Voz, el punto de acceso WebRTC de la API Realtime y varios proyectos de investigación.

Desde el punto de vista operativo, el servicio de transceptor hace dos trabajos:

  • Señalización: negociación de SDP, selección de códecs, credenciales ICE y configuración de la sesión
  • Medios: terminar conexiones WebRTC downstream y mantener conexiones upstream con servicios backend para inferencia y orquestación

Queríamos que el servicio funcionara como el resto de nuestra infraestructura: en Kubernetes, donde las cargas de trabajo pueden escalar hacia arriba y hacia abajo, y moverse entre hosts según cambie la demanda. Pero el modelo convencional de WebRTC de un puerto por sesión encaja mal en ese entorno, porque depende de grandes rangos de puertos UDP públicos que son difíciles de exponer, proteger y preservar a medida que se agregan, eliminan o reprograman pods.2

Agotamiento de puertos

El primer problema era el propio modelo de un puerto por sesión. Con alta concurrencia, eso significa exponer y administrar rangos muy grandes de puertos UDP.

  • Los balanceadores de carga en la nube y los servicios de Kubernetes no están diseñados para decenas de miles de puertos UDP públicos por servicio. Cada rango adicional agrega complejidad operativa en la configuración del balanceador, las verificaciones de estado, la política de firewall y la seguridad de los despliegues.3
  • Los grandes rangos de puertos UDP son difíciles de proteger porque amplían la superficie accesible desde el exterior y hacen que la política de red sea más difícil de auditar.
  • También son poco adecuados para el escalado automático. En Kubernetes se agregan, eliminan y reprograman pods constantemente. Exigir que cada pod reserve y anuncie un gran rango estable de puertos hace que esa elasticidad sea frágil.4

Por eso muchos sistemas WebRTC avanzan hacia un solo puerto UDP por servidor, con demultiplexación a nivel de aplicación detrás de ese puerto.5

Persistencia del estado

Los diseños de un solo puerto por servidor resuelven la cantidad de puertos, pero introducen un segundo problema: preservar la propiedad de cada sesión en toda una flota.

ICE y DTLS son protocolos con estado. El proceso que creó una sesión necesita seguir recibiendo los paquetes de esa sesión para poder validar verificaciones de conectividad, completar el handshake de DTLS, descifrar SRTP y procesar cambios posteriores de la sesión, como reinicios de ICE. Si los paquetes de la misma sesión llegan a un proceso distinto, la configuración puede fallar o los medios pueden romperse.

Eso nos dio un objetivo específico: exponer una superficie UDP pública pequeña y fija a internet, pero seguir enrutando cada paquete al transceptor que posee la sesión WebRTC correspondiente.

Comparación de arquitecturas de medios WebRTC

Evaluamos varias formas de lograrlo, incluido TURN (Traversal Using Relays around NAT), donde un relay de edge termina asignaciones del cliente y reenvía tráfico en su nombre.2

Enfoque

Ventajas

Desventajas

IP:puerto único por sesión (también conocido como UDP directo nativo)

Ruta directa de medios entre cliente y servidor

Sin capa de reenvío en la ruta de datos

Requiere un puerto UDP público por sesión

Los grandes rangos de puertos son difíciles de exponer y proteger

Poco adecuado para Kubernetes y balanceadores de carga en la nube

IP:puerto único por servidor

Huella UDP pública mucho menor que la exposición por sesión

Un socket compartido por servidor puede demultiplexar muchas sesiones

Funciona limpiamente en un solo host, pero no por sí solo en una flota compartida con balanceo de carga

La demultiplexación de sesiones en un solo host solo ayuda después de que un paquete llega a ese host; en una flota con balanceo de carga, el primer paquete aún puede llegar a la instancia equivocada, así que de todos modos se necesita una forma determinista de dirigir cada sesión al proceso que la posee


Relay TURN (con terminación de protocolo)

Los clientes solo necesitan llegar a la dirección y puerto del relay TURN

Puede centralizar políticas en el edge

Las asignaciones TURN agregan viajes de ida y vuelta de configuración

Sigue siendo difícil mover o recuperar asignaciones entre servidores TURN

Reenviador sin estado + terminador con estado (relay + transceptor de OpenAI)

Huella UDP pública pequeña

El transceptor sigue siendo propietario de toda la sesión WebRTC

Agrega un salto de reenvío antes de que los medios lleguen al transceptor propietario

Requiere coordinación personalizada entre relay y transceptor

Resumen de la arquitectura: relay + transceptor

La arquitectura que implementamos separa el enrutamiento de paquetes de la terminación del protocolo. La señalización sigue llegando al transceptor para la configuración de la sesión, mientras que los medios entran primero por el relay. El relay es una capa ligera de reenvío UDP con una pequeña huella pública, y el transceptor es el punto de acceso WebRTC con estado que está detrás de él.

Relay reenvía paquetes al transceptor sin estado

El relay no descifra medios, no ejecuta máquinas de estado ICE ni participa en la negociación de códecs. Lee suficientes metadatos del paquete para elegir un destino y luego reenvía el paquete al transceptor que posee la sesión. El transceptor sigue viendo un flujo WebRTC normal y sigue poseyendo todo el estado del protocolo. Desde la perspectiva del cliente, nada de la sesión WebRTC cambia.

Enrutamiento sobre credenciales ICE

El enrutamiento del primer paquete es el paso clave en esta configuración. Un relay tiene que enrutar el primer paquete de un cliente antes de que exista una sesión en la propia ruta del paquete, en lugar de detenerse en un servicio externo de búsqueda.

Cada sesión WebRTC ya incluye un gancho de enrutamiento nativo del protocolo: el fragmento de nombre de usuario de ICE, o ufrag, un identificador corto intercambiado durante la configuración de la sesión y repetido en las verificaciones de conectividad STUN. Generamos el ufrag del lado del servidor para que contenga apenas los metadatos de enrutamiento suficientes para que el relay infiera el clúster de destino y el transceptor propietario.

El diagrama de secuencia muestra cómo se establece la conexión

Durante la señalización, el transceptor asigna el estado de la sesión y devuelve una VIP de relay compartida y un puerto UDP en la respuesta SDP. Una VIP es una dirección IP virtual al frente de la flota de relay; combinada con el puerto, le da al cliente un único destino estable, como `203.0.113.10:3478`, aunque detrás haya muchas instancias de relay. El primer paquete en la ruta de medios del cliente suele ser una solicitud de binding STUN (Session Traversal Utilities for NAT), que ICE usa para verificar que los paquetes pueden llegar a la dirección anunciada.

El relay analiza solo lo necesario de ese primer paquete STUN para leer el ufrag del servidor, decodificar la pista de enrutamiento y reenviar el paquete al transceptor propietario. Cada transceptor escucha en un socket UDP compartido, es decir, un único punto de acceso del sistema operativo vinculado a una IP:puerto interna, no un socket por sesión. Después de que el relay crea una sesión desde la IP:puerto de origen del cliente hacia ese destino de transceptor, los paquetes posteriores de DTLS, RTP y RTCP fluyen dentro de la sesión sin volver a decodificar el ufrag.

La sesión del relay es deliberadamente mínima y consiste solo en una sesión en memoria para informar el reenvío de paquetes, junto con los contadores necesarios para monitoreo y temporizadores para expiración y limpieza de la sesión. Esta decisión de diseño mantiene el enrutamiento de paquetes directamente en la ruta del paquete. Si un relay se reinicia y pierde la sesión, el siguiente paquete STUN reconstruye la sesión a partir de la pista de enrutamiento del ufrag. Para hacerlo todavía más confiable, se usa una caché de Redis para guardar el mapeo de <IP + puerto del cliente, IP + puerto del transceptor> una vez que la ruta queda establecida, de modo que pueda recuperarse mucho antes, antes de que llegue el siguiente paquete STUN.

Global Relay y señalización guiada geográficamente

Una vez que redujimos la superficie UDP pública a un pequeño número de direcciones y puertos estables, pudimos desplegar el mismo patrón de relay a nivel global. Global Relay es nuestra flota de puntos de ingreso de relay distribuidos geográficamente que implementan el mismo comportamiento de reenvío de paquetes.

Un ingreso geográfico amplio acorta el primer salto del cliente hacia OpenAI porque un paquete puede entrar en nuestra red por un relay cercano al usuario, tanto en geografía como en topología de red, en lugar de cruzar primero internet pública hacia una región lejana. En términos prácticos, eso significa menor latencia, menos jitter y menos ráfagas evitables de pérdida antes de que el tráfico llegue a nuestro backbone.6

La capa Global Relay recibe paquetes del cliente y los reenvía al clúster de transceptores

Usamos el direccionamiento geográfico y por proximidad de Cloudflare para la señalización, de modo que la solicitud inicial por HTTP o WebSocket llegue a un clúster de transceptores cercano. El contexto de la solicitud determina la ubicación de la sesión y qué punto de ingreso de Global Relay se anuncia al cliente. La respuesta SDP proporciona la dirección de Global Relay, mientras que el ufrag contiene información suficiente para que Global Relay enrute los medios al clúster designado y para que el relay los enrute al transceptor de destino.

En conjunto, la señalización guiada geográficamente y Global Relay colocan tanto la configuración como los medios en una ruta de entrada cercana, mientras mantienen la sesión anclada a un solo transceptor. Eso reduce el tiempo de ida y vuelta de la señalización y de la primera verificación de conectividad ICE, lo que acorta directamente el tiempo que un usuario espera antes de poder empezar a hablar.

Implementación y rendimiento del relay

Escribimos el servicio de relay en Go y mantuvimos la implementación deliberadamente acotada. En Linux, la pila de red del kernel recibe paquetes UDP desde la interfaz de red de la máquina y los entrega a un socket, el punto de acceso del sistema operativo que un proceso lee después de vincular una IP:puerto. Relay se ejecuta en espacio de usuario, así que un proceso Go normal lee encabezados de paquetes desde ese socket, actualiza una pequeña cantidad de estado de flujo y reenvía paquetes sin terminar WebRTC. No necesitábamos ningún framework de kernel bypass, que permitiría a un proceso en espacio de usuario sondear directamente las colas de red para tasas más altas de paquetes, pero también agregaría complejidad operativa.

Decisiones clave de diseño:

  • Sin terminación de protocolo: Relay analiza solo encabezados STUN/ufrag; usa estado en caché para DTLS, RTP y RTCP posteriores, manteniendo opacos los paquetes.
  • Estado efímero: Mantiene un mapa pequeño en memoria, con tiempos de espera cortos, de la dirección del cliente al destino del transceptor para estado de flujo y observabilidad.
  • Escalabilidad horizontal: Varias instancias de relay se ejecutan en paralelo detrás de un balanceador de carga. El estado no es estado duro de WebRTC, por lo que los reinicios provocan pérdidas mínimas de tráfico y una rápida recuperación del flujo.

Medidas de eficiencia:

  • SO_REUSEPORT es una opción de socket de Linux que permite que varios workers de relay en la misma máquina vinculen el mismo puerto UDP. Luego el kernel distribuye los paquetes entrantes entre esos workers, lo que evita un cuello de botella en un solo bucle de lectura.
  • runtime.LockOSThread fija cada goroutine que lee UDP a un hilo específico del SO. Combinado con SO_REUSEPORT, eso tiende a mantener los paquetes del mismo flujo (la IP:puerto de origen y destino más el protocolo) en el mismo núcleo de CPU, mejorando la localidad de caché y reduciendo los cambios de contexto.
  • Búferes preasignados y copias mínimas mantienen bajos los costos de análisis y asignación para evitar la recolección de basura en Go.

Esta implementación manejó nuestro tráfico global de medios en tiempo real con una huella de relay relativamente pequeña, así que mantuvimos el diseño más simple en lugar de adoptar una ruta de kernel bypass.

Resultados y aprendizajes

Esta arquitectura nos permite ejecutar medios WebRTC en Kubernetes sin exponer miles de puertos UDP. Eso importa porque una superficie UDP más pequeña y fija es más fácil de proteger y balancear, y permite que la infraestructura escale sin reservar grandes rangos de puertos públicos. Con mejor soporte de infraestructura de Kubernetes y más seguridad gracias a una superficie más pequeña, este diseño también preserva el comportamiento estándar de WebRTC para los clientes y confirma que un diseño sin SFU era la opción predeterminada correcta para nuestra carga de trabajo. La mayoría de nuestras sesiones son punto a punto, sensibles a la latencia y más fáciles de escalar cuando los servicios de inferencia no necesitan comportarse como pares WebRTC.

La lección más amplia es que el mejor lugar para agregar complejidad es una capa delgada de enrutamiento, no cada servicio backend ni un comportamiento personalizado en el cliente. Codificar metadatos de enrutamiento en un campo nativo del protocolo nos dio un enrutamiento determinista del primer paquete, una pequeña huella UDP pública y suficiente flexibilidad para ubicar el ingreso cerca de los usuarios de todo el mundo.

Algunas decisiones fueron especialmente importantes:

  • Preservar la semántica del protocolo en el edge. Los clientes siguen hablando WebRTC estándar, lo que mantiene intacta la interoperabilidad con navegadores y dispositivos móviles.
  • Mantener los estados críticos de la sesión en un solo lugar. El transceptor posee ICE, DTLS, SRTP y el ciclo de vida de la sesión; el relay solo reenvía paquetes.
  • Enrutar usando información ya presente en la configuración. El ufrag de ICE nos dio un gancho para enrutar el primer paquete sin agregar una dependencia de búsqueda en la ruta crítica.
  • Optimizar para el caso común antes de recurrir al kernel bypass. Una implementación acotada en Go con uso cuidadoso de SO_REUSEPORT, fijación de hilos y análisis de baja asignación fue suficiente para nuestra carga de trabajo.

La IA de voz en tiempo real solo funciona cuando la infraestructura hace que la latencia se sienta invisible. Para nosotros, eso significó cambiar la forma de nuestro despliegue de WebRTC sin cambiar lo que los clientes esperan del propio WebRTC.