
Sessizlik Yük Taşıyordu
GeekBye v1'in son iki sürümü aynı rahatsız edici gerçekle ilgili: gerçek bir ağ üzerinden gerçek-zamanlı transkripsiyon kayıpsız değildir, ve dürüst hamle öyleymiş gibi yapmayı bırakmaktır. v1.8.20, her ses parçasının bir kopyasını, bir yeniden bağlanma sırasında atmadan önce diske sakladı, ve transkriptteki boşlukları yüksek sesle işaretlemeye başladı. v1.9.0, bant genişliğinden tasarruf etmek için sessizlik göndermeyi bıraktı — ve sessizliğin, transkripsiyoncunun bir cümlenin bittiğini anlamak için kullandığı tam da o sinyal olduğunu keşfetti. Bir şeyleri atmanın bedeli üzerine iki sürüm.
GeekBye v1'in son iki sürümü — v1.8.20 ve v1.9.0, sürüm-2 dönüm noktasından hemen öncekiler — yalnızca birlikte okuduğunda belirginleşen bir temayı paylaşır. İkisi de uygulamanın sessizce attığı şeylerle ilgilidir, ve ikisi de aynı sonuca varır: verimli olmak için attığın şey, düşündüğünden daha fazla iş yapıyordu. Bir sürüm, ağ yeniden bağlanmaları sırasında ses atıyordu. Diğeri, bant genişliğinden tasarruf etmek için sessizlik atıyordu. Her iki durumda da atılan şey meğer yük taşıyormuş.
Gerçek-zamanlı transkripsiyon kayıpsız değildir, ve v1.8.20 bunu itiraf eder
Bir WebSocket üzerinden akışlı konuşmadan-metne dair, görmezden gelmesi kolay bir gerçek şu: bağlantı düşüp yeniden bağlandığında — ya da uygulama bir transkripsiyon sağlayıcısından diğerine, Deepgram'dan ElevenLabs'a geçtiğinde — istemci o boşluk sırasında yakalanan ses parçalarını kasıtlı olarak atar. Atmak zorundadır. Aniden bir yığın bayat ses alan, yeni bağlanmış bir sağlayıcı boğulur, ya da daha kötüsü, ses-etkinliği dedektörünü yanlış şeye kilitler. Bu yüzden istemci o parçaları atar.
Sorun atmak değil. Sorun, atmanın eskiden ne demek olduğu: o sözcükler transkriptte hiç görünmezdi, ve hiçbir şey sana onların eksik olduğunu söylemezdi. Kayıt eksiksiz görünürdü. Değildi.
v1.8.20 buna iki cepheden saldırır, ve hiçbiri "yeniden bağlanmaların asla ses kaybetmemesini sağlamak" değildir — çünkü yapamazsın, gerçek-zamanlıda olmaz. Bunun yerine kaybı kurtarılabilir ve dürüst kılar.
Kurtarılabilir yarısı, kalıcı bir PCM tee'sidir. Ana-süreç yakalama işleyicisinde, yakalanan her parça artık yeniden bağlanma ve lütuf-süresi atma kapılarına ulaşmadan önce diskteki bir dosyaya yazılır:
every captured chunk is written to
<userData>/sessions/session-<ts>.wavbefore realtime drop gates so reconnect-lost audio is preserved for a future batch-STT reconciliation pass
Mekanizma hoş bir biçimde düşük teknolojili: teeChunkToDisk bir WriteStream açar, önden 44 baytlık bir WAV başlığı ayırır, geldikçe ham 16-bit mono örnekleri ekler, ve durdurmada başlığı nihai boyutlarla yamalar (fs.openSync(..., 'r+'), offset 0'a 44 bayt yaz). Sonuç, eksiksiz sesin çalınabilir bir WAV'ıdır — gerçek-zamanlı transkriptin hiç görmediği kısımlar dahil. Kayıt bittiğinde, dosya yolu ve bayt sayısıyla bir session-audio-saved olayı yayar.
Burada iki dürüstlük notu önemlidir, çünkü bunu abartmak kolaydır. Birincisi: tee bu sürümlerde boşluğu doldurmaz. Ham sesi daha sonraki bir uzlaştırma geçişi için korur — dosyayı canlı transkriptle uzlaştıran bir toplu yeniden-transkripsiyon — ki bu, bu sürümlerin döşediği bir kanca, sevk ettikleri bir özellik değil. Takip commit'i, kimse onu ölü kod sanmasın diye session-audio-saved'i uzlaştırma sorunlarına işaret eden bir TODO ile bile açıklar. İkincisi: bu disk maliyetine yol açar. Ekip kabaca 30 dakikalık kayıt başına 86 MB tahmin etti, bu yüzden aynı sürüm, 7 günden eski kaydedilen oturumları budayan bir StartupCleanupService ekler. Sınırsız büyüyen bir güvenlik ağı bir sızıntıdır; temizlik, tee'yi sevk edilebilir kılan şeydir.
Dürüst yarısı daha da basittir. İlk yeniden bağlanma girişiminde, istemci transkripte gerçek, görünür bir giriş yazar:
[reconnecting — audio may be missing]
O tek satır, sürümün bütün felsefesinin küçük bir minyatürüdür. Uygulama her şeyi duyduğunu garanti edemez, bu yüzden duymuş gibi ima etmeyi bırakır. Görebildiğin bir boşluk, etrafından dolaşabileceğin bir boşluktur; göremediğin bir boşluk, olmayı bekleyen bir hata raporudur.
v1.9.0: optimize edip attığın sessizlik sinyaldi
v1.9.0 son v1 sürümüdür, ve backend'in bir dizi canlı-transkript sorumluluğunu istemciye devrettiği yerdir — sessizlik atmak, her transkript satırını yerel bir ses-etkinliği zaman çizelgesinden mikrofon ya da sistem sesine atfetmek, yeniden bağlanma döngüsüne sahip olmak, kayıt sınırlarını dayatmak. Bunun çoğu sürüm-2 mimarisi için temel çalışmadır ve başka yerde anlatıldı. Ama içindeki bir iplik, iki sürümün de en iyi küçük hikâyesidir, çünkü tek başına tamamen doğru olan bir optimizasyonun verdiği hasarı geri almak zorunda kalan bir düzeltmedir.
Bariz biçimde iyi bir değişiklikle başlar: sessizlik göndermeyi bırak. İstemci, baskın kaynağı sessizlik olan parçaları soket üzerinden göndermeden önce filtrelemeye başladı — daha az bant genişliği, backend'in işleyeceği daha az yararsız ses, ve "sessizlik gönderme" diyen açık bir backend istemci-sözleşmesine uyuyordu. Bunu yapmak için her neden sağlamdı.
Ve utterance sonlandırmayı tamamen bozdu. Onu düzelten commit, nedensel zinciri alışılmadık bir açıklıkla ortaya koyar:
After the client began filtering silence chunks … ElevenLabs Scribe's VAD-based commit strategy no longer fires — EL needs ~1s of silence in the audio stream to commit a partial, and that silence never reaches it. Result: partials accumulate forever, UI shows trailing "...", utterances never finalize.
Bu, sızdıran bir soyutlamanın geri ısırmasının platonik biçimidir. Transkripsiyoncunun ses-etkinliği dedektörünün ayrı bir "konuşmacı bitti" girdisi yoktur — bir cümlenin sonunu, seste yaklaşık bir saniye sessizlik duyarak çıkarımlar. Sessizlik hattaki gürültü değildi; bir mesajdı. Bant genişliğinden tasarruf etmek için onu filtreleyerek, istemci kazara transkripsiyoncuya bir cümlenin bittiğini söyleyen o tek sinyali göndermeyi bıraktı. Böylece her kısmi transkript büyümeye devam etti, kuyruğu asla çözülmeyen bir ... içinde sönüp gitti.
Bunu sessizliği geri koyarak düzeltebilirdin — ama o zaman optimizasyonu geri almış olursun. Asıl düzeltme daha iyidir: sessizliği göndermeden sessizliğin anlamını gönder. İstemci artık sesin ne kadar süredir yerel olarak sessiz olduğunu izler, ve bu SILENCE_COMMIT_THRESHOLD_MS = 1000'i aştığında — kasıtlı olarak backend'in kendi VAD sessizlik eşiğiyle eşleştirilmiş — bant-dışı açık bir commit sinyali ateşler: sendMixedChunk sürekli sessizliği fark eder, IPC üzerinden bir realtime-audio:commit gönderir, ana süreç soket üzerinden bir { type: 'commit' } iletir, ve backend ElevenLabs'a kısmi sonucu sonlandırmasını söyler. İki küçük durum parçası, silenceStartedAt ve silenceCommitFired, onun tekrar tekrar ateşlenmesini engeller. Bant genişliği kazanımı hayatta kalır; sonlandırma yeniden çalışır; sondaki ... çözülür.
Bu sürümün küçük, dürüst bir dipnotu da var: v1.9.0'ın tanıttığı sınırsız yeniden bağlanma mantığı ilk olarak renderer'da bir WsReconnectController olarak inşa edildi, sonra gerçek uygulama ait olduğu ana-süreç işleyicisine indiğinde aynı sürüm içinde silindi. Yeniden bağlanma hikâyesinin kendisi — sınırsız geri çekilme, her beş başarısızlıkta değişen sağlayıcılar — kendi bölümünde anlatılır; v1.9.0'ın eklediği şey, o mimarinin doğduğu ve istemcinin ilk kez onun sahipliğini üstlendiği yer olmasıdır.
Bu sürümlerin öğrettiği üç şey
- Kaybı önleyemediğinde, onu kurtarılabilir ve görünür kıl. v1.8.20, yeniden bağlanmaların ses atmasını durdurmaz — bu gerçek-zamanlıda fiziksel olarak mümkün değil. Ham sesin bir kopyasını sonrası için saklar, ve boşluğu transkriptte etiketler. Hiçbiri göz alıcı değil; birlikte sessiz veri kaybını bilinen, sınırlı, kurtarılabilir bir duruma dönüştürürler.
- Bir güvenlik ağının kendi sınırlarına ihtiyacı vardır. Kalıcı tee, kendi haline bırakılırsa diski sessizce doldururdu — ağır çekim bir hataya dönüşen bir düzeltme. Aynı sürümde sevk edilen 7 günlük temizlik sonradan akıl edilmiş bir şey değil; tee'yi sevk etmeyi güvenli kılan şeyin ta kendisi. Biriken bir mekanizma eklersen, onu budayan mekanizmayı da aynı nefeste ekle.
- Verimli olmak için attığın şey bir sinyal olabilir. İki sürümün de en keskin dersi: sessizliği filtrelemek, normalde kontrol edeceğin her eksende doğruydu — bant genişliği, yük, sözleşme uyumu — ve yine de ürünü bozdu, çünkü aşağı akıştaki bir sistem sessizliğe anlam yüklüyordu. Verimlilik için veri kaldırdığında, o veriden bir şey çıkarımlayan neydi diye sor. Bazen anlamı açıkça göndermek zorundasındır, tam da onun eskiden çıkarımlandığı şeyi göndermeyi bıraktığın için.
Ve v1 hikâyesinin sonu bu — yeniden yazımdan önceki son sertleştirme. Önceki bölüm için, Web Audio'yu hayatta tutan üç fiil (v1.8.16 + v2.0.2); tüm bunların nereye gittiği için, bir versiyon 2 gerçekte neye mal olur: 206 commit (v2.0.0); ve tüm yay için, yazılımı kusursuzluğa kadar sevk etmenin anatomisi.