最近在研究实时语音 + LLM 这个方向,顺手拆解了一下「面试中实时给提示」这个场景的工程实现思路。不聊产品形态,只看技术。
核心问题:为什么这个场景对延迟极度敏感
正常对话类 AI 产品,2-3 秒的响应时间用户完全可以接受。但面试场景不一样——面试官提完问,你有大约 2-4 秒的"思考缓冲期",超过这个窗口再盯屏幕就很奇怪。
所以系统必须在面试官说完关键词那一刻就开始处理,而不是等完整问题出来。这要求整个链路的端到端延迟控制在 800ms 以内,最理想是 500-600ms。
链路拆解
麦克风 → 音频流采集 → VAD → ASR(流式)→ 语义理解 → LLM → 流式渲染
1. 音频采集:用 WebAudio API 直接拿 PCM
不要走 MediaRecorder,那玩意儿有 timeslice 最小 100ms 的限制,而且编码成 webm/opus 还要解码。
直接上 AudioWorklet 拿原始 PCM float32:
class MicProcessor extends AudioWorkletProcessor {
process(inputs) {
const channel = inputs[0][0];
if (channel) {
this.port.postMessage(channel.buffer, [channel.buffer]);
}
return true;
}
}
采样率 16kHz 足够 ASR,别用 44.1kHz 浪费带宽。
2. VAD:端点检测决定何时触发 ASR
轻量方案用 WebRTC 自带的 VAD 算法移植到 WASM,大约 1-2ms 判断一帧:
import webrtcvad
vad = webrtcvad.Vad(2)
is_speech = vad.is_speech(pcm_frame, sample_rate=16000)
关键参数:frame size 30ms,检测到连续 200ms 静音视为断句,触发 ASR。
3. ASR:流式识别是关键
必须用流式 ASR,边说边出中间结果。实测延迟:
| 方案 | 首字延迟 | 准确率 | 费用 |
|---|---|---|---|
| 阿里云实时语音 | ~200ms | 高 | ¥3.5/小时 |
| 腾讯云实时ASR | ~250ms | 高 | ¥3/小时 |
| Whisper Large(本地) | ~400ms | 极高 | 0(需GPU) |
| FunASR(本地CPU) | ~300ms | 高 | 0 |
4. 语义理解:不等全文,用关键词触发
TRIGGER_KEYWORDS = ["说说你对", "讲一下", "怎么理解", "有没有了解", "项目中", "遇到过", "如何实现", "原理是"]
def should_trigger(partial_asr_text: str) -> bool:
return any(kw in partial_asr_text for kw in TRIGGER_KEYWORDS)
5. LLM 调用:流式输出 + 边生成边渲染
async def stream_answer(question: str):
async with client.stream("POST", "/chat/completions", json={
"model": "deepseek-chat",
"stream": True,
"messages": [{"role": "user", "content": build_prompt(question)}]
}) as resp:
async for chunk in resp.aiter_lines():
if chunk.startswith("data:"):
token = parse_token(chunk)
yield token
6. 整体时序
t=0ms 面试官说出关键词
t=50ms VAD 确认语音帧
t=200ms ASR 流式识别出关键词
t=210ms 触发 LLM 请求
t=500ms LLM 第一个 token 到达前端
t=510ms 用户看到第一个字
屏幕共享不可见
macOS: NSWindow.sharingType = .none
Windows: SetWindowDisplayAffinity
这套链路在实际产品里已经有落地了,比如即答侠(interviewasssistant.com)就是做这个方向的。技术原理大致如上,FunASR + DeepSeek + WebSocket 这套组合完全开源,周末能跑起来。
有做过类似项目的欢迎评论区聊。