Saltar para o conteúdo principal
OpenAI

4 de maio de 2026

Engenharia

Como a OpenAI disponibiliza IA de voz de baixa latência à escala

Por Yi Zhang e William McDonald, membros da equipa técnica

A IA de voz só parece natural se a conversa avançar ao ritmo da fala. Quando a rede atrapalha, as pessoas notam-no de imediato através de pausas estranhas, interrupções truncadas ou barge-in atrasado. Isto é importante para o ChatGPT Modo Voz, para programadores que criam com a Realtime API, para agentes que trabalham em fluxos interativos e para modelos que precisam de processar áudio enquanto o utilizador ainda está a falar.

À escala da OpenAI, isso traduz-se em três requisitos concretos:

  • Alcance global para mais de 900 milhões de utilizadores ativos por semana
  • Configuração rápida da ligação para que um utilizador possa começar a falar assim que uma sessão se inicia
  • Tempo de ida e volta do suporte multimédia baixo e estável, com pouca variação de atraso e perda de pacotes, para que a alternância de fala seja fluida

A equipa da OpenAI responsável pelas interações de IA em tempo real reestruturou recentemente a nossa stack WebRTC para resolver três limitações que começaram a colidir à escala: a terminação de media com uma porta por sessão não se ajusta bem à infraestrutura da OpenAI, as sessões com estado de ICE (Interactive Connectivity Establishment) e DTLS (Datagram Transport Layer Security) precisam de propriedade estável, e o encaminhamento global tem de manter baixa a latência no primeiro salto. Neste artigo, explicamos a arquitetura dividida de relay mais transceiver que criámos para preservar o comportamento WebRTC padrão para os clientes, alterando ao mesmo tempo a forma como os pacotes são encaminhados dentro da infraestrutura da OpenAI.

O WebRTC permite-nos criar produtos de IA em tempo real

O WebRTC é uma norma aberta para enviar áudio, vídeo e dados de baixa latência entre navegadores, aplicações móveis e servidores. É muitas vezes associado a chamadas peer-to-peer, mas também é uma base prática para sistemas em tempo real cliente-servidor porque normaliza as partes difíceis dos media interativos: ICE para estabelecimento de conectividade e travessia de NAT (Network Address Translation), DTLS e SRTP (Secure Real-time Transport Protocol) para transporte cifrado, negociação de codecs para comprimir e descodificar áudio, RTCP (Real-time Transport Control Protocol) para controlo de qualidade, e funcionalidades do lado do cliente como cancelamento de eco e buffer de jitter.

Essa normalização é importante para produtos de IA. Sem o WebRTC, cada cliente precisaria de uma resposta diferente para estabelecer conectividade através de NATs, cifrar media, negociar codecs (os codificadores-descodificadores selecionados para transmissão e descompressão) e adaptar-se a condições de rede em mudança. Com o WebRTC, podemos basear-nos numa stack de protocolos já implementada em navegadores e plataformas móveis, concentrando o nosso trabalho na infraestrutura que liga os media em tempo real aos modelos.

Também nos apoiamos no próprio ecossistema WebRTC, incluindo implementações open source maduras e o trabalho de normalização que mantém navegadores, aplicações móveis e servidores interoperáveis. O trabalho fundamental de Justin Uberti (um dos arquitetos originais do WebRTC) e Sean DuBois (criador e maintainer do Pion) tornou possível que equipas como a nossa construíssem sobre uma infraestrutura de media testada em batalha, em vez de reinventarem comportamentos de transporte, cifragem e controlo de congestionamento de baixo nível. Temos a sorte de tanto o Justin como o Sean serem agora nossos colegas na OpenAI, ajudando a orientar a forma como aproximamos o WebRTC e a IA em tempo real.

Para a IA, a propriedade mais importante é que o áudio chega como um fluxo contínuo. Um agente falado pode começar a transcrever, raciocinar, chamar ferramentas ou gerar fala enquanto o utilizador ainda está a falar, em vez de esperar por um carregamento completo. Essa é a diferença entre um sistema que parece conversacional e um que parece push-to-talk.

Escolher uma arquitetura de media

