Pular para o conteúdo principal
OpenAI

4 de maio de 2026

Engenharia

Como a OpenAI oferece IA de voz de baixa latência em escala

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

A IA de voz só parece natural se a conversa avançar na velocidade da fala. Quando a rede atrapalha, as pessoas percebem isso imediatamente como pausas estranhas, interrupções cortadas ou interrupção atrasada. Isso importa para o ChatGPT Modo Voz, para desenvolvedores que criam com a Realtime API, para agentes que trabalham em fluxos interativos e para modelos que precisam processar áudio enquanto a pessoa ainda está falando.

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

  • Alcance global para mais de 900 milhões de usuários ativos semanais
  • Configuração rápida da conexão para que a pessoa possa começar a falar assim que a sessão se iniciar
  • Tempo de ida e volta da mídia baixo e estável, com pouco jitter e perda de pacotes, para que a alternância de turnos seja fluida

A equipe da OpenAI responsável por interações de IA em tempo real recentemente reestruturou nossa stack de WebRTC para lidar com três restrições que começaram a colidir em escala: encerrar a mídia com uma porta por sessão não se adapta bem à infraestrutura da OpenAI, sessões com estado de ICE (Interactive Connectivity Establishment) e DTLS (Datagram Transport Layer Security) precisam de propriedade estável, e o roteamento global precisa manter baixa a latência do primeiro salto. Neste post, explicamos a arquitetura dividida de relay mais transceptor que criamos para preservar o comportamento padrão do WebRTC para os clientes, ao mesmo tempo em que mudamos como os pacotes são roteados dentro da infraestrutura da OpenAI.

O WebRTC nos permite criar produtos de IA em tempo real

O WebRTC é um padrão aberto para enviar áudio, vídeo e dados de baixa latência entre navegadores, apps móveis e servidores. Ele costuma ser associado a chamadas ponto a ponto, mas também é uma base prática para sistemas em tempo real cliente-servidor porque padroniza as partes difíceis da mídia interativa: ICE para estabelecimento de conectividade e travessia de NAT (Network Address Translation), DTLS e SRTP (Secure Real-time Transport Protocol) para transporte criptografado, negociação de codecs para comprimir e decodificar áudio, RTCP (Real-time Transport Control Protocol) para controle de qualidade e recursos no cliente, como cancelamento de eco e buffer de jitter.

Essa padronização importa para produtos de IA. Sem WebRTC, cada cliente precisaria de uma resposta diferente para estabelecer conectividade através de NATs, criptografar mídia, negociar codecs (os codificadores-decodificadores escolhidos para transmissão e descompressão) e se adaptar a condições de rede variáveis. Com WebRTC, podemos construir sobre uma stack de protocolos já implementada em navegadores e plataformas móveis, concentrando nosso próprio trabalho na infraestrutura que conecta a mídia em tempo real aos modelos.

Também desenvolvemos sobre o próprio ecossistema WebRTC, incluindo implementações open source maduras e o trabalho de padronização que mantém navegadores, apps móveis e servidores interoperáveis. O trabalho fundamental de Justin Uberti (um dos arquitetos originais do WebRTC) e Sean DuBois (criador e mantenedor do Pion) tornou possível que equipes como a nossa construíssem sobre uma infraestrutura de mídia testada em produção, em vez de reinventar comportamentos de transporte, criptografia e controle de congestionamento de baixo nível. Temos a sorte de que Justin e Sean agora também sejam colegas aqui na OpenAI, ajudando a orientar como aproximamos WebRTC e IA em tempo real.

Para IA, a propriedade mais importante é que o áudio chegue como um fluxo contínuo. Um agente falado pode começar a transcrever, raciocinar, chamar ferramentas ou gerar fala enquanto a pessoa ainda está falando, em vez de esperar por um upload completo. Essa é a diferença entre um sistema que parece conversacional e outro que parece do tipo aperte para falar.

Escolhendo uma arquitetura de mídia

Depois de escolher o WebRTC, a pergunta seguinte foi onde encerrá-lo (onde aceitaríamos e controlaríamos a conexão WebRTC — por exemplo, na edge) e como conectar essas sessões ao backend de inferência. O ponto de término importa porque determina como lidamos com estado de sessão em tempo real, transporte de mídia, roteamento, latência e isolamento de falhas.

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

