给电话装个"会聊天的大脑":FreeSWITCH + UniMRCP + LLM 的语音智能体

Posted on 一 28 9月 2026 in Tech

Abstract 给电话装个"会聊天的大脑":FreeSWITCH + UniMRCP + LLM 的语音智能体
Authors Walter Fan
Category Tech
Version v1.0
Updated 2026-09-28
License CC-BY-NC-ND 4.0

大纲

展开看看
  • 一个场景:传统 IVR 的"请说'查话费'或'办套餐'"有多别扭,LLM 让它变成"您想办什么?"
  • 三者分工:FreeSWITCH 管呼叫、UniMRCP 管 ASR/TTS、LLM 管"理解 + 决策 + 生成"
  • 架构演进:从固定语法 IVR → FreeSWITCH+UniMRCP 语音 IVR → 接 LLM 大脑
  • 一次对话的完整时序:ASR → LLM → TTS 这一圈怎么转,谁在等谁
  • 工程难点:延迟、打断(barge-in)、流式、工具调用、幻觉兜底
  • 两种架构对比:经典栈 + LLM 大脑 vs 端到端语音大模型,各自适合什么
  • 落地实践:dialplan 怎么写、LLM 网关放哪、一份集成 checklist
  • 为什么有前途:它把"听懂人话"的成本打下来了

你一定接过这样的电话:"普通话请按 1……查询话费请说'话费',办理套餐请说'套餐'。"你说了句"我想看看我这个月扣了多少钱",系统冷冰冰回一句"抱歉,没有听清,请重说"。你憋着火又说了一遍"话费",它才通。

问题不在语音识别不准,而在传统 IVR 只认预设的那几个词。它背后是一份写死的语法(grammar),你说的话不在语法里,再清楚也白搭。

现在把中间那颗"只认关键词"的脑子,换成一个大语言模型(LLM,Large Language Model)。客户随口说"这个月怎么扣这么多",LLM 听懂了意图是"查话费明细",顺手还能调用账务接口查出真实数据,再用自然的语气念给客户听。这就是语音智能体(voice agent),而它的骨架,就是 MRCP 那篇里讲过的 FreeSWITCH 和 UniMRCP。

三者到底怎么分工、一次对话在时序上怎么转、延迟和打断这些坑怎么填,下面一个一个说。

  • 主线是经典架构 + LLM 大脑:改动最小、最容易落地、引擎可替换
  • 端到端语音大模型是另一条路,后面会对比,但不主推

一、先把三者的分工钉死

这套系统能不能跑顺,前提是每个组件只干自己该干的事。先看清楚地盘:

组件 角色 管什么 不管什么
FreeSWITCH 呼叫编排者(软交换 / 媒体服务器) SIP 呼叫、RTP 媒体、放音、录音、桥接、转人工、整通对话的流程 不做语音算法,不做语义理解
UniMRCP 语音资源服务器 语音识别(ASR)、语音合成(TTS)、声纹 不碰呼叫控制,不做业务决策
LLM 对话大脑 理解客户意图、带上下文对答、决定下一步、调用工具、生成回复文本 不接触音频,只吃文本、吐文本

三句话概括:

FreeSWITCH 管"这通电话怎么走",UniMRCP 管"这段声音是什么、该说什么",LLM 管"客户到底想干嘛、我该怎么回"。

一个类比:FreeSWITCH 是餐厅领班(带位、下单、上菜、结账);UniMRCP 是后厨的耳朵和嘴(把客人的话转成文字、把菜单念出来);LLM 是真正拿主意的大堂经理——听懂客人的模糊需求("来点清淡的"),决定上什么菜,还能查库存(调用工具)。

注意一个关键边界:LLM 全程不碰音频。音频进 UniMRCP 变成文本,文本进 LLM 变成回复文本,回复文本再回 UniMRCP 变成语音。LLM 活在纯文本世界里,这也是为什么它能轻松换成任意一家的模型——它跟电话链路是解耦的。


二、架构是怎么一步步长成这样的

这套架构不是一天冒出来的,它是"让机器听懂人话"这条路上,一步步逼出来的。

第一代:按键 IVR。 没有语音识别,全靠 DTMF 按键。"查话费请按 1"。FreeSWITCH 一个组件就够了(放音 + 收按键)。毛病也明摆着:菜单一深,客户就迷路。

第二代:固定语法的语音 IVR。 加进 UniMRCP,客户可以说话了,但只能说语法里预设的词。这就是开头那个别扭场景——FreeSWITCH + UniMRCP 已经就位,缺的是一颗灵活的脑子。识别是准的,可它只认"话费""套餐"这几个词。