Depois de escolhermos o WebRTC, a questão seguinte foi onde o terminar (onde aceitaríamos e deteríamos a ligação WebRTC — por exemplo, na edge) e como ligar essas sessões ao backend de inferência. A terminação é importante porque determina como gerimos o estado da sessão em tempo real, o transporte de media, o encaminhamento, a latência e o isolamento de falhas.

Opção 1: A abordagem SFU inclui a IA como participante WebRTC

Uma SFU, ou unidade de encaminhamento seletivo, é um servidor de media que recebe um fluxo WebRTC de cada participante e encaminha seletivamente fluxos para os restantes. Neste modelo, a SFU termina uma ligação WebRTC separada para cada participante, e a IA junta-se como mais um participante na sessão. Isto pode adequar-se bem a produtos inerentemente multiparticipantes, como chamadas de grupo, salas de aula ou reuniões colaborativas. Mantém codecs de áudio, mensagens RTCP, canais de dados, gravação e política por fluxo num só local.1

Mesmo em produtos cliente-IA, uma SFU é muitas vezes o ponto de partida por defeito porque permite às equipas reutilizar um sistema comprovado para sinalização, encaminhamento de media, gravação, observabilidade e extensões futuras, como transferência para um humano ou adição de mais participantes.

Opção 2: A abordagem com transceiver termina o WebRTC na edge e converte-o num protocolo de backend

A nossa carga de trabalho é diferente. A maioria das sessões é 1:1 — um utilizador a falar com um modelo, ou uma aplicação a falar com um agente em tempo real — com sensibilidade à latência em cada turno. Para este perfil de tráfego, escolhemos um modelo de transceiver: um serviço WebRTC na edge termina a ligação do cliente e depois converte media e eventos em protocolos internos mais simples para inferência do modelo, transcrição, geração de fala, utilização de ferramentas e orquestração.

Neste desenho, o transceiver é o único serviço que detém o estado da sessão WebRTC, incluindo verificações de conectividade ICE, o handshake DTLS, chaves de cifragem SRTP e o ciclo de vida da sessão. “Terminação”, aqui, significa que o transceiver é o endpoint que conclui esses handshakes e cifra ou decifra os media. Manter esse estado num único local tornou a propriedade da sessão mais fácil de entender e permitiu que os serviços de backend escalassem como serviços normais, em vez de atuarem eles próprios como peers WebRTC.

O problema central de deployment: WebRTC encontra Kubernetes

Depois de escolhermos o modelo de transceiver, a nossa primeira implementação foi um único serviço Go baseado em Pion que tratava tanto da sinalização como da terminação de media. É ele que suporta o ChatGPT Modo Voz, o endpoint WebRTC da Realtime API e vários projetos de investigação.

Do ponto de vista operacional, o serviço de transceiver faz dois trabalhos:

  • Sinalização: negociação SDP, seleção de codecs, credenciais ICE e configuração da sessão
  • Media: terminação de ligações WebRTC downstream e manutenção de ligações upstream a serviços de backend para inferência e orquestração

Queríamos que o serviço funcionasse como o resto da nossa infraestrutura: em Kubernetes, onde as cargas de trabalho podem aumentar e diminuir e mover-se entre hosts à medida que a procura muda. Mas o modelo WebRTC convencional de uma porta por sessão ajusta-se mal a esse ambiente, porque depende de grandes intervalos de portas UDP públicas que são difíceis de expor, proteger e preservar à medida que pods são adicionados, removidos ou reescalonados.2

Esgotamento de portas

O primeiro problema era o próprio modelo de uma porta por sessão. Com elevada simultaneidade, isso significa expor e gerir intervalos muito grandes de portas UDP.

  • Os balanceadores de carga na cloud e os serviços Kubernetes não foram concebidos para dezenas de milhares de portas UDP públicas por serviço. Cada intervalo adicional acrescenta complexidade operacional à configuração do balanceador, verificação de estado, política de firewall e segurança de rollout.3
  • Grandes intervalos de portas UDP são difíceis de proteger porque ampliam a superfície acessível externamente e tornam a política de rede mais difícil de auditar.
  • Também não se ajustam bem ao escalonamento automático. Os pods são constantemente adicionados, removidos e reescalonados no Kubernetes. Exigir que cada pod reserve e anuncie um grande intervalo estável de portas torna essa elasticidade frágil.4

