整体架构

AI Voice Platform 由 5 个相互独立的服务/模块 组成,各自职责单一、协议清晰,可以单机起,也可以分布式部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
        ┌────────────────────────────┐
│ Caller / SIP Trunk │
└────────────┬───────────────┘
│ SIP / RTP

┌────────────────────────────────────────┐
│ FreeSWITCH │
│ ┌────────────────────────────────┐ │
│ │ dialplan: socket host:port ... │ │
│ ├────────────────────────────────┤ │
│ │ mod_audio_fork │ │
│ └─────┬──────────────────┬───────┘ │
└────────┼──────────────────┼────────────┘
│ outbound ESL │ WebSocket
│ (TCP 9911) │ (PCM16 16k)
▼ ▼
┌──────────────────┐ ┌─────────────────────────┐
│ callflow-esl │ │ sherpa-asr-online-server│
│ Bun + TS │ │ (C++ WebSocket /audio) │
│ ───────────── │ └─────────────┬───────────┘
│ SessionRunner │ │
│ ↓ │ ┌─────────────▼───────────┐
│ CallRuntime │ │ sherpa-onnx │
│ ↓ │ │ (ONNX Runtime, CPU) │
│ business fn │ │ ┌─────────────────┐ │
│ (speak/hear/ │ │ │ streaming │ │
│ originate) │ │ │ zipformer + │ │
└────────┬─────────┘ │ │ silero-vad │ │
│ │ └─────────────────┘ │
│ HTTP └─────────────────────────┘
│ POST /tts

┌─────────────────────────┐
│ sherpa-tts-server │ ┌───────────────┐
│ (C++ HTTP) │←──│ VITS │
│ POST /tts │ │ aishell3 │
│ GET /wav/<name>.wav │ └───────────────┘
└──────────┬──────────────┘
│ HTTP playback

FreeSWITCH playback

组件职责矩阵

组件 角色 进程 主要协议 默认端口
FreeSWITCH 软交换、媒体处理、ESL 第三方 SIP / RTP / ESL 5060 / RTP / 8021
mod_audio_fork 把通话音频从 FreeSWITCH 桥接出去 FS 内 WebSocket(子协议 audio.drachtio.org 跟随上游
callflow-esl 业务编排:ESL 入站 + HTTP 外呼 API Bun ESL TCP / HTTP 9911 / 9912
sherpa-asr-online-server 流式 ASR + VAD C++ WebSocket 10096
sherpa-tts-server 离线 TTS + wav 缓存与下载 C++ HTTP 9080

四条主数据流

1. 呼入流程(用户拨进来)

1
2
3
4
5
6
7
8
9
SIP INVITE
→ FreeSWITCH dialplan
→ action socket host:9911 async full
→ outbound ESL 连接 callflow-esl
→ SessionRunner 解析 channel info
→ 路由业务(biz_id / 号码映射 → call_businesses)
→ business 函数被调用,拿到 CallContext
→ ctx.answer / ctx.speak / ctx.hear / ...
→ 业务完成 → ctx.hangup

2. 呼出流程(业务发起外呼)

1
2
3
4
5
HTTP POST /outbound-calls  (callflow-esl)
→ inbound ESL → FreeSWITCH originate
→ 接通后 FreeSWITCH 通过 dialplan 把 B leg 送回 callflow-esl
→ outbound ESL 复用同一套业务路由
→ 业务函数被调用

3. 实时识别流程(hear / callHear)

1
2
3
4
5
6
7
8
9
business: ctx.hear({...})
→ CallRuntime 通过 ESL 下发 uuid_audio_fork start ws://asr:10096/audio
→ FreeSWITCH mod_audio_fork 建立 WebSocket
→ 先发 metadata 文本帧(含 channel uuid)
→ 持续推 16kHz/mono/PCM16 二进制帧
→ sherpa-asr-online-server 推 partial / final 文本帧回 FS
→ FS CUSTOM event mod_audio_fork::transcription
→ callflow-esl 订阅事件 → 反序列化 → 触发 hear() Promise
→ 业务拿到识别结果

callHearhear 的”长连接”版本——整通通话保持 audio_fork,业务通过 onResult / onFinal 回调驱动。

4. TTS 播报流程(speak)

1
2
3
4
5
6
business: ctx.speak({ kind: "tts", text: "..." })
→ CallRuntime → HTTP POST http://tts:9080/tts
→ sherpa-tts-server 合成 wav 落盘到 public/wav/tts-<hash>.wav
→ 返回 wavUrl
→ callflow-esl 通过 ESL 下发 playback shout://<wavUrl>
→ FreeSWITCH 从 TTS server 拉 wav 播给用户

文本+speakerId+speed 相同的合成会命中文件级缓存,第二次请求直接返回已有 wavUrl,零合成开销。

端口与协议矩阵

目标 协议 端口 用途
SIP UAC FreeSWITCH SIP/UDP 5060 呼入
FreeSWITCH callflow-esl TCP 9911 outbound ESL(dialplan socket)
callflow-esl FreeSWITCH TCP 8021 inbound ESL(originate)
调用方 callflow-esl HTTP 9912 POST /outbound-calls
FreeSWITCH (mod_audio_fork) ASR Server WebSocket 10096 /audio 路径,子协议 audio.drachtio.org
callflow-esl TTS Server HTTP 9080 POST /tts
FreeSWITCH TTS Server HTTP 9080 GET /wav/<file> 播放
任意 ASR Server HTTP 10096 GET /health
任意 callflow-esl TCP 8021 业务订阅 ESL 事件(被动)

ASR 与 TTS 服务没有鉴权。生产环境务必置于内网或加反代鉴权层,参见 部署

业务编排分层

callflow-esl 内部分三层,自上而下:

1
2
3
4
5
6
7
8
9
10
11
┌───────────────────────────────────────────────┐
│ Business Layer ── 业务函数(一文件一业务) │
│ ctx.speak / hear / originate / conference │
├───────────────────────────────────────────────┤
│ Runtime Layer ── CallRuntime / Controllers │
│ playback / recognition / dtmf / conference │
│ recording / channel-variable / originate │
├───────────────────────────────────────────────┤
│ ESL Layer ── 协议层 + 入站/出站客户端 │
│ parser / commands / events / freeswitch IO │
└───────────────────────────────────────────────┘

业务层只看 CallContext,不直接碰 ESL 命令;Runtime 层负责把抽象命令翻译成 ESL execute / sendmsg / event 订阅;ESL 层负责协议序列化。

资源解耦

各组件之间无强耦合,可灵活替换:

想换什么 怎么做
ASR 引擎 实现兼容 mod_audio_fork 子协议的 WebSocket 服务即可(参见 ASR 服务 协议章节)
TTS 引擎 实现 POST /tts 返回 { wavUrl } 的 HTTP 服务即可
业务编排 直接用裸 ESL 自行实现;或在 callflow-esl 里新增业务函数
模型 替换 models/ 下对应目录与配置中的路径,无需改代码
软交换 任何支持媒体桥接到 WebSocket 的 PBX;本项目以 FreeSWITCH 为最小验证集

下一步