Steven
Steven1 分で読める

音声コードを5,000行削除したら — 文字起こしが二重に出始めた

GeekBye v1.6は、オンデバイスの二つのSwift文字起こしエンジンを引き剥がし、一つの統一パイプラインに置き換えました — 差し引き5,000行超の削除を、人々がアプリで会議の真っ最中に、です。すると文字起こしが二重に現れ始めました。バグが、まだエンジンは二つあると思い込んでいた唯一の層へ移動したからです。

エンジニアリング
文字起こし
アーキテクチャ
GeekByeリリース
音声コードを5,000行削除したら — 文字起こしが二重に出始めた

今まさに稼働中の会議の真っ最中の人々のためのコードをリファクタリングすることには、独特の恐怖があります。メンテナンスのために機能を止めることはできません — 誰かが、二度と繰り返せない会話を文字起こしするのに、それを頼りにしているのですから。GeekBye v1.6は、アプリで最も重要な経路——音声パイプライン——に対して、まさにそれをやりました。これは、私たちが何を削除し、何が壊れ、そこから出てきた二つの信頼性の教訓の物語です。

二つのエンジン、うち一つはSwift製

v1.6以前、GeekByeの音声認識はオンデバイスのSwiftバイナリで動いていました。それは二つありました: 一つはAppleのSpeechフレームワークを包むもの、もう一つはより大きな(1,500行超の)もので、ScreenCaptureKit経由でデュアル音声——あなたのマイクとシステム音声——を取り込み、自前の WebSocketを私たちのバックエンドへ直接開いてDeepgramへストリーミングしていました。二つの文字起こしエンジン、二つの取り込み経路、ネットワークと話す二通りの方法、しかもすべてがコンパイル言語の中——つまり、あらゆる変更にバイナリの再コンパイルが必要でした。

v1.6.0は、そのすべてを一つのパイプラインに畳み込みました。統一の中核となる単一のコミットが、Apple Speech文字起こしエンジン(約1,145行)、Deepgram Swift取り込みエンジン(約1,523行)、両方のTypeScriptブリッジ、旧オーケストレーター、そしてデュアル音声のIPCハンドラを削除しました——差し引き5,200行超の削除です。 それらの代わりに: レンダラーが標準のWeb APIsで音声を取り込み、PCMとしてIPC越しに送り、Electronのメインプロセスがバックエンドへの単一のWebSocket経路を所有し、プロバイダはバックエンド設定で選ばれます。一つの取り込み経路、一つのソケット所有者、推論すべき場所は一つ。

五千行を削除するのは最高の気分です。一日ほどは。

バグは、まだ二つあると思い込んでいた層へ移動した

すると文字起こしが二重に現れ始めました。一つ話した文が、二つの同一エントリとして保存される——時にはもっとひどく。

根本原因は、このリリース全体で最も示唆に富むものです。なぜなら、それは統一の一般法則だからです。私たちはプロバイダをメインプロセスで統一していました——一つのソケット、一つのハンドラ。しかしReactの層は連絡を受け取っていませんでした: それは依然として二つのプロバイダ固有のイベントフックを同時にマウントしていたのです。一つはDeepgram用、もう一つはElevenLabs用。両方が聞いていました。両方が保存していました。すべての文字起こしは、生きているフックのどれによっても永続化された——そして両方とも生きていたのです。

そしてその下に、もっと狡猾な二重化がありました: ElevenLabsはすべての確定行を二度発火する——一度はcommittedイベントとして、もう一度はcommitted_with_timestampsとして。だから単一のフックでさえ、一つの文が二つのイベントとして届き得たのです。

修正(v1.6ライン内)は、二つの原因にぴったり対応する、きれいな二部構成です: 各イベントフックを、バックエンドのアクティブプロバイダ設定で駆動されるenabledフラグの背後でゲートし、一度に生きているリスナーが一つだけになるようにする。そしてソースごとのテキスト重複排除を加える(マイクとシステム音声それぞれについて最後に保存した行を覚えておく)ことで、ElevenLabsの二重発火を飲み込む。

教訓はこのバグより大きい: 二つのバックエンドを一つのインターフェースの背後に統一しても、二重化は消えない——まだ二つあると思い込んでいる層へ移動するだけだ。 私たちはメインプロセスで配管を畳み込みましたが、漏れは二層上、UIのイベント配線に現れました。パイプラインの統一は、生産者が統一されたときに終わるのではありません。すべての消費者が古い形を信じるのをやめたときに終わるのです。

最初の再接続: 「五回試したら諦める」

v1.6.0はまた、指数バックオフ付きのGeekBye初のWebSocket自動再接続も出荷しました——そしてこれは、まさに私たちが今日運用している鉄壁版の原始的な祖先だからこそ、見せる価値があります。

オリジナルは上限付きでした。五回の試行、1s、2s、4s、8s、16sの遅延——そして降参し、致命的なエラーを発しました: 「接続が失われました — 文字起こしを再起動してください。」 当時はそれが責任ある態度に感じられました: 永遠にリトライせず、ユーザーに正直に伝える。しかし実際には、それは変装したUXバグでした。十六秒のネットワークの瞬断——Wi-Fiのハンドオフ、VPNの再接続、トンネル——は一時的なのに、上限付きリトライはそれを終端的なものとして扱います。ユーザーは何も間違っていないのに、稼働中の会議を再起動しろと告げられたのです。

