Steven
Steven2 นาทีที่อ่าน

เราลบโค้ดเสียงทิ้ง 5,000 บรรทัด — แล้วบันทึกการถอดเสียงก็เริ่มโผล่ขึ้นมาสองครั้ง

GeekBye v1.6 ถอนตัวถอดเสียงบนเครื่องที่เขียนด้วย Swift สองตัวออก แล้วแทนที่ด้วยไปป์ไลน์เดียวที่รวมเป็นหนึ่ง — ลบสุทธิกว่า 5,000 บรรทัด ทำไปในขณะที่ผู้คนกำลังใช้แอปอยู่กลางการประชุม จากนั้นบันทึกการถอดเสียงก็เริ่มปรากฏขึ้นสองครั้ง เพราะบั๊กย้ายไปยังชั้นเดียวที่ยังคิดว่ามีเอนจินอยู่สองตัว

วิศวกรรม
การถอดเสียง
สถาปัตยกรรม
การปล่อยเวอร์ชัน GeekBye
เราลบโค้ดเสียงทิ้ง 5,000 บรรทัด — แล้วบันทึกการถอดเสียงก็เริ่มโผล่ขึ้นมาสองครั้ง

มีความหวาดกลัวชนิดหนึ่งโดยเฉพาะในการรีแฟกเตอร์โค้ดที่กำลังทำงานอยู่ ตอนนี้ เพื่อคนที่กำลังอยู่ กลางการประชุม คุณไม่สามารถปิดฟีเจอร์เพื่อบำรุงรักษาได้ — มีคนกำลังพึ่งพามันเพื่อถอดเสียงบทสนทนาที่พวกเขาย้อนกลับมาทำใหม่ไม่ได้ GeekBye v1.6 ทำแบบนั้นเป๊ะ ๆ กับเส้นทางที่สำคัญที่สุดในแอป: ไปป์ไลน์เสียง นี่คือเรื่องราวของสิ่งที่เราลบ สิ่งที่พัง และบทเรียนด้านความน่าเชื่อถือสองข้อที่ออกมาจากมัน

สองเอนจิน หนึ่งในนั้นเขียนด้วย Swift

ก่อน v1.6 การแปลงเสียงเป็นข้อความของ GeekBye รันบนเครื่องใน Swift binaries มีอยู่สองตัว: ตัวหนึ่งห่อหุ้มเฟรมเวิร์ก Speech ของ Apple และอีกตัวที่ใหญ่กว่า (กว่า 1,500 บรรทัด) ที่จับเสียงคู่ — ไมค์ของคุณบวกกับเสียงระบบ — ผ่าน ScreenCaptureKit และเปิด WebSocket ของตัวเอง ตรงไปยัง backend ของเราเพื่อสตรีมไปยัง Deepgram ตัวถอดเสียงสองตัว เส้นทางจับเสียงสองเส้นทาง สองวิธีในการคุยกับเครือข่าย ทั้งหมดอยู่ในภาษาที่คอมไพล์แล้ว ซึ่งหมายความว่าทุกการเปลี่ยนแปลงต้องคอมไพล์ไบนารีใหม่

v1.6.0 ยุบทั้งหมดนั้นให้เหลือไปป์ไลน์เดียว คอมมิตเดียวที่รวมทุกอย่างเข้าด้วยกันได้ลบตัวถอดเสียง Apple Speech (~1,145 บรรทัด), ตัวจับเสียง Deepgram ที่เขียนด้วย Swift (~1,523 บรรทัด), บริดจ์ TypeScript ทั้งสอง, ตัวจัดการเดิม และ dual-audio IPC handlers — ลบสุทธิกว่า 5,200 บรรทัด แทนที่ด้วย: renderer จับเสียงผ่าน Web APIs มาตรฐาน ส่งเป็น PCM ผ่าน IPC และ Electron main process เป็นเจ้าของเส้นทาง WebSocket เดียวไปยัง backend โดยเลือกผู้ให้บริการจากคอนฟิกฝั่ง backend เส้นทางจับเสียงเดียว เจ้าของซ็อกเก็ตเดียว จุดเดียวที่ต้องคิดถึงมัน

การลบโค้ดห้าพันบรรทัดรู้สึกดี อยู่ประมาณหนึ่งวัน

บั๊กย้ายไปยังชั้นที่ยังคิดว่ามีอยู่สองตัว

