Sari la conținutul principal
OpenAI

4 mai 2026

Inginerie

Cum oferă OpenAI AI vocală cu latență redusă la scară largă

De Yi Zhang și William McDonald, membri ai personalului tehnic

AI-ul vocal pare natural doar dacă conversația se desfășoară cu viteza vorbirii. Când rețeaua stă în cale, oamenii observă imediat asta sub forma unor pauze stânjenitoare, întreruperi tăiate sau intervenții întârziate. Acest lucru contează pentru ChatGPT Voice, pentru dezvoltatorii care construiesc cu Realtime API, pentru agenți care lucrează în fluxuri de lucru interactive și pentru modele care trebuie să proceseze audio cât timp utilizatorul încă vorbește.

La scara OpenAI, asta se traduce în trei cerințe concrete:

  • Acoperire globală pentru peste 900 de milioane de utilizatori activi săptămânal
  • Inițializare rapidă a conexiunii, astfel încât un utilizator să poată începe să vorbească imediat ce începe o sesiune
  • Timp redus și stabil de parcurs dus-întors pentru media, cu variație a latenței și pierderi de pachete reduse, astfel încât schimbul de replici să fie fluent

Echipa de la OpenAI responsabilă de interacțiunile AI în timp real a reproiectat recent arhitectura noastră WebRTC pentru a aborda trei constrângeri care au început să intre în conflict la scară mare: terminarea media cu un port per sesiune nu se potrivește bine infrastructurii OpenAI, sesiunile ICE (Interactive Connectivity Establishment) și DTLS (Datagram Transport Layer Security) cu stare au nevoie de proprietate stabilă, iar rutarea globală trebuie să mențină o latență scăzută pe primul hop. În această postare, prezentăm arhitectura separată relay plus transceiver pe care am construit-o pentru a păstra comportamentul WebRTC standard pentru clienți, schimbând în același timp modul în care pachetele sunt rutate în interiorul infrastructurii OpenAI.

WebRTC ne permite să construim produse AI în timp real

WebRTC este un standard deschis pentru trimiterea de audio, video și date cu latență redusă între browsere, aplicații mobile și servere. Este adesea asociat cu apelurile peer-to-peer, dar este și o bază practică pentru sisteme în timp real client-server, deoarece standardizează părțile dificile ale media interactive: ICE pentru stabilirea conectivității și traversarea NAT (Network Address Translation), DTLS și SRTP (Secure Real-time Transport Protocol) pentru transport criptat, negocierea codecurilor pentru comprimarea și decodarea audio, RTCP (Real-time Transport Control Protocol) pentru controlul calității și funcționalități la nivel de client precum anularea ecoului și bufferul de jitter.

Această standardizare contează pentru produsele AI. Fără WebRTC, fiecare client ar avea nevoie de un răspuns diferit pentru modul în care stabilește conectivitatea prin NAT-uri, criptează media, negociază codecurile (codor-decodorii selectați pentru transmisie și decompresie) și se adaptează la schimbarea condițiilor de rețea. Cu WebRTC, putem construi pe o suită de protocoale deja implementată în browsere și pe platforme mobile, concentrându-ne propria muncă pe infrastructura care conectează media în timp real la modele.

De asemenea, ne bazăm pe ecosistemul WebRTC în sine, inclusiv pe implementări mature open-source și pe munca de standardizare care păstrează interoperabilitatea între browsere, aplicații mobile și servere. Munca de bază a lui Justin Uberti (unul dintre arhitecții originali ai WebRTC) și Sean DuBois (creatorul și întreținătorul Pion) a făcut posibil ca echipe precum a noastră să construiască pe o infrastructură media testată în producție, în loc să reinventeze comportamentul de nivel jos pentru transport, criptare și controlul congestiei. Suntem norocoși că atât Justin, cât și Sean sunt acum colegi aici, la OpenAI, și contribuie la felul în care apropiem WebRTC și AI-ul în timp real.

Pentru AI, cea mai importantă proprietate este că sunetul ajunge ca un flux continuu. Un agent vocal poate începe să transcrie, să raționeze, să apeleze instrumente sau să genereze vorbire cât timp utilizatorul încă vorbește, în loc să aștepte o încărcare completă. Aceasta este diferența dintre un sistem care pare conversațional și unul care seamănă cu push-to-talk.

Alegerea unei arhitecturi media

