Steven
Steven5 min lasīšana

Mēs izdzēsām 5,000 rindiņu audio koda — un transkripti sāka parādīties divreiz

GeekBye v1.6 izrāva ārā divus ierīcē strādājošus Swift transkribētājus un aizstāja tos ar vienu vienotu cauruļvadu — neto dzēsums vairāk nekā 5,000 rindiņu, veikts, kamēr cilvēki bija tiešsaistē sanāksmes vidū. Tad transkripti sāka parādīties divreiz, jo kļūda pārcēlās uz to vienīgo slāni, kurš joprojām domāja, ka ir divi dzinēji.

Inženierija
Transkripcija
Arhitektūra
GeekBye laidieni
Mēs izdzēsām 5,000 rindiņu audio koda — un transkripti sāka parādīties divreiz

Ir īpaša veida šausmas, refaktorējot kodu, kas darbojas tieši tagad, cilvēkiem, kuri ir sanāksmes vidū. Tu nevari uz laiku noņemt funkciju apkopei — kāds paļaujas uz to, lai transkribētu sarunu, ko nevar atkārtot. GeekBye v1.6 izdarīja tieši to ar lietotnes vissvarīgāko ceļu: audio cauruļvadu. Šis ir stāsts par to, ko mēs izdzēsām, kas salūza, un divām uzticamības mācībām, kas no tā izrietēja.

Divi dzinēji, viens no tiem Swift valodā

Pirms v1.6 GeekBye runas pārvēršana tekstā strādāja ierīcē Swift binārajos failos. To bija divi: viens ietvēra Apple's Speech ietvaru, bet lielāks (vairāk nekā 1,500 rindiņu) tvēra divkāršu audio — tavu mikrofonu plus sistēmas skaņu — caur ScreenCaptureKit un atvēra savu WebSocket tieši uz mūsu aizmuguri, lai straumētu uz Deepgram. Divi transkribētāji, divi tveršanas ceļi, divi veidi, kā runāt ar tīklu, viss kompilētā valodā, kas nozīmēja, ka katra izmaiņa prasīja binārā faila pārkompilēšanu.

v1.6.0 sabruka to visu vienā cauruļvadā. Vienotais apvienojošais commit izdzēsa Apple Speech transkribētāju (~1,145 rindiņas), Deepgram Swift tvērēju (~1,523 rindiņas), abus TypeScript tiltus, veco orķestrētāju un divkāršā audio IPC apstrādātājus — neto dzēsums vairāk nekā 5,200 rindiņu. To vietā: renderētājs tver audio caur standarta Web APIs, sūta to kā PCM caur IPC, un Electron galvenais process pārvalda vienu WebSocket ceļu uz aizmuguri, ar pakalpojumu sniedzēju, ko izvēlas aizmugures konfigurācija. Viens tveršanas ceļš, viens ligzdas īpašnieks, viena vieta, kur spriest.

Piecu tūkstošu rindiņu dzēšana jūtas lieliski. Apmēram vienu dienu.

Kļūda pārcēlās uz slāni, kas joprojām domāja, ka ir divi

Tad transkripti sāka parādīties divreiz. Viens izrunāts teikums tiktu saglabāts kā divi identiski ieraksti — dažreiz sliktāk.

Pamatcēlonis ir vispamācošākā lieta visā šajā laidienā, jo tas ir vispārējs apvienošanas likums. Mēs bijām apvienojuši pakalpojumu sniedzējus galvenajā procesā — viena ligzda, viens apstrādātājs. Bet React slānis nebija saņēmis ziņu: tas joprojām piestiprināja divus sniedzējam specifiskus notikumu āķus vienlaikus, vienu būvētu Deepgram un vienu ElevenLabs. Abi klausījās. Abi saglabāja. Katrs transkripts tika saglabāts, atkarībā no tā, kuri āķi bija dzīvi — un abi bija.

Un zem tā bija otrs, viltīgāks dublējums: ElevenLabs izdod katru pabeigto rindiņu divreiz — reizi kā committed notikumu un vēlreiz kā committed_with_timestamps. Tātad pat ar vienu āķi viens teikums varēja pienākt kā divi notikumi.

Labojums (v1.6 līnijā) ir kārtīga divdaļīga lieta, kas precīzi atbilst diviem cēloņiem: aizsprosto katru notikumu āķi aiz enabled karodziņa, ko vada aizmugures aktīvā sniedzēja konfigurācija, lai vienmēr būtu dzīvs tikai viens klausītājs; un pievieno teksta dublikātu novēršanu katram avotam (atceries pēdējo saglabāto rindiņu mikrofonam un sistēmas audio), lai norītu ElevenLabs divkāršo izdošanu.

Mācība ir lielāka par šo kļūdu: kad tu apvieno divas aizmugures aiz vienas saskarnes, dublēšanās nepazūd — tā migrē uz slāni, kurš joprojām pieņem, ka ir divi. Mēs sabrukām santehniku galvenajā procesā, un noplūde parādījās divus slāņus augstāk, lietotāja saskarnes notikumu montāžā. Cauruļvada apvienošana nav pabeigta, kad ražotājs ir apvienots; tā ir pabeigta, kad katrs patērētājs ir pārstājis ticēt vecajai formai.

