第一章:AI 语音识别(STT)— 从语音识别失败到全链路跑通

作者:

问题现象

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 也是缺的。即使模型能加载,解码这一步也会挂。

根因分析

三个根因叠加导致了一个”死锁”级故障:

  1. 模型从未下载成功:faster-whisper 初始化时调 download_model(),但 HuggingFace CDN 在国内不可达,每次请求都在等一个永远不会完成的下载
  2. 事件循环被阻塞:同步的模型下载和转写操作跑在 FastAPI 的 async event loop 里,一个请求卡住,所有后续请求(包括 health)全部排队等死
  3. 音频格式链路不匹配:前端产 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 一把梭,能快速排除前端干扰,定位到真正的故障层

← 返回目录

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注