După ce am ales WebRTC, următoarea întrebare a fost unde să-l terminăm (unde vom accepta și deține conexiunea WebRTC — de exemplu, la margine) și cum să conectăm acele sesiuni la backendul de inferență. Terminarea contează pentru că determină cum gestionăm starea sesiunii în timp real, transportul media, rutarea, latența și izolarea defecțiunilor.

Opțiunea 1: abordarea SFU include AI-ul ca participant WebRTC

Un SFU sau selective forwarding unit, este un server media care primește câte un flux WebRTC de la fiecare participant și redirecționează selectiv fluxurile către ceilalți. În acest model, SFU termină o conexiune WebRTC separată pentru fiecare participant, iar AI-ul se alătură ca un alt participant în sesiune. Aceasta poate fi o potrivire bună pentru produse care sunt prin natura lor multiparticipante, cum ar fi apelurile de grup, sălile de clasă sau ședințele colaborative. Păstrează într-un singur loc codecurile audio, mesajele RTCP, canalele de date, înregistrarea și politica per flux.1

Chiar și în produsele client-la-AI, un SFU este adesea punctul de plecare implicit, deoarece permite echipelor să refolosească un sistem dovedit pentru semnalizare, rutare media, înregistrare, observabilitate și extensii viitoare, cum ar fi transferul către un operator uman sau adăugarea mai multor participanți.

Opțiunea 2: abordarea transceiver termină WebRTC la margine și îl convertește într-un protocol backend

Volumul nostru de lucru este diferit. Majoritatea sesiunilor sunt 1:1 — un utilizator care vorbește cu un model sau o aplicație care vorbește cu un agent în timp real — cu sensibilitate la latență la fiecare schimb de replici. Pentru această formă de trafic, am ales un model transceiver: un serviciu WebRTC la margine termină conexiunea clientului, apoi convertește media și evenimentele în protocoale interne mai simple pentru inferența modelului, transcriere, generarea vorbirii, utilizarea instrumentelor și orchestrare.

În acest design, transceiverul este singurul serviciu care deține starea sesiunii WebRTC, inclusiv verificările de conectivitate ICE, handshake-ul DTLS, cheile de criptare SRTP și ciclul de viață al sesiunii. „Terminare” înseamnă aici că transceiverul este punctul final care finalizează acele handshake-uri și criptează sau decriptează media. Păstrarea acelei stări într-un singur loc a făcut mai ușor de înțeles proprietatea asupra sesiunii și a permis serviciilor backend să scaleze ca servicii obișnuite, în loc să acționeze ele însele ca perechi WebRTC.

Problema centrală de implementare: WebRTC întâlnește Kubernetes

După ce am ales modelul transceiver, prima noastră implementare a fost un singur serviciu Go construit pe Pion, care gestiona atât semnalizarea, cât și terminarea media. Acesta alimentează ChatGPT Voice, punctul final WebRTC al Realtime API și o serie de proiecte de cercetare.

Din punct de vedere operațional, serviciul transceiver îndeplinește două roluri:

  • Semnalizare: negociere SDP, selecția codecurilor, acreditive ICE și configurarea sesiunii
  • Media: terminarea conexiunilor WebRTC downstream și menținerea conexiunilor upstream către serviciile backend pentru inferență și orchestrare

Ne-am dorit ca serviciul să ruleze ca restul infrastructurii noastre: pe Kubernetes, unde sarcinile de lucru pot crește și scădea și se pot muta între gazde pe măsură ce cererea se schimbă. Dar modelul WebRTC convențional cu un port per sesiune se potrivește prost acestui mediu, deoarece depinde de intervale mari de porturi UDP publice, care sunt dificil de expus, securizat și păstrat pe măsură ce pod-urile sunt adăugate, eliminate sau reprogramate.2

Epuizarea porturilor

Prima problemă era chiar modelul cu un port per sesiune. La un nivel ridicat de concurență, asta înseamnă expunerea și gestionarea unor intervale foarte mari de porturi UDP.

  • Echilibratoarele de sarcină din cloud și serviciile Kubernetes nu sunt concepute în jurul a zeci de mii de porturi UDP publice per serviciu. Fiecare interval suplimentar adaugă complexitate operațională în configurația echilibratorului de sarcină, verificarea stării de sănătate, politica de firewall și siguranța implementării.3
  • Intervalele mari de porturi UDP sunt greu de securizat deoarece extind suprafața accesibilă extern și fac mai dificil de auditat politica de rețea.
  • De asemenea, acestea nu sunt potrivite pentru autoscalare. Pod-urile sunt adăugate, eliminate și reprogramate constant în Kubernetes. Cerința ca fiecare pod să rezerve și să publice un interval mare și stabil de porturi face ca această elasticitate să fie fragilă.4

