问题现象
2026年7月底,用户反馈:在 CleanLearn 小程序的口语训练模式下,按住录音按钮说完话松手后,页面提示“语音识别失败”。有时候等很久然后弹出一个红色提示条,有时候干脆没有任何反应。
更严重的是,当语音识别触发后,整个小程序的聊天功能都会卡住——发送文字消息也会超时,页面像冻住了一样。
排查过程
第一步:直测后端接口
不通过小程序,直接用 curl 模拟请求打后端 /api/stt 接口:
curl -X POST http://193.112.130.163/api/stt \
-H "Authorization: Bearer <token>" \
-F "audio=@test.wav"
返回 500 Internal Server Error,有时候甚至是连接超时。这说明问题不在小程序端,而在服务器上。
第二步:检查健康接口
接着测 /api/health——这个接口最简单,只返回 {"status":"ok"}。结果也超时了。一个健康检查都超时,说明整个 FastAPI 服务被某次请求卡死了。
第三步:检查 STT 代码
登录服务器查看 /opt/english-api/routers/stt_router.py,发现了两个致命问题:
- 模型文件
faster-whisper-tiny根本不存在于本地——每次启动时都会尝试从 HuggingFace 下载,但服务器访问不了 HuggingFace(国内网络限制) - 模型加载和转写逻辑写在同步函数里,没有放到后台线程。FastAPI 的 async event loop 被同步 I/O 阻塞,一次请求就冻住整个服务
第四步:检查录音格式
前端录音用的是 mp3 格式,后端解码依赖 PyAV(FFmpeg 的 Python 绑定)。但服务器上没有安装 FFmpeg,PyAV 也是缺的。即使模型能加载,解码这一步也会挂。
根因分析
三个根因叠加导致了一个”死锁”级故障:
- 模型从未下载成功:faster-whisper 初始化时调
download_model(),但 HuggingFace CDN 在国内不可达,每次请求都在等一个永远不会完成的下载 - 事件循环被阻塞:同步的模型下载和转写操作跑在 FastAPI 的 async event loop 里,一个请求卡住,所有后续请求(包括 health)全部排队等死
- 音频格式链路不匹配:前端产 mp3,后端期望 pcm/wav,中间依赖一个不存在的 PyAV/FFmpeg
修复方案
1. 模型本地化
从 hf-mirror.com(HuggingFace 国内镜像)下载 faster-whisper-tiny 模型,放到服务器本地路径:
/opt/english-api/models/faster-whisper-tiny/
代码中指定 local_files_only=True,彻底绕开外网依赖。
2. 异步化改造
重写 STT 路由,所有耗时操作(模型加载、语音转写)全部包进 asyncio.to_thread():
# 模型加载 — 启动时后台完成,不阻塞请求
model = await asyncio.to_thread(load_model)
# 语音转写 — 每次请求在独立线程执行
result = await asyncio.to_thread(model.transcribe, audio_data)
这样即使转写耗时 5 秒,其他请求(health、chat)完全不受影响。模型未就绪时返回明确的 503 Service Unavailable,而不是无限挂起。
3. 音频格式统一为 WAV
前端录音参数从 mp3 改为 WAV:
// frontend/src/pages/chat/index.tsx
recorderRef.current?.start({
duration: 60000,
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 48000,
format: 'wav', // ← 从 mp3 改为 wav
})
后端用 Python 内置的 wave 模块解码,零外部依赖:
import wave
import io
with wave.open(io.BytesIO(audio_bytes), 'rb') as wf:
frames = wf.readframes(wf.getnframes())
audio_data = np.frombuffer(frames, dtype=np.int16)
验证结果
- 用 TTS 生成一段英文语音,发送到
/api/stt,1~5 秒内返回正确转写文本 /api/health始终正常响应,不再受 STT 请求影响- 多次连续录音→转写→发送,全链路稳定
经验教训
- AI 相关服务必须有”外网不可用”的降级方案:模型必须预下载到本地,不能假设运行时能访问外部 CDN
- FastAPI 路由里绝对不能放同步阻塞操作:哪怕只是”可能慢一点”的 I/O,都要进
asyncio.to_thread - 依赖越少越好:用 Python 内置库替代 PyAV/FFmpeg,部署复杂度直接归零
- 排查线上问题先直测后端接口:curl 一把梭,能快速排除前端干扰,定位到真正的故障层
发表回复