按”现象 → 根因 → 处理”组织,覆盖部署、识别、合成、外呼几类高频问题。

部署 / 拉起

Q: callflow-esl 连不上 FreeSWITCH(ECONNREFUSED)

现象:启动日志报 inbound ESL connect failed

根因

  • freeswitch.host / port / password 不对
  • FS 的 event_socket.conf.xml 没监听 0.0.0.0 或绑了内网 IP
  • apply-inbound-acl 限制了来源 IP

处理

1
2
3
4
# 在 callflow-esl 所在机器测试
telnet <fs-host> 8021
# 看 FS 端
fs_cli -x "show modules" | grep event_socket

确认端口可达、密码正确、ACL 允许。

Q: FS 拨号没反应,看不到 callflow-esl 收到 outbound

根因

  • dialplan socket 地址不对
  • callflow-esl 监听 127.0.0.1,FS 不在同机
  • 防火墙拦 9911

处理

1
2
3
4
# 在 FS 机器测试
telnet <callflow-esl-host> 9911
# 看 callflow-esl 监听
ss -tlnp | grep 9911 # 应该 0.0.0.0:9911

确认 dialplan socket 用 callflow-esl 实际 IP,且 callflow-esl server.host: "0.0.0.0"

Q: outbound 连进来但立刻断

根因:业务路由失败。

  • channel variable biz_id 没设,且 destination_numbercall_business_number_mappings 里查不到
  • business_namebusiness-registry 里没注册

处理

看 callflow-esl 日志 route business= 行;进 MySQL 确认表里有对应记录;确认 src/runtime/business-registry.ts 已 export 业务函数。


识别(ASR)

Q: 接通后没声音 + ASR 也没 transcription

根因

  • audioFork.wsUrl 不对,FS 连不到 ASR 服务
  • ASR 服务没启动 / 健康检查失败
  • 子协议不匹配

处理

1
2
3
4
5
6
# 在 FS 机器测试 ASR 可达
curl http://<asr-host>:10096/health

# 看 callflow-esl 是否发出了 uuid_audio_fork start
fs_cli -x "show channels"
fs_cli -x "uuid_audio_fork <uuid> start ws://<asr-host>:10096/audio mono 16k test {}"

如果手动 start 报 connect_failed,看 ASR 服务日志是否拒绝握手。

Q: ASR 持续 partial,但没有 final

根因

  • VAD 阈值太低,背景噪声当作”还在说话”
  • 上游一直发音频帧,没有触发 endpoint
  • endpointRuleN 配置过于宽松

处理

打开调试:

1
2
3
4
{
"debugLogRecognitionState": true,
"recordAudioEnabled": true
}

观察 speechActive=true 持续多久;若一直 true,调高 vadMinSilenceDurationvadThreshold

业务侧也可主动 flush:发 0 长度二进制帧,或挂机自动 flush。

Q: ASR partial 全是乱码 / 字错率极高

最常见根因:采样率不匹配。

