跳到正文

集成与排障 ​

接入步骤 ​

代理是透明串接:把原来直连上游 ASR 的那条链路改成经过代理,apps/callflow-esl 零改动。

  1. 在代理 config.toml 中选择 upstream.defaultProfile,并配置对应的 upstream.profiles.<name>。当前实现只提供 iflytek-iat Provider,用于科大讯飞私有云实时识别; 配置项与命令时序见上游适配器。阿里云、Azure 和本地 sherpa 均有自己的服务端断句能力,不经过本代理。

  2. apps/callflow-esl/config.toml 的 audioFork.wsUrl 改指向代理:

    toml
    [audioFork]
    wsUrl = "ws://192.168.2.246:10093/audio"
    bugName = "callflow_asr"
    mixType = "mono"
    sampleRate = "16k"

    bugName / mixType / sampleRate 全部不动——代理要求的就是同一份 16kHz 单声道流。

  3. 想吃到开嗓提速的业务,把该轮识别的 bargeInTrigger 设为 "speech-start" (ctx.hear / conference.play / ConferenceHearInput 都有这个字段)。 不改的话仍是默认 partial 策略(出字打断),抗环境底噪误触最稳妥。

也可以让 FreeSWITCH 直接挂到代理上验证:

bash
uuid_audio_fork <call_uuid> start ws://127.0.0.1:10093/audio mono 16k metadata.json

调用示例:用 ASR 测试客户端当替身 ​

onnx-platform/sherpa-asr-server/request-ws.js 是一个最小 mod_audio_fork 模拟器,支持 --host、--port、--path、--subprotocol、--file / --wav,直接指向代理即可:

powershell
# 先起上游,再起代理
bun run dev:asr
bun run dev:vad

bun run onnx-platform\sherpa-asr-server\request-ws.js --file <audio.wav> --port 10093

常用组合:

命令用途
--port 10093把测试客户端指向代理,验证当前 Profile 的 VAD 与文本事件
--silence3 秒零 PCM,必须不产生任何 VAD 帧与文本
--fast全速推流,压出收尾与排空逻辑(真机是 20ms/帧实时流,容易掩盖这条路径)
--idle只推前 2.5 秒后保持空闲,验证客户端测试模式下的空闲定稿行为

关键验证项 ​

1. 提速量(本项目的交付物)

同一段音频分别记录代理的 VAD 起始帧与首个非空 partial 时间戳:空文本 VAD 起始帧应出现在 首个非空 partial 之前。参考实测(20.34s、含 5 句人声的素材):VAD 起始帧在 1580~1587ms 下发,首个非空 partial 在约 2050ms,提前约 470ms。

2. 与 sherpa VAD 基准的一致性

同一段音频,代理下发起始帧的时刻应与 sherpa-asr-server 的 Silero VAD 基准基本吻合 (实测 1584/1587/1580ms vs 1593ms,偏差 10ms 内)。偏差明显变大说明 vad 段参数没对齐。

3. 误打断回归线

--silence 必须零 VAD 帧。这是空文本帧唯一可能造成危害的场景,每次调 vad.threshold 或 minSpeechDuration 后都要重跑。

4. 闸门不吃字、不丢 partial

speech-only 与 passthrough 两种模式跑同一段音频:文本应一致、partial 数量不减 (实测 5 finals / 31 partials 两种模式相同),首字不缺。speech-only 会让首个上游文本 晚约 300ms(pre-roll 一次性冲入后上游才开始算),但代理自己的 VAD 起始帧不受影响—— 提速来自「不等文本」,与上游何时出字无关。

5. 向后兼容

真机跑两通电话:bargeInTrigger: "speech-start" 应在播报期间被开嗓立刻打断; 默认 partial 策略下空文本帧不得触发任何打断。

排查 ​

下游收不到任何 transcription 事件 ​

  • 先 GET /health,ok:true 只说明模型已加载、配置合法,不代表上游可达。
  • 看代理日志有没有 连接上游失败。上游故障时代理会向下游发 error + disconnect 并 close 1011,mod_audio_fork 会把它们变成 mod_audio_fork::error 事件——先查那里。
  • 握手被 400 拒:客户端没带 Sec-WebSocket-Protocol: audio.drachtio.org。 确认 server.wsSubprotocol 与客户端一致,或置空关闭强制。
  • 握手被 503:并发达到 concurrency.maxSessions。