Din acest motiv, multe sisteme WebRTC evoluează către un singur port UDP per server, cu demultiplexare la nivel de aplicație în spatele acelui port.5

Păstrarea stării pe aceeași instanță

Designurile cu un singur port per server rezolvă numărul de porturi, dar introduc o a doua problemă: păstrarea proprietății fiecărei sesiuni într-o flotă de instanțe.

ICE și DTLS sunt protocoale cu stare. Procesul care a creat o sesiune trebuie să continue să primească pachetele acelei sesiuni pentru a putea valida verificările de conectivitate, finaliza handshake-ul DTLS, decripta SRTP și procesa schimbările ulterioare ale sesiunii, cum ar fi repornirile ICE. Dacă pachetele pentru aceeași sesiune ajung la un alt proces, configurarea poate eșua sau media se poate întrerupe.

Asta ne-a oferit un obiectiv clar: să expunem o suprafață UDP mică și fixă către internetul public, continuând în același timp să rutăm fiecare pachet către transceiverul care deține sesiunea WebRTC corespunzătoare.

Comparație între arhitecturile media WebRTC

Am evaluat mai multe moduri de a ajunge acolo, inclusiv TURN (Traversal Using Relays around NAT), unde un relay de margine termină alocările clientului și redirecționează traficul în numele acestuia.2

Abordare

Avantaje

Dezavantaje

IP:port unic per sesiune (cunoscut și ca UDP direct nativ)

Cale media directă client-server

Fără strat de redirecționare pe calea datelor

Necesită un port UDP public per sesiune

Intervalele mari de porturi sunt dificil de expus și securizat

Nu se potrivește bine cu Kubernetes și cu echilibratoarele de sarcină din cloud

IP:port unic per server

Amprentă UDP publică mult mai mică decât expunerea per sesiune

Un socket partajat per server poate demultiplexa multe sesiuni

Funcționează curat pe o singură gazdă, dar nu și într-o flotă partajată cu echilibrare de sarcină, de unul singur

Demultiplexarea sesiunilor pe o singură gazdă ajută doar după ce un pachet ajunge pe acea gazdă; într-o flotă cu echilibrare de sarcină, primul pachet poate ajunge totuși la instanța greșită, deci ai nevoie în continuare de o modalitate deterministă de a direcționa fiecare sesiune către procesul care o deține


Relay TURN (cu terminare de protocol)

Clienții trebuie doar să ajungă la adresa și portul relay-ului TURN

Poate centraliza politica la margine

Alocările TURN adaugă curse dus-întors la configurare

Mutarea sau recuperarea alocărilor între servere TURN este în continuare dificilă

Forwarder fără stare + terminator cu stare (relay-ul OpenAI și transceiverul)

Amprentă UDP publică redusă

Transceiverul deține în continuare întreaga sesiune WebRTC

Adaugă un hop de redirecționare înainte ca media să ajungă la transceiverul proprietar

Necesită coordonare personalizată între relay și transceiver

Prezentarea generală a arhitecturii: relay + transceiver

Arhitectura pe care am livrat-o separă rutarea pachetelor de terminarea protocolului. Semnalizarea ajunge în continuare la transceiver pentru configurarea sesiunii, în timp ce media intră mai întâi prin relay. Relay-ul este un strat ușor de redirecționare UDP, cu o amprentă publică redusă, iar transceiverul este punctul final WebRTC cu stare din spatele lui.

Relay-ul redirecționează pachetele către transceiver fără stare

Relay-ul nu decriptează media, nu rulează mașini de stare ICE și nu participă la negocierea codecurilor. Citește suficiente metadate din pachet pentru a alege o destinație, apoi redirecționează pachetul către transceiverul care deține sesiunea. Transceiverul vede în continuare un flux WebRTC normal și deține în continuare toată starea protocolului. Din perspectiva clientului, nimic legat de sesiunea WebRTC nu se schimbă.

Rutare pe acreditive ICE

Rutarea primului pachet este pasul-cheie în această configurație. Un relay trebuie să poată ruta primul pachet de la un client înainte ca vreo sesiune să existe pe calea pachetului în sine, în loc să se oprească pentru o interogare către un serviciu extern.

Fiecare sesiune WebRTC poartă deja un mecanism nativ de rutare al protocolului: fragmentul de nume de utilizator ICE sau ufrag, un identificator scurt schimbat în timpul configurării sesiunii și repetat în verificările de conectivitate STUN. Generăm ufrag-ul de pe partea serverului astfel încât să conțină exact suficiente metadate de rutare pentru ca relay-ul să poată deduce clusterul de destinație și transceiverul proprietar.

