Steven
Steven5 min lukuaika

Poistimme 5 000 riviä äänikoodia — ja transkriptit alkoivat näkyä kahdesti

GeekBye v1.6 repi irti kaksi laitteessa toimivaa Swift-transkriboijaa ja korvasi ne yhdellä yhtenäisellä putkella — nettona yli 5 000 riviä poistettiin, ja tämä tehtiin samalla kun ihmiset olivat kesken kokouksen sovelluksessa. Sitten transkriptit alkoivat ilmestyä kahdesti, koska vika siirtyi siihen ainoaan kerrokseen, joka yhä luuli moottoreita olevan kaksi.

Tekniikka
Transkriptio
Arkkitehtuuri
GeekBye-julkaisut
Poistimme 5 000 riviä äänikoodia — ja transkriptit alkoivat näkyä kahdesti

Refaktoroinnissa on erityinen kauhun laji, kun muokkaat koodia joka on käynnissä juuri nyt, ihmisille jotka ovat kesken kokouksen. Et voi ottaa ominaisuutta alas huoltoa varten — joku luottaa siihen transkriboidakseen keskustelua, jota hän ei voi toistaa. GeekBye v1.6 teki juuri näin sovelluksen kriittisimmälle polulle: äänen käsittelyputkelle. Tämä on tarina siitä, mitä poistimme, mikä meni rikki, ja kahdesta luotettavuusopetuksesta jotka siitä syntyivät.

Kaksi moottoria, toinen niistä Swiftissä

Ennen v1.6:ta GeekByen puheentunnistus toimi laitteessa Swift-binääreissä. Niitä oli kaksi: yksi kääri Applen Speech-kehyksen, ja suurempi (yli 1 500 riviä) kaappasi kaksoisäänen — mikrofonisi plus järjestelmän äänen — ScreenCaptureKit-rajapinnan kautta ja avasi oman WebSocketinsa suoraan taustapalveluumme striimatakseen Deepgramille. Kaksi transkriboijaa, kaksi kaappauspolkua, kaksi tapaa keskustella verkon kanssa, kaikki käännetyssä kielessä, mikä tarkoitti että jokainen muutos vaati binäärin uudelleenkääntämisen.

v1.6.0 romahdutti kaiken tämän yhdeksi putkeksi. Yksittäinen yhtenäistävä commit poisti Apple Speech -transkriboijan (~1 145 riviä), Deepgramin Swift-kaappaajan (~1 523 riviä), molemmat TypeScript-sillat, vanhan orkestroijan ja kaksoisäänen IPC-käsittelijät — nettona yli 5 200 rivin poisto. Niiden tilalle: renderöijä kaappaa ääntä standardien Web APIs -rajapintojen kautta, lähettää sen PCM-muodossa IPC:n yli, ja Electron-pääprosessi omistaa yhden WebSocket-polun taustapalveluun, palveluntarjoaja valittuna taustapalvelun konfiguraatiolla. Yksi kaappauspolku, yksi socketin omistaja, yksi paikka jossa päätellä.

Viidentuhannen rivin poistaminen tuntuu hienolta. Noin päivän ajan.

Vika siirtyi kerrokseen, joka yhä luuli niitä olevan kaksi

Sitten transkriptit alkoivat näkyä kahdesti. Yksi ääneen lausuttu lause tallentui kahtena identtisenä merkintänä — joskus pahemmin.

Perimmäinen syy on koko tämän julkaisun opettavaisin asia, koska se on yhtenäistämisen yleinen laki. Olimme yhtenäistäneet palveluntarjoajat pääprosessissa — yksi socket, yksi käsittelijä. Mutta React-kerros ei ollut saanut viestiä: se kiinnitti yhä kaksi palveluntarjoajakohtaista tapahtumakoukkua kerralla, toisen rakennettuna Deepgramille ja toisen ElevenLabsille. Molemmat kuuntelivat. Molemmat tallensivat. Jokainen transkripti tallentui sen koukun toimesta joka sattui olemaan elossa — ja molemmat olivat.

Ja sen alla oli toinen, ovelampi kaksinkertaistuminen: ElevenLabs lähettää jokaisen viimeistellyn rivin kahdesti — kerran tapahtumana committed ja uudelleen tapahtumana committed_with_timestamps. Joten jopa yhdellä koukulla yksi lause saattoi saapua kahtena tapahtumana.

Korjaus (v1.6-linjassa) on siisti kaksiosainen, joka vastaa täsmälleen kahta syytä: portitetaan jokainen tapahtumakoukku enabled-lipun taakse, jota ohjaa taustapalvelun aktiivisen palveluntarjoajan konfiguraatio, jotta vain yksi kuuntelija on koskaan elossa; ja lisätään lähdekohtainen tekstin päällekkäisyyksien poisto (muistetaan viimeisin tallennettu rivi mikrofonille ja järjestelmän äänelle) nielaisemaan ElevenLabsin kaksoislähetys.

Opetus on suurempi kuin tämä vika: kun yhtenäistät kaksi taustapalvelua yhden rajapinnan taakse, päällekkäisyys ei katoa — se muuttaa siihen kerrokseen joka yhä olettaa niitä olevan kaksi. Romahdutimme putkiston pääprosessissa ja vuoto nousi pintaan kaksi kerrosta ylempänä, käyttöliittymän tapahtumakytkennöissä. Putken yhtenäistäminen ei ole valmis kun tuottaja on yhtenäistetty; se on valmis kun jokainen kuluttaja on lakannut uskomasta vanhaan muotoon.