É por isso que muitos sistemas WebRTC caminham para uma única porta UDP por servidor, com desmultiplexação ao nível da aplicação por detrás dessa porta.5

Aderência do estado

Os desenhos de uma única porta por servidor resolvem a contagem de portas, mas introduzem um segundo problema: preservar a propriedade de cada sessão numa frota.

ICE e DTLS são protocolos com estado. O processo que criou uma sessão precisa de continuar a receber os pacotes dessa sessão para poder validar verificações de conectividade, concluir o handshake DTLS, decifrar SRTP e processar alterações posteriores da sessão, como reinícios de ICE. Se os pacotes da mesma sessão chegarem a um processo diferente, a configuração pode falhar ou os media podem deixar de funcionar.

Isso deu-nos um objetivo específico: expor uma superfície UDP pública pequena e fixa à internet pública, continuando ao mesmo tempo a encaminhar cada pacote para o transceiver que detém a sessão WebRTC correspondente.

Comparação de arquiteturas de media WebRTC

Avaliámos várias formas de lá chegar, incluindo TURN (Traversal Using Relays around NAT), em que um relay na edge termina alocações do cliente e encaminha tráfego em seu nome.2

Abordagem

Vantagens

Desvantagens

IP:porta único por sessão (também conhecido como UDP direto nativo)

Percurso direto de media entre cliente e servidor

Sem camada de encaminhamento no percurso de dados

Requer uma porta UDP pública por sessão

Grandes intervalos de portas são difíceis de expor e proteger

Má adequação ao Kubernetes e a balanceadores de carga na cloud

IP:porta único por servidor

Pegada UDP pública muito menor do que a exposição por sessão

Um socket partilhado por servidor pode desmultiplexar muitas sessões

Funciona bem num único host, mas não por si só numa frota partilhada com balanceamento de carga

A desmultiplexação de sessões num único host só ajuda depois de um pacote chegar a esse host; numa frota com balanceamento de carga, o primeiro pacote pode continuar a chegar à instância errada, por isso continua a ser necessária uma forma determinística de encaminhar cada sessão para o processo que a detém


Relay TURN (com terminação de protocolo)

Os clientes só precisam de alcançar o endereço e a porta do relay TURN

Pode centralizar a política na edge

As alocações TURN acrescentam idas e voltas à configuração

Continuar a mover ou recuperar alocações entre servidores TURN é difícil

Encaminhador sem estado + terminador com estado (relay + transceiver da OpenAI)

Pegada UDP pública pequena

O transceiver continua a deter a sessão WebRTC completa

Acrescenta um salto de encaminhamento antes de os media chegarem ao transceiver responsável

Requer coordenação personalizada entre relay e transceiver

Visão geral da arquitetura: relay + transceiver

A arquitetura que lançámos separa o encaminhamento de pacotes da terminação de protocolos. A sinalização continua a chegar ao transceiver para configuração da sessão, enquanto os media entram primeiro pelo relay. O relay é uma camada leve de encaminhamento UDP com uma pequena presença pública, e o transceiver é o endpoint WebRTC com estado que está por trás dela.

O relay encaminha pacotes sem estado para o transceiver

O relay não decifra media, não executa máquinas de estado ICE, nem participa na negociação de codecs. Lê metadados de pacote suficientes para escolher um destino e depois encaminha o pacote para o transceiver que detém a sessão. O transceiver continua a ver um fluxo WebRTC normal e continua a deter todo o estado do protocolo. Da perspetiva do cliente, nada muda na sessão WebRTC.

Encaminhamento com base em credenciais ICE

O encaminhamento do primeiro pacote é o passo fundamental nesta configuração. Um relay tem de encaminhar o primeiro pacote de um cliente antes de existir qualquer sessão no próprio percurso do pacote, em vez de fazer uma pausa para consultar um serviço externo.

Todas as sessões WebRTC já incluem um ponto de ligação de encaminhamento nativo do protocolo: o fragmento de nome de utilizador ICE, ou ufrag, um identificador curto trocado durante a configuração da sessão e repetido nas verificações de conectividade STUN. Geramos o ufrag do lado do servidor de modo a que contenha informação de encaminhamento suficiente para o relay inferir o cluster de destino e o transceiver responsável.

