09-语音识别为什么自己搭:FunASR落地与AI外呼的取舍
语音识别为什么自己搭:FunASR 落地与 AI 外呼的取舍
呼叫中心做 AI 外呼,第一个绕不开的问题是:语音识别(ASR)用商用的还是自己搭?商用 API(阿里 DashScope、火山引擎)按分钟计费,接入快;自建要养模型、养机器。我们最后两样都做了——生产 AI 外呼的识别暂时走 DashScope,同时自建了一套 FunASR 集群承载坐席实时字幕和录音质检,方向是把识别能力逐步迁到自建。这篇讲讲这套取舍和 FunASR 的落地过程。
为什么自建
三个理由,按权重排:
一是数据不出内网。 坐席通话录音、客户语音,都是敏感数据。全部语音流向外部 API,合规上需要客户知情同意链条,商务上也是负担。自建之后语音数据在内网闭环,这个议题直接消失。
二是计费模型。 商用 ASR 按音频分钟收费。坐席实时字幕是全天候的——每个坐席每通电话都产生识别请求;录音质检还要把历史录音再转写一遍。按我们的量算下来,一个月的费用够买一台 16C 的服务器,半年就"回本",之后识别几乎零边际成本。
三是 CPU 就能跑。 FunASR 的 ModelScope 开源模型有 ONNX 量化版本,不需要 GPU。8 核 16G 的机器能扛 8~16 路实时识别,16C32G 到 16~32 路。电话客服场景的并发路数是可预估的(等于坐席数),按需配机器就行。
FunASR 怎么部署
FunASR 部署了两套服务形态,对应两种业务:
- funasr-online(
funasr-wss-server-2pass):流式识别,跑的是 paraformer-large 双流模型——流式部分给实时增量文本,离线部分在 VAD 断句后给纠错整句。用于坐席字幕和 AI 外呼对话。 - funasr-batch(
funasr-wss-server):纯离线转写,paraformer-large-vad-punc 加标点。用于录音质检、事后全量转写。
协议是 WebSocket:文本帧发 JSON 控制(首帧带 mode: 2pass、chunk_size [5,10,5]、采样率),二进制帧发 PCM 音频(16bit 小端单声道),600 毫秒一块,结束发 {"is_speaking": false}。对电话音频很友好——8k 采样率可以直传,不用先重采样到 16k。2pass 的语义设计得很实用:2pass-online 出来的临时文本先渲染给用户看,2pass-offline 断句纠错后整句替换,字幕体验和准确率两头都占。
部署规格上我们吃过一次亏。最早 ASR 跟生产栈挤在同一台机器上,10 月 17 日那天 funasr-wss-server 内存涨到 28.7GB,触发宿主机 global OOM,连带把对象存储 RustFS 打进了写阻断(那是另一个故事,复盘在录音缺失那篇)。从此规矩:ASR 容器必须设内存上限,且独立部署。现在它单独跑在一台 16C/15G 的机器上(ZFS 文件系统,ARC 缓存上限 4G),online 加 batch 两个容器常驻约 10G 内存,模型 10G、镜像 15G,磁盘给到 100G 起步。
安全上有个必须强调的点:FunASR 服务本身无鉴权,任何人连上端口就能白嫖识别。安全完全依赖网络隔离——识别端口只对内网后端开放,严禁映射公网。接入口子走 nginx 反代(wss://…/funasr/online/),鉴权在反代层和后端做。
一次跨机迁移
10 月中旬把 FunASR 从旧机器迁到新机器,用的是最朴素也最稳的手法:
rsync整个数据目录(66 个模型文件,逐个 sha256 校验一致);- 两个镜像
docker save | ssh docker load——不走镜像仓库,内网直传更快; - 新机起服务,拿标准音频跑冒烟:识别文本「欢迎大家来体验达摩院推出的语音识别模型。」连续三轮 PASS 才算迁完;
- 旧机不停机,保留为回滚路径,新机稳定一周后才考虑下线。
整个迁移没写一行"迁移脚本",就是三条命令加一次验收。工具简单不可怕,流程严谨就够。
AI 外呼这块更大的拼图
ASR 只是 AI 外呼的一块。整个链路是:FS 的 mod_audio_stream 把通话音频 fork 成 16k 单声道 PCM 流,经 WebSocket 推给后端;后端对话引擎跑话术状态机,识别用 ASR,应答用 TTS 合成播放,支持打断(用户开口就 uuid_break 停播)。
有两个设计决策值得展开:
「流程为主、LLM 为辅」。 外层是确定性的话术状态机(开场白→产品介绍→异议处理→结束),只有异议节点和开放问答才调 LLM,而且是单次分类(温度 0,延迟预算 300ms)。function calling 只开放一个:transfer_to_human(skillGroupId, reason) 转人工。为什么不放飞让 LLM 全程接管?因为外呼话术必须可控可审计——LLM 幻觉出一句不合规承诺,责任在平台。所有 LLM 输出在进 TTS 之前还要强制过一遍合规检查(跟外呼前置用的是同一套 ComplianceCheckService),命中即丢弃改播兜底话术。
实时链路不走 MQ。 对话轮次(识别→路由→合成→播放)全在进程内和 WS 直连上完成,MQ 只承载状态收敛和审计。语音对话的体感红线是端到端 1.5 秒,任何一跳进消息队列都可能把它击穿。另外做了句级合成缓存(md5(文本+音色+语速) 命中直接复用),话术类外呼的缓存命中率超过 90%——同一句开场白,合成一次,后面全免。
TTS 用 DashScope CosyVoice,识别引擎做了防腐层(AsrEngine/TtsEngine 接口,provider 可切 mock/dashscope/volcano/funasr)——这也是前面说"两样都做"的底气:切换识别引擎是改配置,不是改架构。
现状与接下来
诚实地报告进度:AI 外呼的对话引擎、任务调度、计费(AI 通话按分钟、不足一分钟按一分钟计)都已跑通;媒体面还差一环——LocalAiDialerFacade 的完整媒体编排在与 FreeSWITCH 的音频流对接上还有收尾工作。FunASR 侧,后端的 AiEngineFactory 还没实现 funasr provider(生产识别仍走 DashScope),这是下一块要拼上的图。
自建基础设施的规律大抵如此:第一台机器、第一次迁移、第一次 OOM 都交过学费了,后面才是它真正开始省钱赚钱的时候。
本文是「云呼叫中心技术内幕」系列第 9 篇。

浙公网安备 33010602011771号