Ensimmäinen uudelleenyhdistys: "luovuta viiden yrityksen jälkeen"

v1.6.0 toi myös GeekByen aivan ensimmäisen WebSocket-automaattiuudelleenyhdistyksen eksponentiaalisella viiveellä — ja sitä kannattaa näyttää nimenomaan siksi, että se on nykyään käyttämämme luodinkestävän version alkukantainen esi-isä.

Alkuperäinen oli rajattu. Viisi yritystä, viiveet 1 s, 2 s, 4 s, 8 s, 16 s — ja sitten se antautui, lähettäen kohtalokkaan virheen: "Yhteys menetetty — käynnistä transkriptio uudelleen." Tuolloin se tuntui vastuulliselta: älä yritä ikuisesti, kerro käyttäjälle rehellisesti. Käytännössä se oli naamioitunut käytettävyysvirhe. Kuudentoista sekunnin verkkokatkos — Wi-Fi-siirtymä, VPN-uudelleenyhdistys, tunneli — on ohimenevä, mutta rajattu uudelleenyritys kohtelee sitä lopullisena. Käyttäjä ei tehnyt mitään väärin ja häntä kehotettiin käynnistämään live-kokous uudelleen.

Juuri tämän vian nykyinen suunnittelu poisti. Nykyään GeekBye yhdistää uudelleen rajattomalla viiveellä ja jopa kiertää palveluntarjoajia, ja pysähtyy koskaan vain aidosti kohtalokkaasta syystä — todennus, kiintiö, laskutus. Koko matka "luovuta viiden jälkeen" -ajattelusta "älä koskaan luovuta ohimenevästä virheestä" -ajatteluun kerrotaan artikkelissa miksi AI-muistiinpanovälineesi pysähtyy huonolla Wi-Fillä. v1.6 on paikka jossa tuo tie alkoi, sen naiivin version kanssa jonka piti olla olemassa ensin.

Vanhat yhteydet vainoavat sinua, ellei niillä ole nimiä

Vielä yksi luotettavuusopetus laskeutui v1.6.3:ssa, korjaten hienovaraisen vikaluokan jonka kuka tahansa uudelleenyhdistystä tilallisen taustapalvelun yli tekevä lopulta kohtaa.

Kun yhdistät uudelleen — tai vaihdat transkriptiokieltä kesken session, mikä yhdistää uudelleen konepellin alla — vanha yhteys ei aina kuole hiljaa. Sen kuolinkorina-viestit (CONNECTION_LOST, disconnected) voivat saapua sen jälkeen kun uusi yhteys on jo terve, ja purkaa täydellisen hyvän korvaajan. Korjaus antaa jokaiselle yhteysyritykselle identiteetin — yhteyskohtaisen istunto-ID:n leimattuna WebSocket-URL:iin — sekä lyhyen armonaikaikkunan uudelleenyhdistyksen jälkeen, jonka aikana aiemman yrityksen vanhat katkaisuviestit jätetään huomiotta. Se lähettää myös eksplisiittisen stop-viestin ennen socketin sulkemista, jotta taustapalvelu voi purkaa vanhan session siististi.

Periaate: minkä tahansa uudelleenyhdistyslogiikan tilallisen taustapalvelun yli on merkittävä jokainen yritys identiteetillä ja kohdeltava aiempien yritysten myöhäisiä viestejä kohinana — muuten onnistunut uudelleenyhdistys tapetaan edeltäjänsä viimeisellä henkäyksellä.

Kolme asiaa jotka v1.6 opetti

  1. Yhtenäistäminen siirtää vikoja; se ei poista niitä. Romahduta kaksi moottoria yhdeksi ja auditoi jokainen kuluttaja joka yhä luulee niitä olevan kaksi. Meidän oli käyttöliittymä, joka kiinnitti kaksi tapahtumakoukkua sen jälkeen kun putkisto oli jo yhdistetty.
  2. Ensimmäinen uudelleenyhdistyksesi tulee olemaan rajattu, ja rajattu on väärin live-striimeille. "Luovuta N yrityksen jälkeen" muuttaa ohimenevän katkoksen lopulliseksi vikaksi josta käyttäjää syytetään. Erottele kohtalokas (todennus, kiintiö) ohimenevästä (pudonnut socket) aivan ensimmäisestä versiosta lähtien.
  3. Uudelleenyhdistykset tarvitsevat identiteetin. Merkitse jokainen yritys; jätä huomiotta kuolleiden yritysten myöhäiset viestit. Nimetön yhteys voidaan murhata oman haamunsa toimesta.

Tämä on toinen luku luotettavuustarinasta josta tulee GeekBye v2. Ensimmäisen osalta katso automaattipäivityssaaga joka kesti kuusi julkaisua neljässä päivässä (v1.5.x); sen osalta minne uudelleenyhdistystyö lopulta saapui, miksi AI-muistiinpanovälineesi pysähtyy huonolla Wi-Fillä; ja koko kaaren osalta, anatomia ohjelmiston toimittamisesta täydellisyyteen.