fix(asr): prepare barge-in VAD without blocking playback - #40
Conversation
回应 PR Code-Amadeus#16 第 3 轮评审两点: 1. L2(不装 silero-vad/torch)+ barge-in 配置开启时,_start_barge_in_detector 只看配置开关,每个播放句都会拉起检测线程,线程内惰性导入失败 → 反复报 [BargeIn] detector failed / barge_in_error。现在启动边界判定 消费 ASRManager 公开的 vad_status() 三态:仅 ready 放行;fallback/ degraded/manager 未创建一律拒绝启动,禁用事实记一次性日志,持续状态 仍由 runtime_status 公开,degraded 的 reason 不伪装成缺席。 2. tests/test_speculative_turn.py::test_manager_on_result_callback 移除 pytest.importorskip("torch"):被测 _SpeculativeTranscription 仅依赖 backend/threading/numpy,model-less CI 必须真实执行该路径。 新增 tests/test_barge_in_capability_gate.py:subprocess + sys.meta_path 屏蔽 L3 模块模拟 L2 环境,覆盖 fallback/degraded/manager 缺席/ready/ config off 五种组合,探针自验证 mask 真实性。
Lucas1479
left a comment
There was a problem hiding this comment.
当前 CI 与新增测试均通过,移除 test_manager_on_result_callback 的 torch 假跳过也是正确的。不过新的 capability gate 会回归正常的 L3/L4 首次打断路径,因此暂时不能批准。
阻塞项:不要把惰性 ASRManager 实例的存在当作 barge-in 能力事实。
server.app 中 asr_manager 的进程初值是 None;它只在 AsrHandler 真正开始监听时经 _get_or_create_asr_manager() 创建,并会在空闲卸载时重新清空。BargeInDetector 原本独立拥有自己的 Silero VAD 模型并在首次播放时加载,因此“用户先键盘聊天、角色开始播放、用户第一次插话”是合法的 L3/L4 路径,不要求 ASRManager 事先存在。
当前 _start_barge_in_detector() 把 asr_manager 传入 _barge_in_start_decision(),而该函数对 manager=None 一律返回 False。实测在完整 cu124 环境中 silero_vad=True、torch=True,但全新导入 server.app 后仍得到:
manager None
decision (False, 'asr manager not initialized (no voice capability observed)')
这会让首次键盘会话以及 ASR 空闲卸载后的播放都无法启动 barge-in。新增测试反而把 manager=None -> stay off 冻结成合同,所以没有覆盖这一回归。
请把“L3 依赖缺席/损坏”的一次性判定与缓存放回真正拥有检测线程和 VAD 加载上下文的 BargeInDetector(或另一个已有的能力事实所有者),不要从 ASRManager 的惰性生命周期推断安装能力。最小回归测试应覆盖:配置开启、完整 VAD 依赖可用、ASRManager 尚未创建时,第一次播放仍能启动 detector;缺依赖时只失败/记录一次且后续不反复拉起。无需新增公开状态或额外能力体系。
本地相关套件 16 passed,ruff 通过;上述是现有测试未覆盖的支持旅程回归。
回应第三轮评审:第一版把 asr_manager 的存在当作能力事实,但该 manager 惰性创建(app.py:1232)、空闲卸载时清空(app.py:1260),而 BargeInDetector 自持 VAD 模型(_ensure_vad 独立 load_silero_vad)。 键盘聊天后首次播放、ASR 空闲卸载后的播放是合法 L3/L4 旅程, 不应被 manager=None 一刀切拒绝。 - 能力探测与缓存移回 BargeInDetector.vad_status():惰性(构造函数 零 import,L1/L2 bootstrap 可导入)、单实例只探测一次;缺 silero_vad → fallback;已装但依赖缺失/加载失败 → degraded 保留 reason(ModuleNotFoundError.name 区分),不伪装成缺席 - 探测成功时缓存 _vad_model,detector 线程 _ensure_vad 复用不二次加载 - _barge_in_start_decision 删除 manager 消费,只做配置开关 + 委托 detector 判定;确认不可用后读缓存,不反复拉线程、不反复报错 - 测试删除 manager=None 冻结断言,新增评审核心回归场景: ready + asr_manager is None → 放行(键盘聊天首播旅程)
|
第三轮评审的回归已修复(commit 能力事实归位:
评审核心场景的回归测试:ready 能力 + 权衡说明:ready 路径的首次探测在事件循环线程同步 已知后续项(非阻塞):import 期缺传递依赖( |
Lucas1479
left a comment
There was a problem hiding this comment.
感谢跟进修复!已复核 7b13368:上一轮把 ASRManager 生命周期当作能力事实的问题已解决,8 项定向测试和 Windows CI 均通过。
这轮还有一处需要调整:[P2] 首次 VAD 探测会同步阻塞播放事件循环。server/app.py 的 _on_sentence_start() 同步调用 gate,继而在 asr/barge_in_detector.py:121–139 执行首次 import 和模型加载;流式播放在该回调返回后才开始写音频。本机独立进程实测 gate 耗时 2.424 秒,预设的 10ms 事件循环回调也直到 2.424 秒才执行。具体时长因机器而异,但首句及同期中断/WebSocket 请求被同步阻塞这一点已确认。
请保留 BargeInDetector 对能力与缓存的所有权,将首次 import/模型加载移到后台准备路径,避免在同步播放回调里执行冷加载;保留缺依赖只记录一次的降级行为,以及 ASRManager 尚未创建时的正常插话路径。补一个受控慢加载期间事件循环仍能处理请求的回归测试即可。
|
已由维护方接手补齐上一轮剩余修改,直接推到本 PR( The maintainer has completed the remaining review fix directly on this PR ( |
Lucas1479
left a comment
There was a problem hiding this comment.
上一轮阻塞项已由维护方在 43f1416 修复:首次 VAD 导入/加载移到 detector 自有后台准备线程;停止会取消待启动监听,后续句子可复用准备结果;L2 缺依赖及损坏状态只探测和记录一次。保留独立于 ASRManager 的首次插话路径,并移除投机转写测试的不相关 Torch 跳过。37 项定向测试、真实 Silero 冷加载探针、全仓 Ruff/架构检查及最新远程 CI 均通过。没有剩余阻塞意见。
The previous blocking finding is fixed by the maintainer in 43f1416. Initial VAD imports/loading now run in a detector-owned background preparation thread; stop cancels a pending listener start, and later sentences reuse the preparation. Missing or broken VAD is probed and logged once. Keyboard-first barge-in remains independent of ASRManager, and the unrelated Torch skip is removed from the speculative-transcription test. All 37 focused tests, the real Silero cold-load probe, repository lint/architecture checks, and the updated remote CI passed. No blocking findings remain.
L2 remote-voice installs without Silero VAD no longer start a failing barge-in listener on every playback sentence.
BargeInDetectorprepares and caches its own VAD once in a background thread, so cold imports/model loading cannot block the playback callback or event loop. It distinguishes missing VAD from broken dependencies/model loading and logs the unavailable result once.A stop received during preparation cancels the pending listener start; a later sentence can reuse the same preparation/model. Keyboard-first conversations and playback after ASR idle unload continue to work without an
ASRManagerinstance. The synchronous capability gate inserver/app.pyis removed; the existing configuration gate stays in place. The speculative-transcription callback test now executes in model-less CI without the unrelated Torch skip.Validation: 37 focused tests passed, including slow-load event-loop responsiveness, stop/restart during preparation, missing/transitive/broken dependencies, model reuse, and synthetic first-playback interruption. Full-repository Ruff and all seven architecture views passed. A real Silero cold-load probe in the existing Torch 2.5.1+cu124 environment took 2.223s in the background;
start()returned in 0.228ms, the loop callback ran at 0.335ms, and stopping during preparation did not launch the listener. This probe did not capture microphone audio. All three remote checks passed for43f1416.中文摘要
L2 远程语音环境缺少 Silero VAD 时,不再为每个播放句启动必然报错的插话监听线程。
BargeInDetector在后台完成一次性 VAD 探测和加载,并缓存缺席或损坏原因;播放回调和事件循环不再承担冷加载。准备期间收到停止会取消待启动监听,之后的新句子可以复用准备结果与模型。键盘聊天首句、ASR 空闲卸载后的播放仍可插话,不依赖
ASRManager实例。移除server/app.py中的同步能力探测,保留原有配置开关;投机转写回调测试去除不相关的 Torch 跳过,在无模型 CI 中真实执行。维护方已同步最新 main 并补齐修复。37 项定向测试、全仓 Ruff 和 7 份架构视图检查通过;真实 Silero 冷加载约 2.223 秒在后台完成,启动调用 0.228ms、事件循环回调 0.335ms,准备期间停止后未启动监听。实测未采集麦克风音频。
43f1416的三项远程检查已全部通过。Closes #41
Closes #42