O diagrama de sequência mostra como a ligação é estabelecida

Durante a sinalização, o transceiver aloca o estado da sessão e devolve um VIP de relay partilhado e uma porta UDP na resposta SDP. Um VIP é um endereço IP virtual à frente da frota de relays; combinado com a porta, dá ao cliente um único destino estável, como `203.0.113.10:3478`, mesmo que existam muitas instâncias de relay por trás dele. O primeiro pacote do cliente no percurso de media é normalmente um pedido de binding STUN (Session Traversal Utilities for NAT), que o ICE usa para verificar que os pacotes conseguem chegar ao endereço anunciado.

O relay analisa apenas o suficiente desse primeiro pacote STUN para ler o ufrag do servidor, descodificar a indicação de encaminhamento e encaminhar o pacote para o transceiver responsável. Cada transceiver escuta num socket UDP partilhado, ou seja, um único endpoint do sistema operativo associado a um IP:porta interno, e não um socket por sessão. Depois de o relay criar uma sessão do IP:porta de origem do cliente para esse destino de transceiver, os pacotes DTLS, RTP e RTCP subsequentes fluem dentro da sessão sem voltar a descodificar o ufrag.

A sessão do relay é deliberadamente mínima, consistindo apenas numa sessão em memória para informar o encaminhamento de pacotes, juntamente com os contadores necessários para monitorização e temporizadores para expiração e limpeza da sessão. Esta escolha de desenho mantém o encaminhamento de pacotes diretamente no percurso do pacote. Se um relay reiniciar e perder a sessão, o pacote STUN seguinte reconstrói a sessão a partir da indicação de encaminhamento no ufrag. Para a tornar ainda mais fiável, é utilizada uma cache Redis para guardar o mapeamento de <IP + porta do cliente, IP + porta do transceiver> depois de a rota ser estabelecida, para que possa ser recuperado muito mais cedo, antes da chegada do pacote STUN seguinte.

Global Relay e sinalização geo-orientada

Assim que reduzimos a superfície UDP pública a um pequeno número de endereços e portas estáveis, pudemos implementar o mesmo padrão de relay a nível global. O Global Relay é a nossa frota de pontos de entrada de relay distribuídos geograficamente que implementam todos o mesmo comportamento de encaminhamento de pacotes.

Uma ampla presença geográfica de entrada encurta o primeiro salto do cliente para a OpenAI porque um pacote pode entrar na nossa rede num relay próximo do utilizador, tanto em termos geográficos como de topologia de rede, em vez de atravessar primeiro a internet pública até uma região distante. Em termos práticos, isso significa menor latência, menos jitter e menos rajadas de perda evitáveis antes de o tráfego chegar à nossa backbone.6

A camada Global Relay recebe pacotes do cliente e encaminha-os para o cluster de transceivers

Usamos geo e steering por proximidade da Cloudflare para a sinalização, para que o pedido HTTP ou WebSocket inicial chegue a um cluster de transceivers próximo. O contexto do pedido determina a localização da sessão e qual o ponto de entrada do Global Relay anunciado ao cliente. A resposta SDP fornece o endereço do Global Relay, enquanto o ufrag contém informação suficiente para o Global Relay encaminhar os media para o cluster designado e para o relay encaminhar para o transceiver de destino.

Em conjunto, a sinalização geo-orientada e o Global Relay colocam tanto a configuração como os media num percurso de entrada próximo, mantendo a sessão ancorada a um único transceiver. Isso reduz o tempo de ida e volta da sinalização e da primeira verificação de conectividade ICE, encurtando diretamente o tempo que um utilizador espera antes de poder começar a falar.

Implementação e desempenho do relay