จากนั้นบันทึกการถอดเสียงก็เริ่มโผล่ขึ้นมา สองครั้ง ประโยคที่พูดออกมาประโยคเดียวจะถูกบันทึกเป็นสองรายการที่เหมือนกันเป๊ะ — บางครั้งก็แย่กว่านั้น

ต้นตอของปัญหาคือสิ่งที่ให้บทเรียนมากที่สุดในการปล่อยเวอร์ชันครั้งนี้ทั้งหมด เพราะมันคือกฎทั่วไปของการรวมเป็นหนึ่ง เราได้รวมผู้ให้บริการเข้าด้วยกัน ใน main process — ซ็อกเก็ตเดียว ตัวจัดการเดียว แต่ชั้น React ยังไม่ได้รับข่าว: มันยังคงติดตั้ง event hook เฉพาะผู้ให้บริการสองตัวพร้อมกัน ตัวหนึ่งสร้างมาเพื่อ Deepgram และอีกตัวสำหรับ ElevenLabs ทั้งคู่กำลังฟังอยู่ ทั้งคู่กำลังบันทึกอยู่ ทุกบันทึกการถอดเสียงถูกจัดเก็บโดย hook ตัวใดก็ตามที่ยังมีชีวิต — และทั้งคู่ก็ยังมีชีวิต

และยังมีการซ้ำที่แนบเนียนกว่าซ่อนอยู่ข้างใต้: ElevenLabs ปล่อยทุกบรรทัดที่สรุปเสร็จออกมาสองครั้ง — ครั้งหนึ่งเป็นเหตุการณ์ committed และอีกครั้งเป็น committed_with_timestamps ดังนั้นแม้จะมี hook เดียว ประโยคเดียวก็อาจมาถึงเป็นสองเหตุการณ์ได้

การแก้ไข (ในสาย v1.6) เป็นสองส่วนที่เรียบร้อยและจับคู่พอดีกับสาเหตุทั้งสอง: กั้นแต่ละ event hook ไว้หลังธง enabled ที่ขับด้วยคอนฟิกผู้ให้บริการที่กำลังทำงานของ backend เพื่อให้มี listener เพียงตัวเดียวที่มีชีวิตในแต่ละครั้ง และเพิ่มการตัดข้อความซ้ำแยกตามแหล่ง (จำบรรทัดสุดท้ายที่บันทึกไว้สำหรับไมค์และสำหรับเสียงระบบ) เพื่อกลืนการปล่อยซ้ำของ ElevenLabs

บทเรียนนั้นใหญ่กว่าบั๊กนี้: เมื่อคุณรวมสอง backend ไว้หลังหนึ่งอินเทอร์เฟซ การซ้ำไม่ได้หายไป — มันย้ายไปยังชั้นที่ยังสมมติว่ามีอยู่สองตัว เรายุบท่อร้อยสายใน main process แล้วรอยรั่วก็โผล่ขึ้นมาสองชั้นเหนือขึ้นไป ในการเดินสายเหตุการณ์ของ UI การรวมไปป์ไลน์ยังไม่เสร็จเมื่อผู้ผลิตถูกรวมเป็นหนึ่ง มันเสร็จเมื่อ ผู้บริโภค ทุกตัวหยุดเชื่อในรูปทรงเดิมแล้ว

การเชื่อมต่อใหม่ครั้งแรก: "ยอมแพ้หลังลองห้าครั้ง"

v1.6.0 ยังส่งมอบการเชื่อมต่อ WebSocket อัตโนมัติครั้งแรกสุดของ GeekBye ด้วย exponential backoff — และมันคุ้มค่าที่จะนำมาแสดงเพราะมันคือ บรรพบุรุษดั้งเดิม ของเวอร์ชันกันกระสุนที่เรารันอยู่ทุกวันนี้

ตัวดั้งเดิมมีขีดจำกัด ห้าครั้ง ดีเลย์ 1s, 2s, 4s, 8s, 16s — แล้วมันก็ ยอมแพ้ ปล่อยข้อผิดพลาดร้ายแรงออกมา: "Connection lost — please restart transcription." ในตอนนั้นมันรู้สึกว่ามีความรับผิดชอบ: อย่าลองซ้ำตลอดกาล บอกผู้ใช้อย่างตรงไปตรงมา ในทางปฏิบัติมันคือบั๊ก UX ที่ปลอมตัวมา ช่วงเน็ตสะดุดสิบหกวินาที — การสลับ Wi-Fi, การเชื่อมต่อ VPN ใหม่, อุโมงค์ — เป็นเรื่อง ชั่วคราว แต่การลองซ้ำแบบมีขีดจำกัดมองมันเป็นเรื่อง ถาวร ผู้ใช้ไม่ได้ทำอะไรผิดเลยแต่กลับถูกบอกให้เริ่มการประชุมที่กำลังดำเนินอยู่ใหม่

