Vissza a bloghoz

ProCat Solutions

Voice agent architektúra: STT, LLM és TTS valós időben

Valós idejű hangalapú AI-ügynök felépítése: streaming STT, LLM és TTS lánc, latency-költségvetés, jitter buffer, SIP/WebRTC média, magyar nyelvi sajátosságok.

ProCat Solutions voice-aisttttsllmsipwebrtcarchitektura
Voice agent architektúra: STT, LLM és TTS valós időben

Az elmúlt hónapokban több telefonos AI-ügynököt raktunk össze, és mindegyiknél ugyanaz a három alapkérdés jött elő: mennyi idő telik el, míg a hívó befejezi a mondatot és meghallja a választ, mi történik, ha közbevág, és hogyan bírja a rendszer a magyar nyelvet. Ebben a cikkben azt írjuk le, hogy nálunk milyen architektúrára jutottunk, és hol vannak a buktatók.

A pipeline: streaming mindenhol

A klasszikus felállás három lépcsős: beszédfelismerés (STT), nyelvi modell (LLM), beszédszintézis (TTS). Ha ezt a hármat egymás után, blokkolva futtatjuk, a válaszidő könnyen 3-5 másodperc fölé megy, ami telefonban vállalhatatlan. Ezért mindhárom lépcsőt streaming módban használjuk:

  • az STT részleges (partial) átiratokat ad, ahogy a hang érkezik, nem várja meg a mondat végét;
  • az LLM tokenenként streamel, és az első mondatzáró írásjelnél már továbbadjuk a szöveget;
  • a TTS mondatrészenként szintetizál, az első audio-chunk már akkor kimegy, amikor a modell még a folytatást generálja.

Az egész egy aszinkron csővezeték, ahol minden szakasznak saját sorbaállítása és megszakítási jele van. Node.js alatt ez természetesen adja magát: streamek, async iterátorok, AbortController.

Endpointing, turn detection és a latency-költségvetés

A legtöbb késleltetés nem a modellekben, hanem a döntésben van: mikor fejezte be a hívó a mondatot? Egy fix csendküszöb (például 700 ms) egyszerű, de kétféleképpen rossz: rövid szünetnél belevág a hívóba, hosszú küszöbnél pedig lassúnak tűnik a rendszer.

Mi kombinált megközelítést használunk: VAD (voice activity detection) a nyers hangon, az STT saját end-of-speech jelzése, és egy egyszerű nyelvi heurisztika, ami a részleges átiratból megbecsüli, hogy a mondat szintaktikailag lezárult-e. Magyarban ez nehezebb, mint angolban, mert a szórend szabadabb, de a kérdőszavak és a ragok sokat segítenek. A három jelet egy állapotgép súlyozza, és a küszöb adaptív: ha a hívó lassan, szünetekkel beszél, a rendszer türelmesebb lesz.

Ha az endpointing rendben van, jöhet a költségvetés. Az élmény szempontjából a hívó utolsó szótagjától az első visszahallott hangig eltelt idő számít. Ezt nálunk nagyjából így osztjuk fel, tájékoztató nagyságrendekkel:

  • endpointing döntés: 200-400 ms
  • STT véglegesítés: 100-300 ms
  • LLM első token: 300-600 ms
  • TTS első audio-chunk: 150-300 ms
  • hálózat és jitter buffer: 100-200 ms

Ez összesen egy-másfél másodperc körül van, ami már természetesnek hat. Minden szakaszt külön mérünk (span-ekkel, hívásonként), mert ha csak a végösszeget látjuk, nem tudjuk, hol romlott el. Egy jól bevált trükk a kitöltő reakció: ha az LLM első tokenje késik, egy rövid, kontextusfüggő nyugtázás (“Rendben, megnézem.”) már kimehet, ami időt nyer, és a hívó számára a rendszer nem tűnik némának.

Média: SIP, WebRTC, RTP és a jitter buffer

A telefonos oldal a legkevésbé látványos, mégis a legtöbb munkával jár. PSTN felől SIP trunkön RTP jön, jellemzően G.711 (8 kHz, alaw/ulaw) vagy szerencsés esetben Opus. Böngészőből WebRTC érkezik, Opus 48 kHz-en. Az STT modellek viszont többnyire 16 kHz PCM-et várnak, a TTS pedig gyakran 24 kHz-et ad. Ezért a médiarétegben minden irányban resampling van, és ezt érdemes egy helyen, egyértelmű formátumszerződéssel megoldani, különben a hang “cincogóssá” válik, vagy fél sebességgel megy.

Az RTP csomagok nem egyenletesen érkeznek, így a beérkező oldalon jitter buffert használunk (20-60 ms tartomány, adaptív). A kimenő oldalon pedig pacing kell: a TTS gyorsabban termel hangot, mint ahogy a vonal viszi, ezért 20 ms-os keretekben, órajel szerint küldjük, nem ahogy jön. Ha ezt nem tesszük meg, a túloldali jitter buffer eldobja a csomagokat.

Hibakezelés, barge-in, fallbackek

A hívó közbevág: ez a barge-in. Ilyenkor a TTS-lejátszást azonnal le kell állítani, a kimenő puffert kiüríteni, az LLM-generálást megszakítani, és az új felhasználói inputot új körként kezelni. A nehéz része a félbehagyott válasz kezelése: a beszélgetési előzményben azt tároljuk, ami tényleg elhangzott, nem a teljes generált szöveget.

Timeoutok minden külső hívásra: ha az STT-szolgáltató nem válaszol adott időn belül, másodlagos szolgáltatóra váltunk, az LLM-nél pedig kisebb modellre esünk vissza. A retry logika csak idempotens lépéseknél megengedett; egy már félig lejátszott TTS-t nem ismételünk meg. Ha minden kiesik, az ügynök egy előre rögzített hangfájllal elnézést kér és visszahívást ígér: ez rosszabb, mint a normál működés, de sokkal jobb, mint a csend.

Magyar nyelvi sajátosságok

A magyar agglutináló nyelv, ezért az STT-modellek ritkább szóalakoknál bizonytalanabbak, mint angolban. Amit tapasztaltunk:

  • a tulajdonnevek és a szakszavak (utcanevek, termékkódok) felismerése javul, ha az STT-nek szószedetet vagy prompt-hintet adunk;
  • a számokat, dátumokat és időpontokat nem szabad nyersen a TTS-re bízni: “2024. 12. 10.” helyett “kétezer-huszonnégy december tizedike” formában adjuk át, a ragozással együtt (“tizedikén”, “háromkor”);
  • telefonszámokat és azonosítókat csoportosítva, szünetekkel olvastatunk fel, és utána visszakérdezünk;
  • a magyar TTS-hangok között nagy a szórás a hangsúlyozásban, érdemes több szolgáltatót ugyanazon a mondatkészleten összehasonlítani.

A legfontosabb tanulság: a hangalapú ügynök nem egy LLM plusz két API, hanem egy valós idejű médiarendszer, amelyben az LLM csak egy szakasz. A késleltetés, a megszakíthatóság és a nyelvi normalizálás legalább annyi mérnöki munkát igényel, mint a prompt.

QR Code