Escrevemos o serviço de relay em Go e mantivemos a implementação deliberadamente restrita. Em Linux, a stack de rede do kernel recebe pacotes UDP da interface de rede da máquina e entrega-os a um socket, o endpoint do sistema operativo que um processo lê após associar um IP:porta. O relay corre em userspace, pelo que um processo Go normal lê cabeçalhos de pacotes desse socket, atualiza uma pequena quantidade de estado do fluxo e encaminha pacotes sem terminar WebRTC. Não precisámos de qualquer framework de bypass do kernel, que permitiria a um processo em userspace sondar diretamente filas de rede para taxas de pacotes mais elevadas, mas também acrescentaria complexidade operacional.

Principais escolhas de desenho:

  • Sem terminação de protocolo: O relay analisa apenas cabeçalhos STUN/ufrag; usa estado em cache para DTLS, RTP e RTCP subsequentes, mantendo os pacotes opacos.
  • Estado efémero: Mantém um pequeno mapa em memória, com timeout curto, do endereço do cliente para o destino do transceiver para estado de fluxo e observabilidade.
  • Escalabilidade horizontal: Várias instâncias de relay correm em paralelo por trás de um balanceador de carga. O estado não é estado WebRTC rígido, pelo que reinícios causam quedas mínimas de tráfego e recuperação rápida dos fluxos.

Medidas de eficiência:

  • SO_REUSEPORT é uma opção de socket Linux que permite a vários workers de relay na mesma máquina associar a mesma porta UDP. O kernel distribui então os pacotes recebidos por esses workers, evitando um estrangulamento num único ciclo de leitura.
  • runtime.LockOSThread fixa cada goroutine de leitura UDP a um thread específico do SO. Em conjunto com SO_REUSEPORT, isto tende a manter os pacotes do mesmo fluxo (o IP:porta de origem e destino mais o protocolo) no mesmo núcleo de CPU, melhorando a localidade de cache e reduzindo mudança de contexto.
  • Buffers pré-alocados e cópia mínima mantêm baixo o overhead de parsing e alocação, para evitar garbage collection em Go.

Esta implementação suportou o nosso tráfego global de media em tempo real com uma pegada de relay relativamente pequena, por isso mantivemos o desenho mais simples em vez de adotar uma via de bypass do kernel.

Resultados e aprendizagens

Esta arquitetura permite-nos executar media WebRTC em Kubernetes sem expor milhares de portas UDP. Isto é importante porque uma superfície UDP menor e fixa é mais fácil de proteger e balancear, e permite que a infraestrutura escale sem reservar grandes intervalos de portas públicas. Com melhor suporte de infraestrutura do Kubernetes e mais segurança devido à menor área de exposição, este desenho também preserva o comportamento WebRTC padrão para os clientes e confirma que um desenho sem SFU era a predefinição certa para a nossa carga de trabalho. A maioria das nossas sessões é ponto a ponto, sensível à latência e mais fácil de escalar quando os serviços de inferência não precisam de se comportar como peers WebRTC.

A lição mais ampla é que o melhor lugar para acrescentar complexidade é numa camada de encaminhamento fina, não em cada serviço de backend e não em comportamentos personalizados do cliente. Codificar metadados de encaminhamento num campo nativo do protocolo deu-nos encaminhamento determinístico do primeiro pacote, uma pequena pegada UDP pública e flexibilidade suficiente para colocar a entrada perto dos utilizadores em todo o mundo.

Algumas escolhas foram especialmente importantes:

  • Preservar a semântica do protocolo na edge. Os clientes continuam a falar WebRTC padrão, o que mantém intacta a interoperabilidade entre navegadores e dispositivos móveis.
  • Manter os estados de sessão complexos num só local. O transceiver detém ICE, DTLS, SRTP e o ciclo de vida da sessão; o relay apenas encaminha pacotes.
  • Encaminhar com base em informação já presente na configuração. O ufrag ICE deu-nos um ponto de ligação para encaminhamento do primeiro pacote sem acrescentar uma dependência de consulta no caminho crítico.
  • Otimizar para o caso comum antes de recorrer a bypass do kernel. Uma implementação Go restrita, com utilização cuidada de SO_REUSEPORT, fixação de threads e parsing de baixa alocação, foi suficiente para a nossa carga de trabalho.

A IA de voz em tempo real só funciona quando a infraestrutura faz a latência parecer invisível. Para nós, isso significou mudar a forma da nossa implementação WebRTC sem alterar o que os clientes esperam do próprio WebRTC.