Pirmā atkārtotā savienošanās: "padoties pēc pieciem mēģinājumiem"

v1.6.0 arī nogādāja GeekBye pašu pirmo WebSocket automātisko atkārtoto savienošanos ar eksponenciālu atkāpi — un to ir vērts parādīt tieši tāpēc, ka tas ir primitīvais sencis tai nesatricināmajai versijai, ko darbinām šodien.

Oriģināls bija ierobežots. Pieci mēģinājumi, aizkaves 1s, 2s, 4s, 8s, 16s — un tad tas padevās, izdodot fatālu kļūdu: "Savienojums zaudēts — lūdzu, restartējiet transkripciju." Toreiz tas šķita atbildīgi: nemēģini bezgalīgi, saki lietotājam godīgi. Praksē tā bija maskēta UX kļūda. Sešpadsmit sekunžu tīkla traucējums — Wi-Fi pārslēgšanās, VPN atkārtota savienošanās, tunelis — ir īslaicīgs, bet ierobežots atkārtojums uztver to kā galīgu. Lietotājs neko nedarīja nepareizi un saņēma norādījumu restartēt tiešu sanāksmi.

Tā ir tieši tā kļūme, ko pašreizējais dizains novērsa. Šodien GeekBye atkārtoti savienojas ar neierobežotu atkāpi un pat cikliski maina sniedzējus, un apstājas tikai patiesi fatāla iemesla dēļ — autentifikācija, kvota, norēķini. Viss ceļojums no "padoties pēc pieciem" līdz "nekad nepadoties īslaicīgas kļūdas dēļ" ir izstāstīts kāpēc tavs AI pierakstītājs apstājas uz sliktas Wi-Fi. v1.6 ir vieta, kur šis ceļš sākās, ar naivo versiju, kurai bija jāpastāv vispirms.

Novecojuši savienojumi tevi vajā, ja vien tiem nav vārdu

Vēl viena uzticamības mācība ienāca v1.6.3, labojot smalku kļūdu klasi, ar ko galu galā sastapsies ikviens, kas veic atkārtotu savienošanu pār stāvokli glabājošu aizmuguri.

Kad tu atkārtoti savienojies — vai maini transkripcijas valodu sesijas vidū, kas zem virsmas atkārtoti savienojas — vecais savienojums ne vienmēr mirst klusi. Tā nāves brīža ziņas (CONNECTION_LOST, disconnected) var pienākt pēc tam, kad jaunais savienojums jau ir vesels, un noārdīt pilnīgi labo aizstājēju. Labojums piešķir katram savienojuma mēģinājumam identitāti — savienojumam specifisku sesijas ID, iespiestu WebSocket URL adresē — plus īsu pielaides logu pēc atkārtotās savienošanās, kura laikā novecojušas atvienošanās ziņas no iepriekšēja mēģinājuma tiek ignorētas. Tas arī nosūta skaidru stop ziņu pirms ligzdas aizvēršanas, lai aizmugure varētu tīri noārdīt veco sesiju.

Princips: jebkurai atkārtotās savienošanas loģikai pār stāvokli glabājošu aizmuguri ir jāmarķē katrs mēģinājums ar identitāti un jāuztver vēlīnās ziņas no iepriekšējiem mēģinājumiem kā troksni — citādi veiksmīgu atkārtoto savienošanos nogalina tā priekšgājēja mirstošā elpa.

Trīs lietas, ko v1.6 iemācīja

  1. Apvienošana pārvieto kļūdas; tā tās neizdzēš. Sabruc divus dzinējus vienā un pārbaudi katru patērētāju, kurš joprojām domā, ka ir divi. Mūsu bija lietotāja saskarne, kas piestiprināja divus notikumu āķus pēc tam, kad santehnika jau bija apvienota.
  2. Tava pirmā atkārtotā savienošanās būs ierobežota, un ierobežots ir nepareizi tiešām straumēm. "Padoties pēc N mēģinājumiem" pārvērš īslaicīgu traucējumu par galīgu kļūmi, par kuru vaino lietotāju. Atšķir fatālu (autentifikācija, kvota) no īslaicīga (nomesta ligzda) jau no pašas pirmās versijas.
  3. Atkārtotai savienošanai ir nepieciešama identitāte. Marķē katru mēģinājumu; ignorē vēlīnās ziņas no mirušiem mēģinājumiem. Savienojumu bez vārda var nogalināt tā paša spoks.

Šī ir uzticamības stāsta otrā nodaļa, kas kļūst par GeekBye v2. Par pirmo skati automātiskās atjaunināšanas sāgu, kas prasīja sešus laidienus četrās dienās (v1.5.x); par to, kur atkārtotās savienošanas darbs galu galā nonāca, kāpēc tavs AI pierakstītājs apstājas uz sliktas Wi-Fi; un par visu loku, programmatūras nogādāšanas līdz pilnībai anatomiju.