第三代:LLM 做大脑。 把固定语法换成"自由说话 + LLM 理解"。客户怎么说都行,LLM 负责从一句大白话里拎出意图、维持多轮上下文、决定该查什么该答什么。架构上,前两代的骨架原封不动,只是在识别结果和合成文本之间,插进了一个 LLM。

语音 IVR 架构的三代演进

这个演进路径的妙处在于:你不需要推翻已有的电话系统。FreeSWITCH 集群还是那个集群,UniMRCP 还是那个 UniMRCP,dialplan 改几行,再加一个 LLM 网关,就从"只认关键词的 IVR"升级成"能自由对话的智能体"。对一个已经有存量 contact center 的团队来说,这是改动成本最低的一条路。


三、完整的组件架构

把第三代摊开画一张图,contact center 里各组件的位置和连线:

FreeSWITCH + UniMRCP + LLM 组件架构

从上到下看:

  • OpenSIPS(可选前置):规模大时,注册、鉴权、路由、限流、防欺诈这些信令前线的活交给它,把呼叫落到 FreeSWITCH 集群。小型场景直接省掉。
  • FreeSWITCH 集群:呼叫编排 + 媒体处理的核心。mod_unimrcp 是它内置的 MRCP 客户端,负责和 UniMRCP 对话。dialplan / ESL 脚本里写着整通对话的编排逻辑。
  • UniMRCP 集群:语音资源池。ASR/TTS 引擎作为插件挂在这里——可以是自研,也可以桥接云端引擎。
  • LLM 网关:一个薄薄的服务层,接收识别文本,拼好 prompt(含系统人设、对话历史、可用工具),调 LLM,处理流式返回和工具调用,把最终回复文本吐回去。
  • 业务系统 / 工具:LLM 通过 function calling 调的真实接口——账务、订单、工单、知识库检索(RAG)。这是语音智能体和"会念稿的机器人"的本质区别:它能查到真数据。

三条数据通路要分清(这是最容易搞混的地方):

  • 信令流(SIP):建会话、协商通道。OpenSIPS ↔ FreeSWITCH ↔ UniMRCP 之间都走 SIP。
  • 媒体流(RTP):实际的音频字节。客户的声音进 UniMRCP 做识别,合成的声音从 UniMRCP 回来。
  • 控制流(MRCP):FreeSWITCH 遥控 UniMRCP 的指令——SPEAK、RECOGNIZE 和事件结果。

而 FreeSWITCH ↔ LLM 网关走的是普通 HTTP/WebSocket,跟电话协议栈完全无关。LLM 那一层,就是个普通的后端服务。


四、一次对话在时序上怎么转

架构图是静态的,真正难理解的是动态——一次"客户说一句、系统答一句"里,谁在等谁。以经典架构为例:

一次 LLM 语音对话的完整时序

拆开这一圈:

  1. 放欢迎语:FreeSWITCH 通过 mod_unimrcp 向 UniMRCP 发 SPEAK,UniMRCP 合成"您好,请问有什么可以帮您?",RTP 回送放给客户。
  2. 开始听:FreeSWITCH 发 RECOGNIZE,UniMRCP 开始识别。客户开口,UniMRCP 发 START-OF-INPUT;客户说完,发 RECOGNITION-COMPLETE,带着识别文本回来。
  3. 问大脑:FreeSWITCH 把识别文本(加上会话 ID)POST 给 LLM 网关。网关拼 prompt、调 LLM。若 LLM 决定要查数据,先做一次工具调用(比如查账务),拿到结果再让 LLM 生成最终回复。
  4. 说出来:网关把回复文本返回,FreeSWITCH 再发一次 SPEAK,UniMRCP 合成、放音。
  5. 循环:回到第 2 步,进入下一轮,直到客户挂断或转人工。

这一圈里藏着整个系统的性能命门:客户说完话,到听见系统回话,中间要串起 ASR 收尾 + LLM 推理(可能还有一次工具调用)+ TTS 合成。任何一环慢,客户都会觉得"这机器怎么反应这么迟钝"。人对电话里沉默的容忍度极低——超过一秒的静默就开始别扭。所以下一节的工程难点,几乎全是围着"延迟"打转。


五、真正的难点在工程,不在拼装

把三个组件连起来不难,难的是让它像个正常人一样对话。几个绕不过去的坎:

1. 延迟:能流式就别等整句