Uma SFU, ou selective forwarding unit, é um servidor de mídia que recebe um fluxo WebRTC de cada participante e encaminha seletivamente fluxos para os demais. Nesse modelo, a SFU encerra uma conexão WebRTC separada para cada participante, e a IA entra como mais um participante na sessão. Isso pode ser adequado para produtos inerentemente multiparticipantes, como chamadas em grupo, salas de aula ou reuniões colaborativas. Mantém codecs de áudio, mensagens RTCP, canais de dados, gravação e políticas por fluxo em um só lugar.1

Mesmo em produtos cliente-IA, uma SFU costuma ser o ponto de partida padrão porque permite que as equipes reutilizem um sistema já comprovado para sinalização, roteamento de mídia, gravação, observabilidade e extensões futuras, como transferência para um humano ou inclusão de mais participantes.

Opção 2: a abordagem com transceptor encerra o WebRTC na edge e converte para um protocolo de backend

Nossa carga de trabalho é diferente. A maioria das sessões é 1:1 — um usuário falando com um modelo ou um aplicativo falando com um agente em tempo real — com sensibilidade à latência em cada turno. Para esse formato de tráfego, escolhemos um modelo de transceptor: um serviço de edge WebRTC encerra a conexão do cliente e depois converte mídia e eventos em protocolos internos mais simples para inferência de modelos, transcrição, geração de fala, uso de ferramentas e orquestração.

Nesse desenho, o transceptor é o único serviço que detém o estado da sessão WebRTC, incluindo verificações de conectividade ICE, o handshake DTLS, chaves de criptografia SRTP e o ciclo de vida da sessão. “Encerramento” aqui significa que o transceptor é o endpoint que conclui esses handshakes e criptografa ou descriptografa a mídia. Manter esse estado em um só lugar tornou a propriedade da sessão mais fácil de entender e permitiu que os serviços de backend escalassem como serviços comuns, em vez de atuarem eles mesmos como pares WebRTC.

O problema central de implantação: WebRTC encontra Kubernetes

Depois de escolher o modelo de transceptor, nossa primeira implementação foi um único serviço em Go, baseado em Pion, que lidava tanto com sinalização quanto com encerramento de mídia. Ele dá suporte ao ChatGPT Modo Voz, ao endpoint WebRTC da Realtime API e a vários projetos de pesquisa.

Do ponto de vista operacional, o serviço de transceptor realiza dois trabalhos:

  • Sinalização: negociação de SDP, seleção de codec, credenciais ICE e configuração da sessão
  • Mídia: encerramento de conexões WebRTC downstream e manutenção de conexões upstream com serviços de backend para inferência e orquestração

Queríamos que o serviço rodasse como o restante da nossa infraestrutura: em Kubernetes, onde as cargas de trabalho podem escalar para cima e para baixo e se mover entre hosts conforme a demanda muda. Mas o modelo convencional de WebRTC com uma porta por sessão se encaixa mal nesse ambiente, porque depende de grandes faixas públicas de portas UDP que são difíceis de expor, proteger e preservar à medida que pods são adicionados, removidos ou reprogramados.2

Esgotamento de portas

O primeiro problema era o próprio modelo de uma porta por sessão. Em alta concorrência, isso significa expor e gerenciar faixas muito grandes de portas UDP.

  • Balanceadores de carga em nuvem e serviços do Kubernetes não são projetados em torno de dezenas de milhares de portas UDP públicas por serviço. Cada faixa adicional aumenta a complexidade operacional na configuração do balanceador, verificação de integridade, política de firewall e segurança de rollout.3
  • Grandes faixas 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 combinam mal com autoscaling. Pods são constantemente adicionados, removidos e reprogramados no Kubernetes. Exigir que cada pod reserve e anuncie uma grande faixa 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 demultiplexação em nível de aplicativo por trás dessa porta.5

Afinidade de estado

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

ICE e DTLS são protocolos com estado. O processo que criou uma sessão precisa continuar recebendo os pacotes dessa sessão para poder validar verificações de conectividade, concluir o handshake DTLS, descriptografar SRTP e processar alterações posteriores da sessão, como reinicializações de ICE. Se os pacotes da mesma sessão chegarem a um processo diferente, a configuração pode falhar ou a mídia pode quebrar.

Isso nos deu um objetivo específico: expor uma superfície UDP pequena e fixa à internet pública e, ao mesmo tempo, rotear cada pacote ao transceptor que é dono da sessão WebRTC correspondente.

Comparação entre arquiteturas de mídia WebRTC

Avaliamos várias maneiras de chegar lá, incluindo TURN (Traversal Using Relays around NAT), em que um relay de edge encerra alocações do cliente e encaminha tráfego em nome delas.2

Abordagem

Vantagens

Desvantagens

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