有 VAD 帧但没有文本 ​

代理不做 ASR,text / isFinal 全部来自上游。这种现象说明上游没回话:

  • 有 VAD 帧但没有文本时,先打开 logging.debugTextFrames,检查 ssb / auw / grs 的 厂商返回;本地 VAD 只负责切段,文本完全来自激活的上游 Profile。
  • 客户端如果直接 close 而不先发零长 flush 帧,尾部结果就没有投递窗口 (读到 close 后就不能再向下游发帧了)。mod_audio_fork 与 request-ws.js 都会先 flush。
  • iflytek-iat 的断句由本地 VAD 决定,具体症状表见 上游适配器 · 排障要点。

打断太迟钝 / 太敏感 ​

  • 迟钝:确认业务侧 bargeInTrigger 真的是 "speech-start";默认 partial 策略只认有文本的帧, 空文本 VAD 帧不参与判定。再确认 bargeIn.emitSpeechStartFrame 为 true。
  • 太敏感:调高 vad.threshold(默认 0.35)或 vad.minSpeechDuration(默认 0.15s)。 改之前先 recording.enabled: true 录一段——录的是闸门之前的原始流,就是 VAD 实际看到的音频, 可以反复回放调参。
  • 播报期间被自己的 TTS 打断:mixType: "mono" 只 fork 上行读流,正常不含平台自己播的声音, 但线路侧回声仍可能串入。用录音确认到底录进了什么。

段落切得太碎 / 太长 ​

vad.minSilenceDuration(默认 0.35s)决定多长的静音算一段结束;maxSpeechDuration (默认 20s)是硬切上限。这两个值同时影响 utteranceIndex 的推进节奏。

讯飞 IAT 上游相关 ​

厂商私有档的症状与判定(ssb 被拒、partial 不累进、缺尾字、会话额度不释放等)集中在 上游适配器 · 排障要点,不在此重复。排查第一步固定是看 /health 的 upstreamKind,确认跑的确实是那一档。

构建相关 ​

  • build.ps1 报 ParserError:用了 Windows PowerShell 5.1。改用 pwsh(PowerShell 7)。
  • cargo 产物出现在仓库根 target/:在仓库根用 --manifest-path 调用了 cargo。 .cargo/config.toml 按当前工作目录的祖先链解析,必须先 cd onnx-platform/stream-vad-proxy (根脚本 bun run check:rust 已经这么做)。
  • vad.windowSize 必须是 512:仓库内的 silero_vad.onnx 是 v4 模型,窗口写死在计算图里。
  • 启动报模型找不到:确认从交付目录(dist/<platform>/)运行,或按 配置参考 · 模型路径解析 检查四级回落。

日志 ​

日志同时输出到标准输出与 logging.dir 下按天滚动的 stream-vad-proxy.log.<日期>。 排障时按需打开三个开关(都会显著放大日志量):

开关看什么
debugVadState开嗓、段完成、pre-roll 回补的时刻
debugTextFrames客户端与上游的每一条文本帧
debugAudioFrames每个音频帧的字节数(确认推流节奏与帧大小)

会话结束时固定打一条统计:audio_bytes / forwarded_bytes / start_frames / completed_frames / upstream_frames / utterance_index。用 forwarded_bytes / audio_bytes 直接看出闸门拦下了多少(实测约 32%)。

注意事项 ​

  • 服务没有鉴权且默认监听 0.0.0.0,建议只在可信网络运行;要公网可达必须在 Nginx / FRP 侧限源或加 token。
  • 代理不自造 text / isFinal,也不做 ASR。任何「文本不对」的问题都要先去上游查。
  • 每会话独占一个 VAD,建链时加载模型约 90ms;concurrency.maxSessions 默认 256 是上限保护 而非实测容量。
  • 任务中启动的服务必须在结束前停止(代理、上游 ASR、callflow-esl),并用 netstat 确认端口已释放。

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。