一、问题发现
系统日志显示火山引擎TTS语音合成成功返回了74KB~80KB的MP3音频数据,但从发起合成请求到音频收集完成,整个过程耗时长达16秒。对于AI对话场景而言,用户期望秒级响应,16秒的等待完全不可接受。初步排查发现两个核心瓶颈:
1) WebSocket 超时等待过长:代码在收集完所有 AudioOnlyServer 音频块后,仍等待15秒超时才发送 FinishSession 断开连接。实际火山引擎的 TTSSentenceEnd 事件在 FinishSession 之后才返回,导致不必要的长时间挂起。
2) chat 接口同步等待 TTS:POST /chat 的实现中,TTS 合成是阻塞式
await 调用,直接拖慢整个对话接口的响应速度。
二、优化方案设计
针对两个瓶颈分别实施精准优化:
- 自适应超时策略:WebSocket 接收循环中,如果已有音频数据(chunks 非空),则将接收超时从 15s 缩短为 3s —— 3 秒内无新音频块则视为合成完成,立即断开连接。若尚无音频数据(首次接收),仍保持 15s 等待,避免网络波动导致的误判。
- chat 接口非阻塞后台 TTS:将 chat.py 中的
await tts_service.synthesize()改为asyncio.create_task()非阻塞模式,TTS 合成在后台独立执行,不阻塞 chat 接口的 HTTP 响应返回。
三、代码实现要点
自适应超时核心逻辑(tts.py):
chunks: list[bytes] = []
while True:
recv_timeout = 3 if chunks else 15 # 有音频后缩短超时
try:
msg = await asyncio.wait_for(receive_message(ws), timeout=recv_timeout)
except asyncio.TimeoutError:
if chunks:
break # 有数据超时,正常结束
break
# 收集 AudioOnlyServer 和 TTSResponse 音频块
if msg.type == MsgType.AudioOnlyServer and msg.payload:
chunks.append(msg.payload)
elif ... TTSSentenceEnd and chunks:
break # 句子结束且有音频,立即退出chat.py 非阻塞改造:
# 之前(阻塞):
# audio = await tts_service.synthesize(reply_text)
# 之后(非阻塞):
asyncio.create_task(background_tts(reply_text, session_id))
四、优化效果与验证
优化后验证结果:- WebSocket 连接时间:从 16s 降低到音频合成实际耗时(约 3~5s),不再有无意义的 15s 空等;
- chat 接口响应:不再被 TTS 拖累,AI 回复文本立即返回给前端,TTS 合成在后台独立完成并通过 MQTT 异步下发;
- 用户感知:对话回复即时出现在界面上,语音播报稍后跟随,整体体验从"等待 16s 才能看到回复"变为"秒级看到回复 + 稍后听到语音"。
此次优化打通了从"TTS合成完成"到"连接断开"之间的瓶颈,为后续流式播报优化奠定了 WebSocket 实时数据推送的基础。


💬 过往技术交流