[控场AI]
· 7 分钟阅读· 3,946 字

语音Agent通话中突然静默?排查6个管线Bug

语音Agent通话中突然静默?排查6个管线Bug

语音Agent静默源于六个管线Bug和服务商卡顿,而非模型变笨。

一位开发者排查语音Agent通话中突然不响应的问题,历时一周,发现六个管线层面的Bug和一个服务商卡顿问题,没有一个是模型本身的问题。共同模式是某个管线组件对“通话是否正在进行”给出自信但错误的答案,而代码选择相信它。问题包括:watcher因硬编码路径失效在来电时崩溃;辅助功能树检测盲区导致看门狗误判并关闭活跃会话;挂断信号方向反了;音频设备打开时死锁;卡住的循环自我维持故障;macOS在屏幕共享时抑制来电通知;服务商拥堵造成四分钟静默。修复方案遵循失败开放、单一持有者、进程存活不等于工作正常三条规则,并辅以每五分钟运行的自愈程序。

语音Agent通话静默排查

一位开发者遇到语音Agent在通话中突然不响应的问题,用户反馈“我听到了你,但你没有回应”。经过一周排查,他发现了六个管线层面的Bug和一个服务商卡顿问题——没有一个是模型本身变笨。每个问题的共同模式是:某个管线组件对“通话是否正在进行”给出了自信但错误的答案,而代码选择了相信它。

检测与监控的双重失效

监听进程在通话接通时恰好死亡

监听进程(watcher)通过硬编码的.venv解释器路径启动语音循环,但目录早已被重命名为venv。旧的路径失效导致Popen抛出FileNotFoundError,而异常发生在watcher内部,直接杀死了watcher——恰好在来电的那一刻。电话接通了,但没有进程被启动,也没有日志留下解释原因。

修复方式刻意保持无聊:从候选列表中解析解释器路径,逐个用导入测试探测,回退到sys.executable,并包装spawn过程以记录失败。负责监视失败的进程,不应该自己先死掉。

Popen 是 Python 标准库 subprocess 模块中用于启动子进程的类。它接受一个参数列表,其中第一个元素通常是可执行文件的路径,后续元素是传递给该程序的命令行参数。如果指定的路径不存在或不可执行,Popen 会在构造时抛出 FileNotFoundError 或 PermissionError。在本文场景中,watcher 进程通过 Popen 启动语音循环子进程,而它硬编码的解释器路径指向一个已被重命名的目录,因此每次尝试启动都会失败。关键在于,这个异常发生在 watcher 自身的代码路径中,且没有被捕获,导致 watcher 进程本身崩溃,而不是仅仅记录一次启动失败并继续运行。

检测盲区与看门狗的误判

这台Mac没有摄像头,FaceTime的通话中UI绘制在辅助功能树无法触及的位置:AX节点为零,窗口本身只显示“连接摄像头”。在整整448秒的通话中,辅助探针始终返回idle。看门狗读到idle,判定通话结束,在约14秒时关闭了一个活跃会话。

替代信号来自音频侧:通话期间循环持有两个虚拟音频设备,两者都读取到IsRunningSomewhere = True,通话结束则为False。看门狗规则随之改变:明确的ended立即关闭,连续四次异常读数才挂断,不确定的读数则保持会话存活。

更深一层还有同类问题:watcher过去在单次unknown时就会关闭,而检测在历史数据库复制竞争时会短暂闪烁为unknown,一次闪烁就能杀死通话。现在要求连续六次unknown读数(约三秒)才触发。群组通话也需要独立路径,因为它们从不发出ringing,直接进入connected。

IsRunningSomewhere 是 macOS Core Audio 框架中 AudioDevice 对象的一个属性,用于指示某个音频设备是否被系统中任何进程以任何方式使用(包括输入或输出)。它返回布尔值:True 表示设备当前至少被一个进程占用,False 表示完全空闲。与逐个检查进程的音频流不同,这个属性从系统层面汇总设备的使用状态,因此不依赖于辅助功能树或 UI 可见性。在本文中,通话期间循环持有两个虚拟音频设备,IsRunningSomewhere 稳定为 True,通话结束后变为 False,从而成为比辅助探针更可靠的活跃通话信号。

挂断信号与音频死锁

挂断信号方向反了

通话进行时CallHistory没有记录行,通话完成后才出现带最终时长的行。旧检查将duration == 0读作“仍在通话”,这在两个方向上都是错的——失败的拨号会让0.0永远留在那里。

可靠信号是快照:会话开始时记录最新的Z_PK,任何更新的行都意味着通话结束。这是这台机器上唯一诚实的挂断来源,因为持有音频设备的进程永远无法检测到自己通话的结束。

音频挂起但进程仍然存活

sounddevice在OpenAndSetupOneAudioUnit内部死锁,等待在mach_msg。sample <pid>显示了确切的帧,裸InputStream以同样方式挂起。sudo killall coreaudiod能解除卡死,因为守护进程按需重启,设备打开恢复正常。

