Steven
Steven1 分钟阅读

为什么你的 AI 笔记助手会在会议中途停止录音

我们自己的应用在对方话说到一半时结束了我们的两场会议。取证的线索指向一个出发点良好、却只听得见你自己的空闲计时器 — 以及第二个可能锁死你整个桌面的 bug。两者都已在 GeekBye v2.0.9 中修复。

可靠性
会议
工程
GeekBye 发布
为什么你的 AI 笔记助手会在会议中途停止录音

7月2日,GeekBye 自己结束了一场会议录音。数据库里那一行记录说明了一切:ended_reason = 'idle',时长 519 秒,99 条转写记录 — 最后一条写入的时间,距离应用判定"没人在场"只有 2 秒。

当时另一位参会者正讲到一半。转写稿的最后一行就是一个不折不扣的半句话:"...executes it or turns it on or so—"

这不是第一次。前一天晚上,另一个会话以同样的方式结束。两场会议,被我们自己的可靠性功能杀掉了。下面是诊断过程,以及随 GeekBye v2.0.9 发布的修复 — 外加同一版本里修掉的另一个更吓人的 bug。

一个出发点良好、却只听得见你的计时器

空闲自动关闭的存在有充分的理由。有人会忘记录音开了一整夜;一个没关的会议标签页会永远往里滴音频。所以 GeekBye 会监测不活跃状态:60 秒没有人声活动后,它会弹出一个小小的 "Still recording?"(还在录音吗?)提示,再过 30 秒无人应答,它就结束会话 — 礼貌地保存好所有内容。

缺陷出在一个词上:人声。活动时钟只统计持续的麦克风能量。这是一个深思熟虑的设计决定,而且并不蠢 — 如果统计原始系统音频的能量,一个静音但吵闹的标签页就能让一个死会话无限续命,而这恰恰是这个功能要防止的失败。你以听为主的会议,本应由会议窗口检测来兜底。

问题是,会议检测看不见所有会议。浏览器标签页、少见的客户端、你正在观看的演示 — 都检测不到。而在一场未被检测到的会议里,你听了 90 秒 — 别人给你讲解他们的 Databricks 流水线时,这再正常不过 — 在空闲时钟眼里,你和一个空房间没有任何区别。

看看那场被杀掉的会话的时间线:来自我们麦克风的最后一条转写,出现在结束前 68 秒。接下来是对方说话的 60 秒(转写得完美无缺,却被时钟无视)、那个没人注意到的提示、30 秒的倒计时,然后是那一刀 — 在对方最后一句话之后 2 秒。

修复:转写就是活着的证据

事后看,这个纠正简单得几乎让人不好意思:一条到达的转写,是"会话没有空闲"最有力的证据。 谁说的话根本不重要。语音模型刚刚识别出了词句 — 那就是会议本身。

于是 v2.0.9 在每一条到达的转写上都给活动时钟盖章,不管它来自哪一方。原始系统音频的能量依然不算数 — 音乐、等待音和空调的嗡嗡声依然无法让一个死会话永生,录音时长硬上限也依然兜底。只有被识别出的语音能让会话保持存活,这正是恰到好处的边界。

代码评审里有一个值得转述的细节:第一版修复把盖章放在了说话人归属的路径里 — 而有一部分转写可以合法地跳过这条路径。评审发现,未来的某次改动可能悄悄让这个 bug 复活,而且恰恰复活在最要紧的那些转写上(对方说的话)。现在盖章是无条件的,先于任何分支执行,并有一个测试守着 — 谁把它挪走,测试就失败。

同一个版本修掉了更吓人的东西

在测试这些修复时,我们以最惨痛的方式撞上了另一个 bug:界面进程的一次崩溃,让整个桌面都点不动了

GeekBye 的悬浮层是一个覆盖整个屏幕的透明置顶窗口。它默认是点击穿透的;当你在使用某个面板时,界面会把它切换成可交互。这些切换指令来自界面进程 — 所以当那个进程在面板打开时崩溃,这个看不见的窗口就停在了可交互模式,背后却没有任何活着的 UI。你在桌面上的每一次点击,都落在一块死掉的、看不见的玻璃上。唯一的逃生路线是强制退出应用。

v2.0.9 的崩溃处理器现在会立即恢复点击穿透并重新加载界面 — 并设了每分钟最多三次重载的上限,防止崩溃循环无限空转(超过上限后,应用会放弃重载,但你的桌面保持可用,这才是要紧的部分)。代码评审同样磨利了这个修复:恢复被限定在悬浮层窗口本身,因为如果不分青红皂白地给一个崩溃的普通窗口 — 比如更新对话框 — 套上点击穿透,就会制造出方向相反的锁死。

你可以亲手用最粗暴的方式验证这个修复:打开一个 GeekBye 面板,在活动监视器里强制结束 "GeekBye Helper (Renderer)" 进程,然后看着应用在一秒之内把你的桌面还给你。

这一对 bug 教会了我们什么

  1. 每一种"用户还在吗?"的代理指标都会在某处失效。 麦克风能量对听众失效。窗口检测对浏览器失效。被识别出的转写对……我们至今还没找到失效场景,因为它不是代理指标 — 它就是产品本身。
  2. 任何由渲染进程驱动的东西,都需要一套崩溃预案。 如果一个死掉的 UI 进程可能留下操作系统层面的状态(鼠标捕获、窗口置顶、内容保护),主进程就必须负责把它们复位。
  3. 做自己最重度的用户,是一种找 bug 的策略。 两个 bug 都在真实会议中先砸到了我们头上,那时还没有几个客户注意到。我们几个月前为可观测性加上的 ended_reason 列,让这次诊断变成了一条数据库查询,而不是一次猜测。

两个修复都在一天之内从诊断走到了签名公证的正式发布,各自由一个经过评审、带回归测试的 PR 承载。如果你在用 GeekBye v2,自动更新从 v2.0.9 起就已经把它们送到了你手上。

想了解这次发布的其余故事,请看同系列的为什么 AI 转写总是听错技术术语(v2.0.11)、为什么你的 AI 笔记助手在糟糕的 Wi-Fi 下会停摆里的可靠性基础工作,以及悬浮层如何在屏幕共享时保持隐身的同时不抢走你的点击。