Caminho direto de mídia entre cliente e servidor

Sem camada de encaminhamento no caminho dos dados

Requer uma porta UDP pública por sessão

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

Baixa compatibilidade com Kubernetes e balanceadores de carga em nuvem

IP:porta exclusivo por servidor

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

Um socket compartilhado por servidor pode demultiplexar muitas sessões

Funciona bem em um único host, mas não em uma frota compartilhada com balanceamento de carga por si só

A demultiplexação de sessão em um único host só ajuda depois que um pacote chega a esse host; em uma frota com balanceamento de carga, o primeiro pacote ainda pode cair na instância errada, então ainda é necessário um modo determinístico de direcionar cada sessão ao processo que a controla


Relay TURN (com encerramento de protocolo)

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

Pode centralizar políticas na edge

As alocações TURN adicionam idas e voltas na configuração

Mover ou recuperar alocações entre servidores TURN ainda é difícil

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

Pequena presença UDP pública

O transceptor continua controlando toda a sessão WebRTC

Adiciona um salto de encaminhamento antes de a mídia chegar ao transceptor proprietário

Exige coordenação personalizada entre relay e transceptor

Visão geral da arquitetura: relay + transceptor

A arquitetura que colocamos em produção separa o roteamento de pacotes do encerramento de protocolo. A sinalização ainda chega ao transceptor para a configuração da sessão, enquanto a mídia entra primeiro pelo relay. O relay é uma camada leve de encaminhamento UDP com pequena presença pública, e o transceptor é o endpoint WebRTC com estado por trás dela.

O relay encaminha pacotes sem estado para o transceptor

O relay não descriptografa a mídia, não executa máquinas de estado de ICE nem participa da negociação de codecs. Ele lê metadados de pacote suficientes para escolher um destino e então encaminha o pacote ao transceptor que é dono da sessão. O transceptor ainda vê um fluxo WebRTC normal e continua sendo dono de todo o estado de protocolo. Do ponto de vista do cliente, nada muda na sessão WebRTC.

Roteamento com base em credenciais ICE

O roteamento do primeiro pacote é a etapa-chave nessa configuração. Um relay precisa rotear o primeiro pacote de um cliente antes que qualquer sessão exista no próprio caminho do pacote, em vez de pausar para consultar um serviço externo de lookup.

Toda sessão WebRTC já carrega um gancho de roteamento nativo do protocolo: o fragmento de nome de usuário do 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 que ele contenha metadados de roteamento suficientes para que o relay deduza o cluster de destino e o transceptor proprietário.

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

Durante a sinalização, o transceptor aloca o estado da sessão e retorna um VIP de relay compartilhado e uma porta UDP na resposta SDP. Um VIP é um endereço IP virtual à frente da frota de relays; combinado com a porta, ele dá ao cliente um único destino estável, como `203.0.113.10:3478`, embora muitas instâncias de relay estejam por trás dele. O primeiro pacote no caminho de mídia do cliente geralmente é uma solicitação de binding STUN (Session Traversal Utilities for NAT), que o ICE usa para verificar se os pacotes conseguem alcançar o endereço anunciado.

O relay analisa apenas o suficiente desse primeiro pacote STUN para ler o ufrag do servidor, decodificar a dica de roteamento e encaminhar o pacote ao transceptor proprietário. Cada transceptor escuta em um socket UDP compartilhado, isto é, um endpoint do sistema operacional vinculado a um IP:porta interno, não um socket por sessão. Depois que o relay cria uma sessão do IP:porta de origem do cliente para o destino desse transceptor, os pacotes DTLS, RTP e RTCP subsequentes fluem dentro da sessão sem decodificar novamente o ufrag.

A sessão do relay é propositalmente mínima, consistindo apenas em uma sessão em memória para informar o encaminhamento de pacotes, junto com os contadores necessários para monitoramento e timers para expiração e limpeza de sessão. Essa escolha de desenho mantém o roteamento de pacotes diretamente no caminho do pacote. Se um relay reiniciar e perder a sessão, o próximo pacote STUN reconstrói a sessão a partir da dica de roteamento no ufrag. Para torná-lo ainda mais confiável, um cache Redis é usado para manter o mapeamento de <IP + porta do cliente, IP + porta do transceptor> assim que a rota é estabelecida, para que ele possa ser recuperado muito antes, antes da chegada do próximo pacote STUN.

Global Relay e sinalização geodirecionada

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

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

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

