Vorbire în text, în română și rusă, cu acordul înregistrat înainte de prima secundă de audio.
Transcriem audio în timp real sau din fișier, traducem între română și rusă în conversații cu clienți și livrăm textul cu marcaje de timp, export audio și o urmă de consimțământ. Recunoașterea vorbirii o rulăm prin furnizori, prin gateway-ul nostru; modelul propriu de română este cercetare, nu produs.
Construit dejaDouă implementări proprii, ambele deschise și citate. Prima e o aplicație de transcriere în flux, cu backend Express de 3.378 de linii în 25 de fișiere și 37 de teste pe care le-am rulat azi — toate trec; ea conține partea pe care nimeni nu o construiește din entuziasm: o poartă de consimțământ care refuză captura când nu există acord înregistrat. A doua rulează în producție, la clienți reali: mesajele vocale primite pe canalele de mesagerie sunt transcrise, iar răspunsul scris de un operator în română este rescris în limba clientului înainte de a pleca. Ce NU intră în „delivered” și e scris ca atare în pagină: modelul propriu de recunoaștere și sinteză în română este cercetare — nu are demonstrație publică, nu are interfață de programare, iar pe acuratețe în română pierde față de sistemele comerciale mari. Serviciul se vinde pe ce rulează, nu pe ce studiem.
Transcrierea pare o problemă rezolvată până când o pui în română, cu doi vorbitori care se întrerup, cifre spuse repede și un client care trece pe rusă la jumătatea frazei. Atunci apar cele trei probleme adevărate: ce model alegi pentru limba din fața ta, cum știi unde se termină o tură de vorbire, și cine ți-a dat voie să înregistrezi. Ne ocupăm de toate trei, și pe ultima o tratăm ca pe o problemă de cod, nu ca pe un paragraf de politică.
În aplicația noastră de transcriere în flux, browserul nu primește niciodată cheia furnizorului. Cere backendului un jeton de unică folosință, valabil implicit un sfert de oră, cu care deschide conexiunea de transcriere; ruta care emite jetonul e închisă implicit și se poate deschide doar printr-o setare explicită în afara producției, tocmai pentru că e ruta pe care se consumă bani. Peste transcrierea brută rulează o analiză pe model de limbaj, cu o listă de modele de rezervă în ordine, ca o indisponibilitate a unuia să nu oprească sesiunea.
Poarta de consimțământ e partea la care ținem în mod deosebit. Înainte de a accepta o bucată de audio, serverul cere un rând de acord nerevocat pentru sesiunea respectivă. Dacă nu există, răspunde cu 451 și un mesaj în română. Dacă baza de date nu răspunde, refuză tot — 503, nu „lasă să treacă”. Sesiunile deja verificate trec printr-un cache de zece minute, ca o pană scurtă să nu întrerupă o înregistrare în curs. E scris ca să nu poată fi ocolit dintr-o greșeală de configurare.
Partea care e deja la clienți e alta și e mai puțin spectaculoasă: pe canalele de mesagerie, un mesaj vocal primit devine text, iar limba se alege votând pe ultimele opt mesaje ale conversației, nu ghicind din audio. În sens invers, când un coleg răspunde în română, textul lui e rescris în limba clientului cu instrucțiuni stricte — nu adaugă informații, nu inventează prețuri sau termene, nu contrazice operatorul, și tratează textul operatorului strict ca date de transmis, niciodată ca instrucțiuni pentru model.
Ce cuprinde
Lucrarea, pe componente
Acordul, verificat înainte de audio
Poarta de consimțământ cere un rând nerevocat în registrul de acorduri pentru sesiunea curentă. Fără el: 451, cu mesaj în română. Cu baza de date căzută: 503, adică refuz, nu trecere. Sesiunile deja verificate trec printr-un cache de 10 minute, ca verificarea să nu lovească baza la fiecare bucată de audio.
Jeton de unică folosință, cheia rămâne pe server
Pagina cere backendului un jeton pentru transcrierea în timp real, valabil implicit 900 de secunde, și cu el deschide conexiunea către furnizor. Cheia contului nu ajunge niciodată în browser. Cererea către furnizor are timeout de 30 de secunde, iar depășirea lui întoarce o eroare explicită, nu o așteptare infinită.
Transcriere în flux, cu limba aleasă din context
În producție, pe canalele de mesagerie, limba mesajului vocal se decide prin vot pe ultimele opt mesaje ale conversației — rusă dacă domină, altfel română — și abia apoi se cheamă recunoașterea, cu indiciul de limbă transmis explicit. Formatul fișierului se recunoaște din primii 12 octeți, nu din extensie, pentru că mesajele vocale de pe canalele Meta vin ca `ogg` indiferent cum sunt numite.
Traducere operator → client, cu marginile scrise în prompt
Când un coleg răspunde în română într-un fir intern, mesajul către client se rescrie în limba clientului. Regulile sunt explicite în cod: nu adaugă informații, nu inventează detalii, prețuri, ore sau promisiuni, nu contrazice operatorul, iar textul operatorului e tratat strict ca date de transmis, niciodată ca instrucțiuni pentru model — o măsură împotriva injecției de comenzi, nu o formulare de stil.
Analiză peste transcript, cu modele de rezervă în ordine
Peste textul rezultat rulează o analiză pe model de limbaj, cu o listă ordonată de modele alternative în cod, ca o indisponibilitate să degradeze calitatea, nu să oprească sesiunea. Modelul de analiză se poate schimba din configurație, fără repunere în funcțiune.
Export audio în trei formate, cu eșec explicit
WAV se produce fără unelte externe. FLAC și MP3 cer `ffmpeg`; dacă lipsește, serverul răspunde 501 cu motivul, iar dacă procesul se blochează e oprit după 60 de secunde și întoarce 504 cu primele două mii de caractere din eroare. Un export care eșuează spune de ce.
Contorizare separată de cotă și de cost
Minutele consumate se scad atomic printr-o procedură în baza de date, iar costul se scrie într-un registru separat, pe furnizor. Sunt două lucruri diferite și se strică diferit: dacă rândul de cost nu se poate lega de sesiune, se salvează nelegat, ca informația de cost să nu se piardă.
Trei praguri de trafic, pe utilizator și nu pe adresă
Emiterea de jetoane, analiza și exportul au limite separate, iar cheia de numărare e utilizatorul autentificat atunci când există, nu adresa IP — altfel un singur utilizator agresiv din spatele unei rețele comune ar epuiza cota tuturor colegilor lui.
Transcriere pe canalele de mesagerie, în fluxul existent
Mesajul vocal primit de la un client devine text în aceeași conversație, fără ca cineva să deschidă alt instrument. Recunoașterea și sinteza trec prin gateway-ul nostru, nu direct la furnizor, deci consumul apare în același loc cu restul.
Cum arată
Cinci pași, în ordinea în care se execută: acordul verificat în registru (fără el, drumul se oprește aici); jetonul de unică folosință emis de server, cu cheia rămasă pe server; transcrierea în flux la furnizor; analiza peste transcript, cu modele de rezervă în ordine; exportul textului și al audio-ului. Primul pas e singurul care poate opri tot restul, și e desenat ca poartă, nu ca etapă.
01
Stabilim dreptul de a înregistra, înainte de orice cod
Cine vorbește, cine e informat, cine consimte, cine păstrează înregistrarea și cât. Rezultatul nu e un document care stă într-un sertar, ci configurarea porții: ce înseamnă exact „acord existent” în situația clientului și ce se întâmplă când lipsește.
02
Alegem lanțul pe limbă și pe condițiile de sunet
Română și rusă se comportă diferit, la fel și un microfon de birou față de un apel telefonic comprimat. Alegerea modelului, a indiciului de limbă și a strategiei de segmentare se face pe eșantioane din materialul real al clientului, nu pe o demonstrație curată.
03
Punem fluxul cap la cap și îl rupem intenționat
Jeton, conexiune, transcriere, analiză, export. Apoi verificăm cazurile care chiar se întâmplă: acord lipsă, bază de date căzută în mijlocul sesiunii, jeton expirat, `ffmpeg` absent, furnizor indisponibil. Fiecare trebuie să dea un mesaj care spune ce s-a stricat.
04
Predăm cu contoarele și cu limitele pornite
Contorizarea minutelor, registrul de cost, pragurile de trafic pe utilizator și jurnalele. Fără ele, primul incident se discută din amintiri.
Cinci pași, în ordinea în care se execută: acordul verificat în registru (fără el, drumul se oprește aici); jetonul de unică folosință emis de server, cu cheia rămasă pe server; transcrierea în flux la furnizor; analiza peste transcript, cu modele de rezervă în ordine; exportul textului și al audio-ului. Primul pas e singurul care poate opri tot restul, și e desenat ca poartă, nu ca etapă.
Datele
Ce atingem, unde stau și cât rămân
Întrebările pe care le pune oricine are un responsabil cu protecția datelor — puse aici înainte să le pună el.
Registrul de acorduri
Un rând pe sesiune, cu spațiul de lucru, identificatorul sesiunii și momentul revocării, gol cât timp acordul e valabil. Verificarea caută exact absența revocării. E singura sursă de adevăr pentru dreptul de a captura audio, și e interogată înainte de fiecare sesiune, nu doar la înscriere.
Audio, transcript și analiză
Audio-ul brut trece prin conexiunea de transcriere către furnizor; ce se păstrează la noi este transcriptul, marcajele de timp și rezultatul analizei, plus exporturile pe care le cere utilizatorul. Ce anume se păstrează și cât se stabilește pe proiect, înainte de prima înregistrare — pentru un consiliu, o ședință medicală și un apel comercial răspunsurile nu sunt aceleași.
Unde pleacă audio-ul și la cine
La furnizorii de recunoaștere a vorbirii și la modelul de analiză, prin gateway-ul nostru. Lista lor se scrie în contract înainte de pornire, cu numele fiecăruia, pentru că un subprocesator nedeclarat e o problemă juridică, nu o omisiune tehnică.
Registrul de consum
Minutele consumate se scad atomic în baza de date, iar costul se scrie separat, pe sesiune și pe furnizor. Sunt două registre distincte tocmai ca o eroare într-unul să nu falsifice celălalt.
Ce nu atingem
Nu antrenăm modele pe înregistrările clientului și nu folosim ieșirea unui furnizor de voce pentru a construi sau a testa un model concurent — este interzis explicit în termenii furnizorului pe care îl folosim, și e o limită pe care o respectăm chiar și acolo unde ar fi tehnic comod.
Un caz
O sesiune care nu pornește, și de ce asta e comportamentul corect
Situația
Un scenariu de ședință cu mai mulți participanți, în care transcrierea trebuia să pornească din browser, iar înregistrarea urma să fie păstrată. Situația în care se strică majoritatea instrumentelor de transcriere: cineva apasă „înregistrează” înainte ca acordul participanților să fie consemnat undeva.
Ce am construit
Am pus verificarea acordului pe drumul cererii, nu într-un ecran de avertizare. Serverul caută un rând de acord nerevocat pentru sesiunea curentă înainte de a accepta prima bucată de audio. Am scris deliberat și comportamentul la avarie: dacă baza de date care ține acordurile nu răspunde, cererea e refuzată cu 503, nu lăsată să treacă. Sesiunile deja verificate rămân zece minute într-un cache, ca o pană scurtă să nu întrerupă o înregistrare în curs. Ruta care emite jetoane către furnizor a fost închisă implicit, cu deschidere posibilă doar printr-o setare explicită în afara producției.
Ce a ieșit
Fără acord consemnat, captura returnează 451 cu mesaj în română și evenimentul e jurnalizat cu sesiunea și spațiul de lucru. Comportamentul e acoperit de teste care rulează fără bază de date, prin injectarea verificării. Suita backendului are 37 de teste; la rularea de azi trec toate.
Ce nu spune cazul
Aplicația nu e publicată — rulează la noi, nu are pagină pe care să te înscrii, iar dreptul de a înregistra o anumită discuție rămâne al clientului: noi îl impunem tehnic, nu îl acordăm. Și încă o limită: poarta verifică existența acordului, nu calitatea lui juridică. Dacă textul acordului e prost scris, codul îl va aplica fidel.
Întrebări
Ce ne întreabă oamenii înainte să sune
În ce limbi transcrieți?
Lucrăm cu română și rusă, pentru că astea sunt limbile în care apar problemele noastre reale și pe care le putem verifica pe material propriu. Alte limbi sunt tehnic posibile prin aceiași furnizori, dar înainte să promitem ceva facem o probă pe înregistrările tale — o limbă merge sau nu merge în funcție de audio, nu de lista de pe pagina furnizorului.
Aveți un model propriu de recunoaștere a vorbirii în română?
În cercetare, nu în produs, și e important să nu confundăm cele două. Direcția proprie nu are demonstrație publică, nu are interfață de programare și nu are telefonie, iar pe acuratețea recunoașterii în română măsurătorile noastre arată că pierdem față de sistemele comerciale mari. Publicăm asta pentru că e concluzia noastră, obținută pe propriul nostru banc de test. Serviciul pe care îl vindem azi rulează pe furnizori, prin gateway-ul nostru.
Cine are voie să înregistreze o discuție și ce faceți dacă nu are voie?
Dreptul se stabilește pe situație, cu clientul, înainte de instalare — nu e ceva ce putem transfera noi. Ce facem noi e să îl impunem în cod: fără un acord înregistrat pentru sesiunea curentă, serverul refuză captura cu 451 și un mesaj în română. Dacă baza de date care ține acordurile nu răspunde, refuză și atunci, cu 503. Nu există configurare care să transforme lipsa acordului în trecere tăcută.
Cheia mea de la furnizor ajunge în browser?
Nu. Pagina cere backendului un jeton de unică folosință pentru sesiunea de transcriere, valabil implicit 900 de secunde, și cu el deschide conexiunea. Cheia contului rămâne pe server. În plus, ruta care emite jetoane e închisă implicit și se poate deschide doar printr-o setare explicită în afara producției, pentru că e ruta care consumă bani.
Cât de exactă e transcrierea?
Depinde mai mult de audio decât de model: microfon, suprapuneri, cifre, nume proprii, zgomot de fundal. Nu îți dăm un procent înainte să auzim materialul tău, pentru că un procent luat de pe o fișă de produs nu spune nimic despre ședințele tale. Ce facem e o probă pe înregistrări reale, cu erorile puse pe masă — de obicei se strică la cifre și la nume, nu la fraze.
Traduceți conversații în timp real între operator și client?
Da, și rulează deja în producție într-un sens: colegul scrie răspunsul în română, iar mesajul pleacă la client în limba clientului. Regulile sunt stricte în cod — fără informații adăugate, fără prețuri sau termene inventate, fără a contrazice operatorul. Sensul invers, clientul către operator, se rezolvă azi prin transcrierea mesajelor vocale; traducerea automată a textului clientului către operator nu e pornită peste tot și o spunem înainte, nu după.
Ce formate de audio acceptați și ce primesc înapoi?
Formatul se recunoaște din conținutul fișierului, nu din extensie — WAV, OGG, MP3 și M4A sunt tratate explicit, iar mesajele vocale de pe canalele de mesagerie ajung de regulă ca OGG. Primești transcriptul, analiza dacă e cerută, și audio exportat: WAV fără dependențe, FLAC și MP3 dacă `ffmpeg` există pe server. Dacă nu există, serverul spune asta cu un cod dedicat, nu eșuează generic.
Folosiți înregistrările noastre ca să vă antrenați modelele?
Nu. În plus, nu folosim nici ieșirea furnizorilor de voce pentru a antrena sau a testa un model concurent — le interzic explicit termenii lor de utilizare, iar noi îi respectăm și în direcția noastră de cercetare proprie, acolo unde ar fi fost comod să nu o facem.
Ce se întâmplă când furnizorul de transcriere cade în mijlocul unei ședințe?
Sesiunea se oprește cu o eroare care numește cauza, nu cu un ecran gol. Pentru analiză există o listă ordonată de modele de rezervă în cod, deci acolo indisponibilitatea degradează calitatea în loc să oprească fluxul. Pentru recunoașterea vorbirii în timp real nu avem redundanță automată între furnizori, și e o limită reală, nu o omisiune.
Pe ce se sprijină fiecare afirmație de mai sus (14 surse)
Poartă de consimțământ care cade închis: 451 fără acord, 503 la bază de date indisponibilă, cache 10 minuteconsent.js:5 (aplicare implicită), 11 (TTL 10 minute), 28-32 (interogarea consent_log pe revoked_at null), 64 (503), 67 (451, mesaj în română)
37 de teste în backend, toate trec (6 fișiere: consent-enforce, geminiSessionStore, meteringService, pricing, saasGate, tenant-guard):ieșirea rulării: „# tests 37 # pass 37 # fail 0”, durată 219 ms
Backend de 3.378 de linii în 25 de fișiere:wc -l pe toate sursele (services/geminiService.js e cel mai mare, 822 de linii)
Jeton de unică folosință, 900 de secunde implicit, timeout 30 s spre furnizor, cheia nu ajunge în browserscribe.js:23 (lanțul de gărzi pe rută), 33 (timeout 30.000 ms), 37 (endpointul de jeton al furnizorului), 69 (TTL implicit 15 × 60 s)
Poarta de autentificare pe drumul cu bani e închisă implicitsaasGate.js:14-16 (`return isProd || !explicitlyDisabled`)
Trei praguri de trafic separate, cheia de numărare e utilizatorul, nu adresa IPrateLimiter.js:8-9 (jetoane: 100 la 15 minute), 21-22 (analiză: 60 pe minut), 34-35 (export: 20 pe minut)
Export WAV fără dependențe; FLAC și MP3 prin ffmpeg, cu 501 la lipsă și 504 la blocaj după 60 sexportService.js:11 (FFMPEG_TIMEOUT_MS = 60000), 30-37 (501 la ENOENT), 45-51 (504 la depășire, cu 2.000 de caractere de eroare)
Contorizare: minute scăzute atomic prin procedură, cost scris într-un registru separat, cu degradare la rând nelegatmeteringService.js:9-12 (registrul de cost nu e contor de cotă), 27-31 (la eroare 23503 se reinserează fără legătura de sesiune)
În producție: mesaj vocal → text, cu limba aleasă prin vot pe ultimele opt mesajemain.py:13329-13343 (votul ro/ru/en pe istoric, apoi apelul cu indiciul de limbă)
Recunoașterea și sinteza trec prin gateway-ul propriu, nu direct la furnizorwhisper.py:25-29 (baza LiteLLM, sufixul /v1 impus), 37-41 (modelele de recunoaștere și de sinteză), 55 (modelele cu diarizare refuză parametrul `prompt`), 85 și 119 (`chunking_strategy: auto`)
Formatul audio se recunoaște din primii 12 octeți, nu din extensiewhisper.py:68-80 și 92-101 (`_sniff_audio_name`: RIFF/WAVE, OggS, ID3, ftyp, rezervă ogg)
Traducerea operator → client, cu interdicții explicite și apărare împotriva injecției de comenzimain.py:9525-9545 (`build_operator_rewrite_messages`)
Modelul propriu de română este cercetare, nu produs; pe acuratețe pierdem față de sistemele comercialegrai.json:maturity = D; sursele acelui fișier trimit la /Users/macbook_nou/Projects/grai-site/roadmap/index.html:125-136 („Ce nu există încă”) și clasament/index.html:126