Diagrama de secvență arată cum este stabilită conexiunea

În timpul semnalizării, transceiverul alocă starea sesiunii și returnează un VIP relay partajat și un port UDP în răspunsul SDP. Un VIP este o adresă IP virtuală aflată în fața flotei de relay-uri; combinată cu portul, îi oferă clientului o destinație unică și stabilă, precum „203.0.113.10:3478”, chiar dacă în spatele ei se află multe instanțe de relay. Primul pachet de pe calea media al clientului este de obicei o cerere de asociere STUN (Session Traversal Utilities for NAT), pe care ICE o folosește pentru a verifica dacă pachetele pot ajunge la adresa anunțată.

Relay-ul analizează doar cât este necesar din acel prim pachet STUN pentru a citi ufrag-ul serverului, a decodifica indiciul de rutare și a redirecționa pachetul către transceiverul proprietar. Fiecare transceiver ascultă pe un socket UDP partajat, adică un singur punct final al sistemului de operare asociat unei perechi IP:port interne, nu un socket per sesiune. După ce relay-ul creează o sesiune de la IP-ul și portul sursă ale clientului către destinația acelui transceiver, pachetele DTLS, RTP și RTCP ulterioare circulă în cadrul sesiunii fără a mai fi necesară redecodificarea ufrag-ului.

Sesiunea relay-ului este în mod intenționat minimă, constând doar dintr-o sesiune în memorie pentru a ghida redirecționarea pachetelor, împreună cu contoarele necesare pentru monitorizare și temporizatoarele pentru expirarea și curățarea sesiunii. Această alegere de proiectare menține rutarea pachetelor direct pe calea pachetului. Dacă un relay repornește și pierde sesiunea, următorul pachet STUN reconstruiește sesiunea din indiciul de rutare din ufrag. Pentru a o face și mai fiabilă, se folosește un cache Redis pentru a păstra maparea <IP client + port, IP transceiver + port> odată ce ruta este stabilită, astfel încât aceasta să poată fi recuperată mult mai devreme, înainte de sosirea următorului pachet STUN.

Global Relay și semnalizare direcționată geografic

După ce am redus suprafața UDP publică la un număr mic de adrese și porturi stabile, am putut implementa același model relay la nivel global. Global Relay este flota noastră de puncte de intrare relay distribuite geografic, care implementează toate același comportament de redirecționare a pachetelor.

O intrare geografică extinsă scurtează primul hop client-OpenAI, deoarece un pachet poate intra în rețeaua noastră la un relay aflat aproape de utilizator, atât geografic, cât și ca topologie de rețea, în loc să traverseze mai întâi internetul public către o regiune îndepărtată. În termeni practici, asta înseamnă latență mai mică, jitter mai redus și mai puține rafale de pierderi evitabile înainte ca traficul să ajungă în backbone-ul nostru.6

Stratul Global Relay primește pachete de la client și le redirecționează către clusterul de transceivere

Folosim direcționarea geografică și de proximitate Cloudflare pentru semnalizare, astfel încât cererea HTTP sau WebSocket inițială să ajungă la un cluster transceiver apropiat. Contextul cererii dictează locația sesiunii și punctul de intrare Global Relay care este anunțat clientului. Răspunsul SDP oferă adresa Global Relay, iar ufrag-ul conține suficiente informații pentru ca Global Relay să ruteze media către clusterul desemnat și pentru ca relay-ul să o ruteze către transceiverul de destinație.

Împreună, semnalizarea direcționată geografic și Global Relay plasează atât configurarea, cât și media pe o cale de intrare apropiată, menținând în același timp sesiunea ancorată la un singur transceiver. Acest lucru reduce timpul dus-întors pentru semnalizare și pentru prima verificare de conectivitate ICE, scurtând direct timpul pe care un utilizator îl așteaptă înainte ca vorbirea să poată începe.

Implementarea relay-ului și performanța

Am scris serviciul relay în Go și am păstrat în mod intenționat implementarea restrânsă. Pe Linux, stiva de rețea a nucleului primește pachete UDP de la interfața de rețea a mașinii și le livrează către un socket, punctul final al sistemului de operare pe care un proces îl citește după asocierea unei perechi IP:port. Relay-ul rulează în spațiul utilizatorului, astfel că un proces Go obișnuit citește anteturile pachetelor din acel socket, actualizează o cantitate mică de stare a fluxului și redirecționează pachetele fără a termina WebRTC. Nu am avut nevoie de niciun cadru de tip kernel-bypass, care ar permite unui proces din spațiul utilizatorului să interogheze direct cozile de rețea pentru rate mai mari de pachete, dar ar adăuga și complexitate operațională.