最要命的问题。串行等"识别完 → 想完 → 合成完"轻松破 2 秒。对策是全链路流式:

  • ASR 流式:别等客户彻底说完才出结果,边说边出中间结果,靠端点检测(endpointing)尽早判断"这句说完了"。
  • LLM 流式:开启 streaming,LLM 吐出第一个句子就往下传,不等整段生成完。
  • TTS 流式:LLM 吐一句,TTS 就合成一句、放一句,而不是攒齐全文再开口。

流式做好,客户的体感延迟能从"整句时间"压到"第一句话的时间"。这是语音智能体能不能用的分水岭。

2. 打断(barge-in):让客户能插嘴

真人对话里,你话没说完对方就接上了,很正常。系统正在念一长串,客户不耐烦想打断——这时 UniMRCP 会发来 START-OF-INPUT。FreeSWITCH 收到就得立刻停掉 TTS(靠 Kill-On-Barge-In 头或主动 STOP),切换到识别。

barge-in 处理不好,机器自顾自念、客户干着急,是语音 IVR 最劝退的体验之一。这活儿在 FreeSWITCH 侧,和 LLM 无关——别指望 LLM 帮你处理。

3. 工具调用:让它查到真数据

LLM 光会聊天没用,contact center 要的是"真能办事"。通过 function calling,LLM 可以调账务查询、订单状态、工单创建这些真实接口。这一步在 LLM 网关里做:LLM 说"我要调 query_balance(user_id)",网关执行、拿结果、再喂回 LLM 生成最终答复。

注意时序:一次工具调用会往对话回路里塞进一段额外延迟。所以工具接口本身要快,慢接口要么加缓存,要么在等待时先给客户一句"稍等,正在帮您查"。

4. 幻觉与兜底:别让它瞎编

LLM 会一本正经地胡说。在客服场景,编错一个金额、承诺一个不存在的优惠,都是事故。几条底线:

  • 涉及金额、余额、订单这类事实,必须来自工具调用的真实数据,不许 LLM 自由发挥。
  • 系统 prompt 里划死边界:不知道就说不知道,该转人工就转人工。
  • 置信度 + 兜底路径:ASR 置信度低、LLM 反复答非所问,要能干脆利落转人工,别在死循环里耗着客户。

六、经典架构 vs 端到端语音大模型

前面一直在讲"经典栈 + LLM 大脑"。但最近还有另一条路值得知道:端到端语音大模型(speech-to-speech,如各家的 Realtime API)——音频直接进模型,模型直接出音频,ASR/TTS 被内化进模型里。

两条路各有取舍:

维度 经典栈 + LLM 大脑(主推) 端到端语音大模型
组件 FreeSWITCH + UniMRCP + LLM,分层清晰 电话侧 + 一个 S2S 模型
ASR/TTS 独立可替换,各家引擎自由选 内化进模型,选择受限
延迟 需自己做全链路流式优化 天然更低(少了组件间往返)
可控性 高——每一环都能插日志、加规则、做兜底 低——中间是个黑盒
成本 引擎和模型分开计费,可各自优化 通常更贵,按语音时长计
存量改造 友好——复用现有电话系统 往往要重搭媒体接入
情感/自然度 受 TTS 上限约束 语气、停顿、情绪更自然

一句话选型:

有存量 contact center、要可控可审计、要自由选引擎 → 经典栈 + LLM 大脑。 追求极致自然的对话体验、能接受黑盒和更高成本 → 端到端语音大模型。

这两条路不是你死我活。现实里很多团队用经典栈打底(稳、可控、能兜底),在高价值场景试点端到端。架构选型从来不是选"最先进的",而是选"最匹配你约束的"。


七、落地实践

给一条能照着走的路径。

dialplan 的骨架

FreeSWITCH 侧的核心就是 speak(合成) 和 detect_speech(识别)两类应用,配合 mod_unimrcp 指向 UniMRCP。示意骨架(实际语法以你的 FreeSWITCH 版本为准):

<!-- 放欢迎语:通过 unimrcp TTS profile 合成 -->
<action application="speak"
        data="unimrcp:my_tts_profile|voice_name|您好,请问有什么可以帮您?"/>

<!-- 开始识别:通过 unimrcp ASR profile,启用 barge-in -->
<action application="detect_speech"
        data="unimrcp my_asr_profile ..."/>

<!-- 拿到识别结果后,交给脚本去调 LLM 网关 -->
<action application="lua" data="voice_agent.lua"/>

真正的对话循环建议用脚本(Lua / ESL)写,而不是纯 XML dialplan——因为要和 LLM 网关做 HTTP 交互、维护多轮上下文,XML 表达不了这种逻辑。

LLM 网关放哪、干什么