นั่นคือความล้มเหลวเป๊ะ ๆ ที่การออกแบบปัจจุบันกำจัดทิ้งไป ทุกวันนี้ GeekBye เชื่อมต่อใหม่ด้วย backoff แบบ ไม่มีขีดจำกัด และยังหมุนสลับผู้ให้บริการ และจะหยุดก็ต่อเมื่อมีเหตุผลร้ายแรงจริง ๆ เท่านั้น — auth, quota, billing เส้นทางทั้งหมดจาก "ยอมแพ้หลังห้าครั้ง" ไปสู่ "ไม่มีวันยอมแพ้กับข้อผิดพลาดชั่วคราว" ถูกเล่าไว้ในทำไมแอปจดบันทึก AI ของคุณถึงหยุดเมื่อ Wi-Fi แย่ v1.6 คือจุดที่ถนนสายนั้นเริ่มต้น ด้วยเวอร์ชันแบบเรียบง่ายที่ต้องมีอยู่ก่อน

การเชื่อมต่อที่เก่าค้างจะหลอกหลอนคุณ เว้นแต่มันจะมีชื่อ

บทเรียนด้านความน่าเชื่อถืออีกหนึ่งข้อลงจอดใน v1.6.3 แก้บั๊กประเภทที่แนบเนียนที่ใครก็ตามที่ทำการเชื่อมต่อใหม่บน backend ที่มีสถานะจะได้พบสักวันหนึ่ง

เมื่อคุณเชื่อมต่อใหม่ — หรือสลับภาษาการถอดเสียงกลางเซสชัน ซึ่งเชื่อมต่อใหม่อยู่เบื้องหลัง — การเชื่อมต่อ เก่า ไม่ได้ตายอย่างเงียบ ๆ เสมอไป ข้อความลมหายใจเฮือกสุดท้ายของมัน (CONNECTION_LOST, disconnected) อาจมาถึง หลังจาก ที่การเชื่อมต่อใหม่แข็งแรงแล้ว และรื้อตัวแทนที่ดีสมบูรณ์ทิ้ง การแก้ไขให้ทุกความพยายามเชื่อมต่อมี อัตลักษณ์ — session ID ต่อการเชื่อมต่อที่ประทับไว้บน WebSocket URL — บวกกับ ช่วงเวลาผ่อนผัน สั้น ๆ หลังเชื่อมต่อใหม่ ที่ในระหว่างนั้นข้อความตัดการเชื่อมต่อที่เก่าค้างจากความพยายามก่อนหน้าจะถูกเพิกเฉย มันยังส่งข้อความ stop อย่างชัดแจ้งก่อนปิดซ็อกเก็ต เพื่อให้ backend รื้อเซสชันเก่าลงได้อย่างสะอาด

หลักการ: ตรรกะการเชื่อมต่อใหม่ใด ๆ บน backend ที่มีสถานะต้องติดป้ายอัตลักษณ์ให้แต่ละความพยายาม และมองข้อความที่มาช้าจากความพยายามก่อนหน้าเป็นสัญญาณรบกวน — มิฉะนั้นการเชื่อมต่อใหม่ที่สำเร็จจะถูกฆ่าด้วยลมหายใจเฮือกสุดท้ายของรุ่นก่อน