Alegeri-cheie de proiectare:

  • Fără terminare de protocol: relay-ul analizează doar anteturile STUN și ufrag-ul; pentru DTLS, RTP și RTCP ulterioare folosește stare din cache, păstrând pachetele opace.
  • Stare efemeră: menține o mapare mică, în memorie, cu timeout scurt, de la adresa clientului la destinația transceiverului, pentru starea fluxului și observabilitate.
  • Scalabilitate orizontală: mai multe instanțe relay rulează în paralel în spatele unui echilibrator de sarcină. Starea nu este stare WebRTC strictă, așa că repornirile provoacă minime căderi de trafic și recuperare rapidă a fluxului.

Măsuri de eficiență:

  • SO_REUSEPORT este o opțiune de socket Linux care permite mai multor procese worker de relay de pe aceeași mașină să asocieze același port UDP. Nucleul distribuie apoi pachetele primite între acei workeri, ceea ce evită un blocaj într-o singură buclă de citire.
  • runtime.LockOSThread fixează fiecare gorutină care citește UDP pe un anumit fir de execuție al sistemului de operare. Combinat cu SO_REUSEPORT, asta tinde să păstreze pachetele din același flux (IP:port sursă și destinație plus protocol) pe același nucleu CPU, îmbunătățind localitatea cache-ului și reducând schimbarea contextului.
  • Bufferele prealocate și copierea minimă mențin scăzut costul de analiză și alocare pentru a evita adunarea de gunoi în Go.

Această implementare a gestionat traficul nostru media global în timp real cu o amprentă relay relativ redusă, așa că am păstrat designul mai simplu în loc să adoptăm o soluție de tip kernel bypass.

Rezultate și lecții învățate

Această arhitectură ne permite să rulăm media WebRTC în Kubernetes fără a expune mii de porturi UDP. Acest lucru contează deoarece o suprafață UDP mai mică și fixă este mai ușor de securizat și de echilibrat din punctul de vedere al sarcinii și permite infrastructurii să scaleze fără a rezerva intervale mari de porturi publice. Cu suport mai bun din partea infrastructurii Kubernetes și mai multă securitate datorită unei suprafețe mai mici, acest design păstrează și comportamentul WebRTC standard pentru clienți și confirmă că un design fără SFU a fost alegerea implicită potrivită pentru volumul nostru de lucru. Majoritatea sesiunilor noastre sunt point-to-point, sensibile la latență și mai ușor de scalat atunci când serviciile de inferență nu trebuie să se comporte ca perechi WebRTC.

Lecția mai importantă este că cel mai bun loc pentru a adăuga complexitate este într-un strat subțire de rutare, nu în fiecare serviciu backend și nici în comportamentul personalizat al fiecărui client. Codificarea metadatelor de rutare într-un câmp nativ al protocolului ne-a oferit rutare deterministă pentru primul pachet, o amprentă UDP publică redusă și suficientă flexibilitate pentru a plasa punctele de intrare aproape de utilizatori, oriunde în lume.

Câteva alegeri au fost deosebit de importante:

  • Păstrează semantica protocolului la margine. Clienții continuă să vorbească WebRTC standard, ceea ce menține intactă interoperabilitatea între browsere și dispozitive mobile.
  • Păstrează stările dificile ale sesiunii într-un singur loc. Transceiverul deține ICE, DTLS, SRTP și ciclul de viață al sesiunii; relay-ul doar redirecționează pachetele.
  • Rutează pe baza informațiilor deja prezente la configurare. Ufrag-ul ICE ne-a oferit un mecanism de rutare pentru primul pachet fără a adăuga o dependență de interogare pe calea critică.
  • Optimizează pentru cazul comun înainte de a apela la kernel bypass. O implementare Go restrânsă, cu utilizare atentă a SO_REUSEPORT, fixarea firelor și analiză cu puține alocări, a fost suficientă pentru volumul nostru de lucru.

AI-ul vocal în timp real funcționează doar atunci când infrastructura face ca latența să pară invizibilă. Pentru noi, asta a însemnat să schimbăm forma implementării noastre WebRTC fără a schimba ceea ce clienții așteaptă de la WebRTC în sine.