单独起一个薄服务,职责很清楚:

  1. 接收 FreeSWITCH 传来的 {session_id, 识别文本}。
  2. 按 session_id 取出对话历史,拼 prompt(系统人设 + 历史 + 可用工具定义)。
  3. 调 LLM,开启流式;处理 function calling(执行工具、把结果喂回)。
  4. 把最终回复文本(最好分句流式)返回给 FreeSWITCH。

为什么要单独一层而不是让 FreeSWITCH 直接调 LLM:解耦。换模型、改 prompt、加工具、做限流和审计,全在这一层动,电话侧不用碰。

集成 Checklist

从零到打通第一通"能对话"的电话:

  • [ ] FreeSWITCH + mod_unimrcp 装好,能连上 UniMRCP
  • [ ] UniMRCP 挂上真实(或 demo)的 ASR / TTS 引擎,单独验证识别和合成
  • [ ] dialplan 里 speak / detect_speech 跑通,能放音、能出识别文本
  • [ ] LLM 网关起好,能吃文本、吐文本,流式打开
  • [ ] 对话脚本(Lua/ESL)把"识别文本 → LLM → 合成"串成循环
  • [ ] barge-in 验证:念长句时能被打断
  • [ ] 延迟验证:客户说完到听见回话,压进 1.5 秒以内(靠全链路流式)
  • [ ] 工具调用:LLM 能查到至少一个真实业务数据
  • [ ] 兜底路径:识别失败 / LLM 跑偏能干脆转人工
  • [ ] 可观测:每通对话的 ASR 文本、LLM 输入输出、各环节耗时都有日志
  • [ ] 生产环境 MRCP 控制通道走 TLS

总结:LLM 不是替换这套栈,而是给它装上大脑

回到开头那个别扭的 IVR。它别扭不是因为技术不行——FreeSWITCH 接通、UniMRCP 识别,全都工作正常。它别扭,是因为中间那颗脑子只认关键词。

三句话记住:

  • 分工不变:FreeSWITCH 管呼叫、UniMRCP 管 ASR/TTS、LLM 管理解和生成,各守边界。LLM 全程只碰文本,所以能自由替换、和电话链路解耦。
  • 难点在工程:延迟(全链路流式)、打断(FreeSWITCH 侧处理)、工具调用(查真数据)、幻觉兜底——把这四个填平,才算能用。
  • 路径最省:不用推翻存量电话系统,dialplan 改几行 + 加个 LLM 网关,就从"关键词 IVR"升级成"能对话的智能体"。

为什么说它有前途?因为它把"听懂人话"的门槛和成本一起打下来了。过去要让 IVR 听懂一个新说法,得改语法、测试、上线;现在客户怎么说都行,脑子在 prompt 和工具里,迭代快得多。对客服、外呼、回访、预约这些高频语音场景,这是实打实的降本增效。

下一步很自然:装一份 FreeSWITCH + UniMRCP,先用 demo 引擎跑通一次识别和合成,再接一个 LLM 网关,让它听懂你随口说的第一句话。这套栈的每一块都是开源的、成熟的,现在缺的那颗大脑,恰好是这两年最不缺的东西。

全文思维导图

@startmindmap
<style>
mindmapDiagram {
  node {
    BackgroundColor #F8F9FA
    RoundCorner 10
    Padding 10
    FontSize 13
  }
  :depth(0) {
    BackgroundColor #1E3A5F
    FontColor white
    FontSize 18
    FontStyle bold
  }
  :depth(1) {
    FontSize 15
    FontStyle bold
  }
  :depth(2) {
    FontSize 13
  }
}
</style>

* 语音智能体
** 三者分工
*** FreeSWITCH 管呼叫
*** UniMRCP 管 ASR/TTS
*** LLM 管理解与生成
*** LLM 只碰文本,解耦
** 架构演进
*** 一代:按键 IVR
*** 二代:固定语法语音 IVR
*** 三代:LLM 做大脑
*** 骨架不变,插进 LLM
** 一次对话时序
*** SPEAK 放音
*** RECOGNIZE 识别
*** 问 LLM (可带工具调用)
*** SPEAK 说出回复
*** 循环直到挂断/转人工
** 工程难点
*** 延迟:全链路流式
*** 打断:FreeSWITCH 侧处理
*** 工具调用:查真数据
*** 幻觉兜底:该转人工就转
** 两种架构
*** 经典栈 + LLM 大脑 (主推)
*** 端到端语音大模型
*** 选匹配约束的,不选最新的
** 落地
*** dialplan: speak / detect_speech
*** LLM 网关单独一层
*** 集成 checklist
@endmindmap

语音智能体 - 思维导图

参考资料


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。 欢迎在我的个人网站 https://www.fanyamin.com 访问原文并评论。