สามสิ่งที่ v1.6 สอน

  1. การรวมเป็นหนึ่งย้ายที่บั๊ก มันไม่ได้ลบบั๊กทิ้ง ยุบสองเอนจินให้เป็นหนึ่ง แล้วตรวจสอบผู้บริโภคทุกตัวที่ยังคิดว่ามีอยู่สองตัว ของเราคือ UI ที่ติดตั้ง event hook สองตัวหลังจากที่ท่อร้อยสายได้รวมเข้าด้วยกันแล้ว
  2. การเชื่อมต่อใหม่ครั้งแรกของคุณจะมีขีดจำกัด และการมีขีดจำกัดนั้นผิดสำหรับสตรีมสด "ยอมแพ้หลังลอง N ครั้ง" เปลี่ยนช่วงเน็ตสะดุดชั่วคราวให้กลายเป็นความล้มเหลวถาวรที่ผู้ใช้ถูกกล่าวโทษ จงแยกความล้มเหลวร้ายแรง (auth, quota) ออกจากชั่วคราว (ซ็อกเก็ตหลุด) ตั้งแต่เวอร์ชันแรกสุด
  3. การเชื่อมต่อใหม่ต้องการอัตลักษณ์ ติดป้ายให้แต่ละความพยายาม เพิกเฉยต่อข้อความที่มาช้าจากความพยายามที่ตายไปแล้ว การเชื่อมต่อที่ไม่มีชื่ออาจถูกฆาตกรรมด้วยผีของตัวเอง

นี่คือบทที่สองของเรื่องราวด้านความน่าเชื่อถือที่กลายมาเป็น GeekBye v2 สำหรับบทแรก ดูมหากาพย์การอัปเดตอัตโนมัติที่กินหกการปล่อยเวอร์ชันในสี่วัน (v1.5.x); สำหรับจุดที่งานเชื่อมต่อใหม่ไปถึงในที่สุด ทำไมแอปจดบันทึก AI ของคุณถึงหยุดเมื่อ Wi-Fi แย่; และสำหรับเส้นเรื่องทั้งหมด กายวิภาคของการส่งมอบซอฟต์แวร์ให้สมบูรณ์แบบ

บทความที่เกี่ยวข้อง

ตัวแปร CSS หนึ่งตัว, การรีวิวห้ารอบ, และ Swift Toolchain ที่โกหก
Steven
Steven2 นาทีที่อ่าน

ตัวแปร CSS หนึ่งตัว, การรีวิวห้ารอบ, และ Swift Toolchain ที่โกหก

GeekBye v2.0.7 ทำให้ทั้งโอเวอร์เลย์โปร่งแสงปรับได้ — ซึ่งฟังดูเหมือนเป็นการแก้ CSS แค่บรรทัดเดียว แต่มันไม่ใช่แน่นอน เรื่องจริงอยู่ที่สิ่งที่การรีวิวโค้ดจับได้: สองพื้นผิวที่ไม่ยอมจางลง, แถบบันทึกที่ดูผิดเพี้ยนทั้งที่ค่า opacity เท่ากัน, และไบนารีที่คอมไพล์แล้วซึ่งกลับไปกลับมา 672 ไบต์ เพราะเอกสารของเราเองบอกเวอร์ชัน Swift ที่ผิดให้ผู้รีวิว

วิศวกรรม
การออกแบบ
การบิลด์
เวอร์ชัน 2 จริง ๆ แล้วต้องแลกด้วยอะไร: 206 คอมมิตแห่งสถานะที่ซื่อสัตย์
Steven
Steven2 นาทีที่อ่าน

เวอร์ชัน 2 จริง ๆ แล้วต้องแลกด้วยอะไร: 206 คอมมิตแห่งสถานะที่ซื่อสัตย์

GeekBye v2 ไม่ใช่การปล่อยฟีเจอร์ มันคือ 206 คอมมิตที่เล็งไปยังความคิดเดียว: แอปไม่ควรโกหกเกี่ยวกับสถานะของตัวเองเลย นี่คือราคาที่ต้องจ่าย — รวมถึงความผิดพลาดในไฟล์ล็อกเพียงบรรทัดเดียวที่เกือบทำให้เราส่งอะไรออกไปไม่ได้เลย

ความน่าเชื่อถือ
วิศวกรรม
การปล่อยเวอร์ชัน
ทำไม AI ถอดเสียงถึงฟังศัพท์เทคนิคผิด (และเราแก้มันอย่างไร)
Steven
Steven3 นาทีที่อ่าน

ทำไม AI ถอดเสียงถึงฟังศัพท์เทคนิคผิด (และเราแก้มันอย่างไร)

เซสชันจริงฟังประโยค "what is the pointer in C++" เป็น "what is the point in life" นี่คือเส้นทางการสืบสวนจากทรานสคริปต์นั้นไปจนถึง GeekBye v2.0.11 — keyterm biasing, race condition ที่ทำให้การเชื่อมต่อหลุด และวันที่การแก้ไขของเราเองย้อนกลับมาเล่นงานเรา

การถอดเสียง
ความเสถียร
วิศวกรรม