Lanțul
Ce se întâmplă în secunda de după ce cineva termină de vorbit.
Șapte etape, patru ținte de latență, un singur registru de evenimente. Pagina asta este mecanismul întreg, inclusiv părțile care încă sunt ținte, nu măsurători.
- transport audio la intrare și la ieșire
- stt vorbire în text
- endpoint apelantul a terminat
- llm modelul răspunde
- tool unelte și MCP
- tts text în vorbire
- playback audio către apelant
Șapte etape și câte o culoare pentru fiecare.
Culorile de mai jos nu sunt o temă de grafic. Fiecare etapă păstrează aceeași nuanță oriunde apare — bara de latență, cronologia de depanare, fila de metrici, cardul de furnizor de unde ai ales-o — până când culoarea nu mai are nevoie să fie citită.
| transport | Audio-ul vine de la un operator ca flux media sau prin SIP și pleacă pe același drum. Poarta de acces ține socketul pe toată durata apelului și emite transport.connected și transport.disconnected în jurul lui. |
|---|---|
| stt | Transcriere în flux. Ipotezele parțiale sosesc continuu și sunt corectate pe loc; cea finală este ce primește modelul. Transcriptoarele vin în trei clase — flux, tampon și lot — iar doar unul de flux poate fi publicat ca transcriptor live. |
| endpoint | Decizia că apelantul a terminat, adică o ghicire făcută sub presiunea timpului: prea grăbită și vorbești peste cineva care trage aer, prea răbdătoare și fiecare răspuns pare lent. O alimentează și activitatea vocală, și punctuația. |
| llm | Modelul răspunde, în flux. Uneltele pe care decide să le cheme, cunoștințele pe care le caută și câmpurile structurate pe care le completează se întâmplă toate în etapa asta, iar primul token este ținta cronometrului. |
| tool | Unelte de server și unelte MCP, fiecare cu schema ei JSON, fiecare chemată separat. O unealtă poate fi refuzată înainte să ruleze, iar refuzul se scrie ca tool.rejected, nu se pierde în tăcere. |
| tts | Sinteza vocală, transmisă propoziție cu propoziție, ca redarea să înceapă înainte ca răspunsul să fie gata. Cronometrul merge până la primul cadru audio, nu până la ultimul. |
| playback | Audio-ul care se întoarce la apelant și punctul în care trebuie să se oprească în clipa în care acesta intră peste. Tot ce a auzit efectiv apelantul se măsoară aici. |
Patru cifre la care sistemul se ține singur.
Un agent vocal are o singură constrângere dură: pauza dintre momentul în care omul termină și cel în care agentul începe. Peste vreo secundă nu mai sună a conversație, așa că pauza este tratată ca un buget cu părți numite, nu ca o proprietate emergentă.
Fiecare replică este cronometrată față de ele, iar consola le marchează pe cele ratate. Un buget depășit este scris în cuvinte, nu doar în culoare, fiindcă o cifră roșie singură este invizibilă pentru un supervizor daltonist care se uită la un tabel de perete.
Sunt ținte, nu promisiuni. Pe primul apel real, ținta de 1 200 ms a fost ratată cu 3 091 ms măsurați, iar consola a marcat replica. Așa arată contractul când nu este respectat.
- stt.first_partial
- 300 ms
- llm.first_token
- 600 ms
- tts.first_audio
- 400 ms
- endpoint_to_audio
- 1,200 ms
- barge_in.stop_audio
- 120 ms
de când apelantul începe să vorbească până la prima transcriere parțială.
de la trimiterea promptului până la primul token al modelului.
de la trimiterea textului până la primul cadru de audio sintetizat.
contractul întreg: de la tăcerea detectată până la audio plecat spre apelant.
de când apelantul intră peste asistent până când audio-ul acestuia se oprește.
Citite din configurație în momentul randării paginii. Schimbi bugetul, se schimbă și panoul.[1]
Să știi când cineva chiar a terminat.
Tăcerea nu este semnalul care pare. Un apelant se oprește la mijlocul frazei ca să gândească, respiră între propoziții și se oprește sec la capătul unui răspuns scurt. Un detector care se declanșează doar pe tăcere îl întrerupe pe primul și lâncezește la al treilea.
Așa că merg două lucruri în paralel: detecția activității vocale cu o fereastră de reținere și un cronometru de punctuație care se declanșează repede odată ce transcrierea pare încheiată. Suprapunerea dintre ele este intenționată și este motivul pentru care poarta de întrerupere măsoară voce susținută, nu timpul scurs de la ultimul început.
| on_punctuation_seconds | 100 ms implicit. Odată ce transcrierea se termină cu punctuație, detectorul se declanșează după atâta, fără să aștepte acordul detectorului de voce. |
|---|---|
| vad_hangover | 240 ms. Detectorul de voce rămâne agățat atâta timp după ultimul cadru de vorbire, ca o respirație între propoziții să nu fie citită drept sfârșit de frază. Se suprapune constant peste declanșarea detectorului de pauză, motiv pentru care timpul scurs de la începutul vocii este un semnal inutil pentru întrerupere. |
| start_speaking_plan.wait_seconds | Implicit 0,4 s, interval 0 până la 5. Cât așteaptă asistentul după detecția pauzei înainte să vorbească, per asistent. |
Planul de pornire a vorbirii pus pe asistent este cel pe care rulează apelul: poarta de acces îl citește la începutul sesiunii, iar cele patru așteptări ale lui — cea simplă și cele pentru o transcriere care se termină în punctuație, în cifre sau în niciuna — decid când s-a încheiat replica.[2]
- 2.040 assistant.speech.started
- 2.600 user.speech.started apelantul intră peste
- 2.720 assistant.speech.interrupted țintă barge_in.stop_audio
- 2.720 llm.aborted nu se mai generează tokenuri
Cele 120 ms sunt ținta configurată. Restul este ritmul apelantului și nu afirmă nimic.[3]
Oprirea în mijlocul cuvântului.
Când apelantul intră peste, trei lucruri trebuie să se întâmple împreună: redarea se oprește, generarea modelului este abandonată ca să nu se mai plătească și să nu se mai rostească alte tokenuri, iar replica asistentului este notată ca întreruptă, ca transcrierea să nu pretindă că apelantul a auzit fraza întreagă.
Poarta măsoară vocea susținută a apelantului auzită în timp ce asistentul era audibil, nu timpul de la ultima declanșare a detectorului de voce. Diferența nu este academică: măsurat de la început, chiar primul cadru primit în replica asistentului trece deja de o poartă de 200 ms, iar asistentul este oprit după un cuvânt sau două la fiecare replică.
| num_words | Implicit 0, interval 0 până la 10. Câte cuvinte transcrise trebuie să existe ca întreruperea să conteze. Zero înseamnă că decide detectorul de voce; o valoare peste zero adaugă câteva sute de milisecunde de transcriere la fiecare întrerupere, pe care apelantul le aude ca pe o vorbire peste el. |
|---|---|
| voice_seconds | Implicit 0,2 s, maxim 0,5. Voce susținută a apelantului, auzită cât timp asistentul era audibil, înainte ca replica să fie anulată. |
| backoff_seconds | Implicit 1,0 s, interval 0 până la 10. Cât stă asistentul tăcut după ce a fost întrerupt, înainte să poată vorbi din nou. |
70 de lucruri pe care un apel le poate spune despre el însuși.
Aceasta este lista completă, citită din enumerare la randarea paginii, nu bătută la mașină aici. Fiecare are un decalaj în milisecunde de la începutul apelului și o încărcătură opțională, iar toate ajung la tine prin ruta de evenimente și prin webhook-uri.
Mai jos sunt grupate după etapa care le emite, în aceleași culori. Nu există un flux separat, rezumat, pentru clienți: cronologia de depanare pe care o deschide un inginer la trei dimineața este o randare directă a acestor rânduri.
Grupate cu aceeași regulă folosită de componenta de cronologie a consolei.[4]
transport 43
- call.created
- call.ringing
- call.connected
- call.ended
- call.failed
- transport.connected
- transport.disconnected
- call.state.changed
- s2s.started
- s2s.first_audio
- s2s.completed
- s2s.failed
- handoff.started
- handoff.completed
- transfer.started
- transfer.completed
- transfer.failed
- operator.queued
- operator.ringing
- operator.connected
- operator.rejected
- operator.missed
- operator.wrap_up
- queue.abandoned
- queue.overflowed
- supervisor.attached
- supervisor.mode_changed
- supervisor.detached
- dtmf.received
- dtmf.sent
- sip.request.sent
- sip.response.received
- voicemail.detected
- campaign.opt_out_detected
- recording.consent_announced
- recording.consent_recorded
- recording.started
- recording.paused
- recording.resumed
- recording.ready
- analysis.completed
- provider.fallback
- provider.error
stt 4
- user.speech.started
- user.speech.ended
- transcript.partial
- transcript.final
endpoint 1
- endpoint.detected
llm 5
- llm.started
- llm.first_token
- llm.completed
- llm.failed
- llm.aborted
tool 9
- tool.started
- tool.completed
- tool.failed
- tool.rejected
- mcp.started
- mcp.completed
- mcp.failed
- knowledge.queried
- knowledge.insufficient
tts 5
- tts.started
- tts.first_audio
- tts.completed
- tts.failed
- tts.aborted
playback 3
- assistant.speech.started
- assistant.speech.interrupted
- assistant.speech.completed
Ce lasă în urmă un apel încheiat.
Fiecare dintre ele este și o rută pe resursa de apel, nu doar o filă în consolă, așa că orice poți citi pe ecran poți și descărca.
| GET /{call}/recording | Mono, stereo sau pe două canale. Varianta pe două canale ține apelantul pe un canal și asistentul sau operatorul pe celălalt, aliniate în timp, ca să poată fi separate ulterior — un mix stereo este spațial și nu poate. Se livrează ca URL semnat care expiră. |
|---|---|
| GET /{call}/transcript | Transcrierea în direct, replică cu replică, cu replicile întrerupte marcate ca atare. Este exact textul primit de model, nu o variantă periată. |
| transcriere după apel | O a doua trecere, mai exactă, făcută după închidere de un transcriptor de lot, care poate fi alt furnizor decât cel live. Sistemul refuză să publice ca live un transcriptor care merge doar pe lot, fiindcă greșeala asta pică toate apelurile deodată. |
| GET /{call}/metrics | Latența fiecărei replici, desfăcută pe etape, adică exact ce desenează bara stivuită din consolă. |
| GET /{call}/costs | Contabilitatea costului per apel, împărțită pe etape și furnizori, scrisă într-un registru imuabil, nu recalculată la citire. |
| ieșiri structurate | Câmpurile structurate pe care i-ai cerut modelului să le completeze în timpul apelului, validate față de schema ta și păstrate ca înregistrări cu versiuni. |
| GET /{call}/artifacts | Tot ce a produs apelul sub formă de fișiere, pe bucketul platformei sau pe al tău. |
| retenție și ștergere | O sarcină programată aplică zilnic politica ta de retenție. Ștergerea unei înregistrări e o ștergere reală din stocare; ștergerea restului unui apel trece prin retenție, nu printr-un buton. |
Note
-
1
Ținte:
config/callagent.phpșipackages/contracts/src/metrics.ts, care țin aceleași cinci constante de o parte și de alta a firului. -
2
Cifrele pentru detecția pauzei și fereastra de reținere:
apps/voice-gateway/src/session/barge-in.ts. Planul de pornire a vorbirii este definit înapp/Filament/Resources/Assistants/Schemas/AssistantForm.phpși este citit deendpointingConfig()dinCallSession.ts, care are propriul fișier de teste. -
3
Valorile implicite ale planului de oprire:
app/Filament/Resources/Assistants/Schemas/AssistantForm.php, secțiunea de oprire a vorbirii. -
4
Vocabularul de evenimente:
app/Enums/CallEventType.php. Regula de grupare este cea dinresources/views/components/ca/timeline.blade.php.