Usamos direcionamento geográfico e por proximidade da Cloudflare para sinalização, para que a solicitação HTTP ou WebSocket inicial chegue a um cluster de transceptores próximo. O contexto da solicitação dita a localização da sessão e qual 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ções suficientes para que o Global Relay encaminhe a mídia ao cluster designado e para que o relay encaminhe ao transceptor de destino.

Juntos, a sinalização geodirecionada e o Global Relay colocam tanto a configuração quanto a mídia em um caminho de entrada próximo, ao mesmo tempo em que mantêm a sessão ancorada a um único transceptor. Isso reduz o tempo de ida e volta da sinalização e da primeira verificação de conectividade ICE, o que encurta diretamente quanto tempo a pessoa espera antes que a fala possa começar.

Implementação e desempenho do relay

Escrevemos o serviço de relay em Go e mantivemos a implementação deliberadamente enxuta. No Linux, a stack de rede do kernel recebe pacotes UDP da interface de rede da máquina e os entrega a um socket, o endpoint do sistema operacional do qual um processo lê depois de fazer o bind a um IP:porta. O relay roda em userspace, então um processo Go comum lê cabeçalhos de pacotes desse socket, atualiza uma pequena quantidade de estado de fluxo e encaminha pacotes sem encerrar o WebRTC. Não precisamos de nenhum framework de bypass do kernel, que permitiria a um processo em userspace sondar diretamente filas de rede para taxas mais altas de pacotes, mas também acrescentaria complexidade operacional.

Principais escolhas de design:

  • Sem encerramento de protocolo: o relay analisa apenas cabeçalhos STUN/ufrag; usa estado em cache para os pacotes 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 transceptor, para estado de fluxo e observabilidade.
  • Escalabilidade horizontal: várias instâncias de relay rodam em paralelo atrás de um balanceador de carga. O estado não é um estado WebRTC rígido, então reinicializações causam quedas mínimas de tráfego e recuperação rápida do fluxo.

Medidas de eficiência:

  • SO_REUSEPORT é uma opção de socket do Linux que permite que vários workers de relay na mesma máquina façam bind na mesma porta UDP. O kernel então distribui os pacotes recebidos entre esses workers, o que evita um gargalo em um único loop de leitura.
  • runtime.LockOSThread fixa cada goroutine de leitura UDP a uma thread específica do SO. Combinado com SO_REUSEPORT, isso tende a manter 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 trocas de contexto.
  • Buffers pré-alocados e cópia mínima mantêm baixos os custos de parsing e alocação para evitar coleta de lixo no Go.

Essa implementação lidou com nosso tráfego global de mídia em tempo real com uma presença de relay relativamente pequena, então mantivemos o desenho mais simples em vez de seguir pelo caminho de bypass do kernel.

Resultados e aprendizados

Essa arquitetura nos permite executar mídia WebRTC no Kubernetes sem expor milhares de portas UDP. Isso importa porque uma superfície UDP menor e fixa é mais fácil de proteger e balancear, e permite que a infraestrutura escale sem reservar grandes faixas públicas de portas. Com melhor suporte de infraestrutura do Kubernetes e mais segurança devido à menor superfície, esse desenho também preserva o comportamento padrão do WebRTC para os clientes e confirma que um design sem SFU era o padrão certo para 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 se comportar como pares WebRTC.

A lição mais ampla é que o melhor lugar para adicionar complexidade é em uma camada fina de roteamento, não em cada serviço de backend e não em um comportamento personalizado do cliente. Codificar metadados de roteamento em um campo nativo do protocolo nos deu roteamento determinístico do primeiro pacote, uma pequena presença UDP pública e flexibilidade suficiente para posicionar a entrada perto de usuários no mundo todo.

Algumas escolhas foram especialmente importantes:

  • Preservar a semântica do protocolo na edge. Os clientes continuam falando WebRTC padrão, o que mantém intacta a interoperabilidade entre navegador e dispositivos móveis.
  • Manter os estados difíceis da sessão em um só lugar. O transceptor controla ICE, DTLS, SRTP e o ciclo de vida da sessão; o relay apenas encaminha pacotes.
  • Rotear com base em informações já presentes na configuração. O ufrag do ICE nos deu um gancho de roteamento do primeiro pacote sem adicionar uma dependência de lookup no caminho crítico.
  • Otimizar para o caso mais comum antes de recorrer a bypass do kernel. Uma implementação enxuta em Go, com uso cuidadoso de SO_REUSEPORT, fixação de threads e parsing com baixa alocação, foi suficiente para 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 o formato da nossa implantação de WebRTC sem mudar o que os clientes esperam do próprio WebRTC.