几天后问题复现,coreaudiod是无辜的。真正持有者是audiomxd,自启动以来处于R状态29天,约94% CPU,负载平均值在48到88之间,空闲率为零。杀死该守护进程后音频立即恢复。规则更新为:音频打开挂起时,先读进程状态再重启显而易见的守护进程。

mach_msg 是 macOS 内核中 Mach 微内核提供的进程间通信(IPC)原语,用户态通过 mach_msg_trap 系统调用进入内核执行消息收发。当某个线程调用 mach_msg 等待一条消息时,它会进入不可中断的等待状态,在 sample 或 spindump 的输出中表现为栈帧停留在 mach_msg_trap 或 mach_msg 上。音频框架(Core Audio)内部大量使用 Mach 消息在客户端与 coreaudiod 守护进程之间同步设备状态,因此在音频设备打开过程中出现 mach_msg 等待,通常意味着客户端正在等待守护进程的响应,而守护进程可能已经卡死或过载。sample <pid> 命令可以捕获目标进程的调用栈快照,从而定位到具体的阻塞点。

循环自身维持了故障

一个卡住的循环持有虚拟音频设备,检测读到“已连接”,看门狗又启动另一个循环,两个循环争抢一个设备。这个楔子能在coreaudiod重启后存活,所以看起来像硬件问题。

证据来自两次对比运行:停止看门狗并杀死陈旧进程后,音频立即打开;让它们运行时,每次打开都挂起。得出的规则是:每次通话一个循环,空闲时零循环,从两侧强制执行,加上一个看门狗在通话中杀死重复循环、无通话时杀死陈旧循环。

系统层与服务商问题

从未响起的来电

在所有这些之上,还有一个Agent代码无法触及的故障。屏幕共享会话开启时,macOS在来电响起前就将其抑制。日志明确写着:outcome: suppressed; reason: display shared。一个通知设置——镜像或共享显示器时的通知——处于关闭状态。打开后,五分钟内48次来电全部允许,零抑制。Agent没有错过电话,是操作系统从未让电话存在。

服务商增加了四分钟静默

管线修复后,一个实时通话仍然安静了四分钟。日志显示了轮廓:20:19:03派发,20:23:07首次工具输出,工具本身耗时3.1秒。下一轮再次发生:20:23:18派发,20:27:31完成,约11.6秒的工作周围是4分13秒的死寂。客户端没有任何超时限制,所以拥堵的服务商队列看起来和挂起一模一样。

修复是为每个大脑调用设置35秒的socket读取超时。流式增量会持续重置超时,这样既限制了死寂时间,又不会切断真正在思考的模型。其背后是一个备用模型接收相同对话,实测通过一条调用路径为1.9秒,另一条为1.8秒。重连时另一个限制也咬了我一口:问候语携带了2500个字符的通话尾部,三次触发Context append text must not exceed 500 tokens,每次都静默失败。现在上限设为900字符。

socket 读取超时是指在进行网络 I/O 时,为 recv 或 read 操作设置一个最长时间限制。如果在该时间内没有收到任何数据,底层调用会抛出超时异常(如 Python 中的 socket.timeout),而不是无限期阻塞。对于流式响应(如 SSE 或分块传输),每收到一个数据块都会重置计时器,因此超时限制的是“连续无数据”的时长,而不是整个请求的总时长。在本文中,为每次大脑调用设置 35 秒的 socket 读取超时,既能在服务商队列拥堵导致长时间无输出时及时失败并切换到备用模型,又不会打断真正在持续输出 token 的慢速模型。

由此得出的规则

失败开放。不确定的读数保持会话存活,只有明确的结束才关闭它。把“我无法判断”当作“已经结束”的看门狗,会在任何检测天然盲区的机器上杀死活跃通话——而大多数机器都是如此。

单一持有者。每次通话恰好一个进程持有音频设备,空闲时为零。每个重复都是未来的反馈循环。

进程存活不等于工作正常。卡死的守护进程保留其PID、内存和状态,然后在第一行实际工作时停下。测试事物本身,而不是进程表。健康脚本现在每次运行都打开音频设备,它的沉默就是一切正常的信号。

自愈程序每五分钟运行一次,有五个任务:通话中杀死重复、杀死卡住的循环让watcher重新生成、无通话时杀死陈旧循环、设备打开挂起时重启音频守护进程、watcher宕机时踢它一脚。除非采取行动,否则它保持沉默。

最先该检查什么

如果你的Agent突然沉默,不要从模型开始。从最后一个报告“发生了什么”的地方开始,问它:当它不知道时,它打印什么?

你的技术栈中,哪个看门狗报告了它并未赢得的信心?

#语音Agent#通话静默#音频管线#看门狗#macOS音频#AI Agent调试
分享:

相关推荐