处理

  • 确认 FS 发的是 16kHz(audioFork.sampleRate: "16k"
  • 确认 ASR 服务 sampleRate: 16000
  • 录音对比:recordAudioEnabled: true 后听 ASR 端落盘的 wav,再用 sherpa-demo/demo_online_asr.exe 离线复现
  • 如果 demo 也错,是模型 / 输入问题;如果 demo 对、服务错,是采样率 / 帧顺序问题

Q: mod_audio_fork::transcription 事件收不到

根因:FreeSWITCH 默认按 event plain CUSTOM mod_audio_fork::transcription 订阅,在某些 FS 版本下不稳定。

处理:在 callflow-esl config.json

1
2
3
{
"audioFork": { "eventSubscriptionMode": "all" }
}

"all" 是当前推荐的、唯一稳定下发 transcription 的策略。


合成(TTS)

Q: speak 后用户没听到声音 / FS 报 playback 404

根因:FS 拉不到 wav URL。

处理

1
2
# 在 FS 机器测试 TTS wav 可达
curl -I http://<tts-host>:9080/wav/tts-xxx.wav

如果 404 / connect refused:

  • 检查 sherpa-tts-server/config.jsonpublicBaseUrl 是否填了FS 能访问的地址(不是 127.0.0.1)
  • 检查防火墙 9080 是否对 FS 开放
  • 检查 listenHost 是否 0.0.0.0

Q: TTS 503 Server busy

根因:等待队列已满。

处理

  • 增加 workerThreads 处理 HTTP 速度
  • 增加 ttsPoolSize 并行合成数
  • 增加 maxQueuedRequests(默认 0 不限,如果手动配过限制,调大)
  • 检查是否有”循环合成同一段长文本”的业务 bug

Q: TTS 同一句话每次都重新合成(不命中缓存)

根因:缓存 key 是 text + speakerId + speed 的 FNV-1a 哈希。如果业务每次传的 text 有微小差异(带时间戳、加随机标记),缓存就不命中。

处理:业务侧把文本规范化(去掉时间戳/请求 ID);或针对动态文本的业务,接受不命中。

Q: TTS RTF 偏高(合成时间 > 实时时间)

根因:单线程 CPU 推理在小机器上偏慢。

处理

  • 在 sherpa-tts-server / sherpa-asr-online-server 的 numThreads 改为 2 或 4,重新构建
  • 升级机器
  • 或缩短业务里 TTS 文本长度

外呼

Q: POST /outbound-calls 返回 ok 但电话没拨出去

根因

  • inbound ESL 的 originate 在 FS 端失败
  • originateDialStringTemplate 不对(gateway / user 配置问题)
  • 主叫号码不被 SIP trunk 接受

处理

1
2
3
4
5
# 看 FS 日志
tail -f /usr/local/freeswitch/log/freeswitch.log | grep -i originate

# 手动验证 originate
fs_cli -x "originate user/13800138000 &echo"

Q: 接通后 callflow-esl 收不到 B leg outbound 连接

根因callbackHost 不可达。

处理

  • 在 FS 机器 telnet 测试 <callbackHost>:9911
  • 不通就检查防火墙 / 网络
  • 不要写 127.0.0.1(除非同机)

Q: originateWithRingbackDetection 经常误判

根因

  • 运营商提示音被 ASR 识别成了别的文本
  • 没有 ringback / early-media,业务在 ringing 阶段拿不到音频

处理

  • 业务里降低对 carrierPromptKind 的强依赖,把 detectedText 拿出来做关键词正则匹配
  • 在 dialplan 里启用 ringback<action application="set" data="ringback=${us-ring}"/>

中文与编码

Q: PowerShell 终端中文 ASR 输出乱码

1
2
chcp 65001
$OutputEncoding = [System.Text.UTF8Encoding]::new()

Q: 业务里中文文本经过 ESL 后变成 ?

根因:channel variable 在 FS 内部默认是 latin-1,复杂字符可能被吞。

处理

  • 业务里直接用 TS 字符串 + ctx.speak({ kind: "tts", text })不要把中文写入 channel variable 再读出来
  • 必须用 channel variable 传中文时,base64 编码后传

模型 / 性能

Q: 服务启动报 missing TTS model file: ...Failed to create OnlineRecognizer

根因:模型路径不对,或文件缺失。

处理

  • 看日志打印的绝对路径,进文件系统确认存在
  • sherpa-onnx/win_x64/bin/sherpa-onnx-version.exe 验证 sherpa-onnx 本体完整
  • sherpa-demo 的对应 demo 验证模型本身能跑

Q: ASR / TTS 服务启动后内存占用很高

根因:每个 sessionPoolSize / ttsPoolSize 都会预热一个独立的模型实例,约 600MB-800MB 每个。

处理

  • 业务并发量不大时把池大小调小(比如 ttsPoolSize: 1
  • 不必要的预热可以关掉(注意启动后第一次请求会冷启动慢)

Q: FS 跟 ASR 之间的 WebSocket 频繁断连

根因

  • 网络抖动 / NAT 超时
  • 单连接持续时间过长(超过 NAT keepalive)
  • FS 端没设 keep-alive

处理

  • 业务侧不要在一通通话里反复 start / stop audio_fork;用 callHear 整通保持
  • 中间设备如果有 idle timeout,业务定期发 ping 帧
  • 内网部署优先,减少经过的中间设备

数据库

Q: MySQL 连不上 / 表不存在

处理

1
2
3
4
5
6
7
8
9
10
# 1. 测连接
mysql -h 127.0.0.1 -u root -p ai-voice

# 2. 初始化 schema
cd callflow-esl
bun run db:generate
bun run db:push

# 3. 浏览器确认
bun run db:studio

Q: 业务路由偶尔失败,但 MySQL 表里有记录

根因destination_number 字符串匹配,前后空格 / + 号不一致。

处理:业务侧或路由层做规范化(去空格、去 +86)。


调试组合

排查端到端识别延迟:

1
2
3
4
5
1. callflow-esl 日志:speak 完成时间 / hear 启动时间
2. ASR 服务日志:握手时间 / partial 间隔 / final 时间
3. TTS 服务日志:generated <ms>
4. FS 日志:playback start / end
5. 网络抓包:WebSocket / HTTP 是否有重传

排查”接通后没反应”:

1
2
3
4
1. callflow-esl 日志看是否走到 ctx.execute("answer")
2. FS 日志看是否真的 answer 了
3. settleDelayMs 是否过短
4. speak 是否真的发出了 playback 命令

没列出来的问题

去看仓库内对应 README 的 “Notes” / “Troubleshooting” 章节,那里通常有更细节的处理思路。也欢迎提 issue 反馈。