それこそ、現在の設計が排除したまさにその失敗です。今日のGeekByeは上限なしのバックオフで再接続し、プロバイダの切り替えまで行い、本当に致命的な理由——認証、クォータ、課金——のときにだけ停止します。「五回で諦める」から「一時的なエラーでは決して諦めない」までの旅の全容は、AIノートテイカーはなぜ悪いWi-Fiで止まるのかで語られています。v1.6は、その道が始まった場所です——最初に存在しなければならなかった、あの素朴な版とともに。

名前を持たない限り、古い接続はあなたに取り憑く

もう一つの信頼性の教訓がv1.6.3で着地し、ステートフルなバックエンド越しの再接続をやる誰もがいずれ出会う、微妙な種類のバグを直しました。

再接続するとき——あるいはセッションの途中で文字起こし言語を切り替えるとき、これは裏で再接続します——古い接続は必ずしも静かに死んでくれません。その断末魔のメッセージ(CONNECTION_LOSTdisconnected)は、新しい接続がすでに健全になった後で届き、完全に良好な後継を取り壊し得るのです。修正は、すべての接続試行にアイデンティティを与えます——WebSocketのURLに刻印される接続ごとのセッションID——加えて、再接続後の短い猶予期間を設け、その間は前の試行からの古い切断メッセージを無視します。さらに、ソケットを閉じる前に明示的なstopメッセージを送り、バックエンドが古いセッションをきれいに取り壊せるようにします。

原則はこうです: ステートフルなバックエンド越しのあらゆる再接続ロジックは、各試行にアイデンティティのタグを付け、前の試行からの遅れて届くメッセージをノイズとして扱う必要がある——さもないと、成功した再接続が、その前任者の断末魔に殺されてしまいます。

v1.6が教えた三つのこと

  1. 統一はバグを再配置する。削除はしない。 二つのエンジンを一つに畳み込み、まだ二つあると思い込んでいるすべての消費者を監査しましょう。私たちの場合はUIで、配管がすでにマージされた後も二つのイベントフックをマウントしていました。
  2. 最初の再接続は上限付きになり、そして上限付きはライブストリームには間違いだ。 「N回試したら諦める」は、一時的な瞬断を、ユーザーが責められる終端的な失敗に変えてしまいます。致命的(認証、クォータ)と一時的(落ちたソケット)を、まさに最初の版から区別しましょう。
  3. 再接続にはアイデンティティが要る。 各試行にタグを付け、死んだ試行からの遅れたメッセージを無視しましょう。名前のない接続は、自分自身の亡霊に殺され得るのです。

これは、GeekBye v2になる信頼性物語の第二章です。第一章は、アプリを不死身にした、四日で六リリースの自動アップデート奮闘記(v1.5.x)を。再接続の仕事が最終的にたどり着いた先は、AIノートテイカーはなぜ悪いWi-Fiで止まるのかを。そして全体の弧については、ソフトウェアを完璧に出荷することの解剖学をご覧ください。

関連記事

ファイアウォールがWebSocketを塞ぐとき、リアルタイム文字起こしはどうなるか
Steven
Steven1 分で読める

ファイアウォールがWebSocketを塞ぐとき、リアルタイム文字起こしはどうなるか

企業ネットワークはHTTPSは通すくせに、WebSocketのアップグレードをこっそり殺すのが大好きです。それがリアルタイム文字起こしを黙って壊します。GeekBye v2.0.8は自動でピュアHTTPSトランスポートへフォールバックする — そしてそれを出荷する過程で、機能全体を無意味にしかねないバグが見つかりました。

文字起こし
ネットワーク
エンジニアリング
AI文字起こしはなぜ専門用語を聞き間違えるのか(そして私たちはどう直したか)
Steven
Steven1 分で読める

AI文字起こしはなぜ専門用語を聞き間違えるのか(そして私たちはどう直したか)

ライブセッションで「what is the pointer in C++」が「what is the point in life」と聞き取られました。そのトランスクリプトからGeekBye v2.0.11に至るまでの調査記録 — keytermバイアス、接続を切断していたタイミング競合、そして自分たちの修正が裏目に出た日の話です。

文字起こし
信頼性
エンジニアリング
接続が切れただけでアプリ全体が落ちてはいけない — でも私たちのは落ちた
Steven
Steven1 分で読める

接続が切れただけでアプリ全体が落ちてはいけない — でも私たちのは落ちた

バックエンドが会議の途中でオフラインになったとき、それは文字起こしを止めただけではありませんでした — アプリ全体をクラッシュさせたのです。原因はたった一つの処理されないイベント、修正は十行でした。これは、GeekByeがかつて自分を殺していた事態を通り抜けても、サインインを保ち、接続を保つようにしたリリース群の物語です。

エンジニアリング
信頼性
Electron