用 ElevenLabs 和 Twilio 20 分钟搭建语音智能体
- 发布时间
- 最近更新
语音智能体可以接听呼入电话,通过 语音转文本 (STT)实时转录来电者语音,使用大语言模型(LLM)生成回复,再通过 文本转语音 (TTS)模块回复。使用 ElevenLabs 和 Twilio,大约 20 分钟即可让智能体在真实电话号码上运行。
开发所用的完整技术栈包括:ElevenLabs 负责语音合成(Flash v2.5)和转录(Scribe v2 Realtime),Twilio 负责电话通信,OpenAI 或 Anthropic 作为 LLM。不过这些组件都可替换,你可以选择最熟悉的组件。
本文介绍如何使用 Node.js 和 Typescript,在 20 分钟内构建语音智能体。如果希望使用托管方案,无需自行维护整条级联链路,同时处理轮次管理、打断和电话通信,可前往 ElevenAgents。
语音智能体架构如何运作
在编写代码前,先了解技术栈中的 3 项服务如何连接会很有帮助。
- Twilio: 处理电话通话和音频传输。
- ElevenLabs: 通过 Scribe v2 Realtime 处理 STT,通过 Flash v2.5 处理 TTS。
- LLM: 处理 工具调用 和回复生成。
每个阶段都是轻量适配器,因此无需改动其余部分,即可替换其中一项服务。例如,可以将 OpenAI LLM 换成 Anthropic,而无需重写其他组件。
电话通过 Twilio 接入服务器。Twilio 接听 PSTN 电话,向服务器建立 WebSocket,并将来电者音频以 base64 编码的 mu-law 帧流形式转发。服务器运行级联链路,并通过同一个 WebSocket 回传合成音频,由 Twilio 播放给来电者。

以下是构建语音智能体所用的流程:
来电者拨打 Twilio 号码。Twilio 从 webhook 获取一份 TwiML 文档。TwiML 会指示 Twilio 向 WebSocket 端点打开 Media Stream。Twilio 将呼入音频作为 JSON 事件流传输,其中包含 base64 编码的 mu-law(ulaw_8000)负载。
服务器将音频块转发给 Scribe v2 Realtime 进行流式转录。来电者一轮发言结束后,将转录文本发送给 LLM,再使用 Flash v2.5 以 ulaw_8000 合成回复。随后通过 WebSocket 将 base64 编码的合成 mu-law 帧发回 Twilio,由其播放给来电者。
Scribe v2 Realtime 输出部分转录的延迟约为 150ms,Flash v2.5 的模型推理约为 75ms,不含网络和应用延迟。LLM 是首段音频生成时间中最大且最难预测的影响因素,占据了大部分延迟预算。为缩短等待时间,我们逐 token 流式传输 LLM 输出,并在模型完成整句前开始合成。
如需了解这些选择背后的模型权衡,请参阅 模型概览 及 延迟解析。
构建语音智能体前需要准备什么
本指南假设已准备好 4 项内容。每项都能快速完成设置,但缺少任何一项都会导致服务器无法运行。
请检查以下前提条件:
- 支持 Voice 功能的 Twilio 电话号码:记下该号码,以及 Twilio 控制台中的 Account SID 和 Auth Token。
- ElevenLabs API 密钥:在 ElevenLabs 控制面板中创建。密钥通过 xi-api-key 请求头传递,属于机密信息,只应保存在服务器端。参阅 API 身份验证。
- LLM API 密钥:本教程将 Anthropic Claude 和 OpenAI 视为可互换的后端,任选其一即可。
- 用于本地开发的 Ngrok(或其他隧道): Twilio 必须通过公开的 HTTPS 和 WSS URL 访问服务器,ngrok 无需部署即可提供该能力。
将机密设置为环境变量,切勿提交到代码仓库。
然后启动一个指向服务器端口的隧道:
了解 Twilio Media Streams 协议
Twilio 不会提供原始音频 socket,而是通过 WebSocket 使用结构化 JSON 协议封装所有内容。了解 4 种事件类型和发送格式后,就能在编写前完全理解步骤 2 中的 WebSocket 处理器。
Twilio 连接到 WebSocket 后,会发送一系列 JSON 文本消息,共有 4 种事件类型。
connected 事件最先到达,确认 WebSocket 已建立。媒体流开始时会发送一次 start 事件;其中包含必须保存的 streamSid,以便回传音频,还包含位于 start.customParameters 和 start.callSid 下的通话元数据。
media 事件会持续出现:media.payload 是一段 base64 编码的 8kHz mu-law 音频,每帧 20ms;来电者音频的 media.track 为 inbound。最后,媒体流结束时会发送 stop,通常是因为通话已挂断。
要播放回传音频,需发送类型为 media 的消息,其中包含相同的 streamSid 和 base64 mu-law 负载。要打断已排队的音频,需发送带有 streamSid 的 clear 消息,以清空 Twilio 的出站缓冲区。
呼入和呼出编码相同(ulaw_8000)。我们向 ElevenLabs 文本转语音请求 ulaw_8000,并直接将字节转发给 Twilio,无需中间重采样。
步骤 1:提供 TwiML webhook
来电时,Twilio 会向 webhook 发起 HTTP 请求,而你需要返回 TwiML,将通话连接到 Media Stream。<Connect><Stream> 指令会建立双向 WebSocket。这里应使用 <Connect> 而非 <Start>:它会在流持续期间保持通话,并允许回传音频,这正是此设置的目的。
webhook 返回的 TwiML 如下:
在 Express 中,只需一个 POST 处理器来填入主机地址并返回该文档:
在 Twilio 控制台中,将号码的“有来电时” webhook 设置为 https://your-subdomain.ngrok.app/incoming-call,并使用 HTTP POST。
步骤 2:接收 Media Stream WebSocket
WebSocket 处理器读取 Twilio 事件、驱动级联链路并回传音频。
我们会保存少量每通电话状态:streamSid、STT 连接,以及标记智能体是否正在说话的标志。处理器会将每个呼入媒体帧从 base64 解码,并将原始 mu-law 字节转发给 STT:
步骤 3:使用 Scribe v2 Realtime 转录
Scribe v2 Realtime 接收流式音频块并返回部分和最终转录结果,同时直接支持 mu-law 编码,因此可以原样输入 Twilio 帧。
它还提供基于静音分段的语音活动检测,以及用于完成分段的手动提交控制。对于 电话智能体,由 VAD 驱动的分段通常是正确选择,因为自然停顿是判断来电者一轮发言结束的最可靠信号。
具体步骤如下:通话开始时打开 STT 流;推送每个呼入 mu-law 块;在转录最终完成时调用 LLM。
实时 STT 客户端接口仍在演进,因此下方结构通过一个小型 TypeScript 适配器(openRealtimeStt)实现,应根据实时 API 而非固定字段名进行开发。将 onFinal 视为把完整来电者发言交给下一阶段的钩子。
实时识别的部分结果延迟约为 150ms,可缩短来电者说完到智能体开始回应之间的感知间隔。如需了解批量对应功能和完整功能集,请参阅 语音转文本文档 和 实时语音转文本 产品页面。
步骤 4:使用 LLM 生成回复
这一阶段生成回复。LLM 接收对话历史并返回助手文本。以流式方式输出回复,即可在第一句时开始合成。
这里使用 OpenAI:
上方模型 ID gpt-4.1-mini 是低延迟选择之一;Anthropic 的 claude-haiku-4-5 是类似选项。两家提供商都可实现相同的 llmReply 契约;只需替换函数体,智能体其余部分无需改动。
系统提示词会限制回复长度,这对电话场景很重要:过长的回复会显得缓慢,也不便自然打断。
步骤 5:使用 ulaw_8000 通过 Flash TTS 合成
现在需要将文本转为 Twilio 可播放的音频。请求 Flash v2.5 时使用 outputFormat: "ulaw_8000",使字节匹配 Twilio 所需编码,然后流式传输音频,并通过 WebSocket 将每个块作为 media 事件回传。
将 LLM token 累积成句子大小的片段,每完成一个片段就进行合成,而非等待完整回复。这样能缩短首段音频生成时间:模型还在生成第二句时,来电者已能听到第一句。如需更精细地控制增量合成,实时 TTS WebSocket 指南介绍了如何将文本输入单个已打开的合成 socket;下方 HTTP 流式方案更简单,足以处理简短对话轮次。
强化 AI 语音智能体以用于生产环境
完成以上 5 个步骤后,你已有一个可用的智能体,但这不等同于生产环境部署。
在将智能体接入真实电话线路前,还需注意以下问题。
验证 Twilio webhook 签名
任何知道 webhook URL 的人都能向其发送 POST 请求,因此首要任务是确认请求确实来自 Twilio。Twilio 会使用 Auth Token 在 X-Twilio-Signature 请求头中为每个请求签名,应拒绝所有未通过验证的请求。签名基于完整 URL 和 POST 参数计算,因此必须按与 Twilio 相同的方式计算。
Twilio 的辅助工具可为你完成此操作:
妥善管理机密
将 ELEVENLABS_API_KEY、LLM 密钥和 TWILIO_AUTH_TOKEN 保存在机密管理器中,不要放在源代码中,也不要放在提交到仓库的明文 env 文件中。将 ElevenLabs 密钥的权限限定为此服务所需端点,并设置额度配额,使泄露影响范围受限。
企业版计划还可通过 IP 白名单将密钥限制在特定 IP 范围内。此服务器直接使用 API 密钥,因为密钥不会离开后端;如果将任何音频逻辑移至浏览器或移动客户端,则应改用一次性令牌,避免在客户端暴露密钥。
了解并发限制
每个计划都设有并发限制,且不同模型系列的限制不同;该限制计算同时处于音频生成状态的请求数。
对于电话智能体,这种计算方式很有利。音频生成比播放快,因此每通电话仅在合成回复的短暂窗口占用 TTS 并发,而非整段通话期间。粗略来说,并发限制约为 5 时,可支持约 100 通同时进行的对话式通话,因为生成会远早于播放结束。
不过,应监控余量而非凭猜测判断。ElevenLabs 响应会提供 current-concurrent-requests 和 maximum-concurrent-requests 请求头;记录这些值,并在接近上限时发出告警。超出限制后,请求会按优先级排队,通常增加约 50ms 延迟;持续过载则会返回 HTTP 429。
处理 HTTP 429 响应时采用短暂退避。若持续出现,可在定价页面升级以提高限制;企业版客户则可联系客户经理。
处理插话和打断
智能体说话时,如果来电者开始讲话,会期待智能体停止。这称为插话,正确处理它是让智能体自然、不像脚本的重要部分。
使用 STT VAD 信号在智能体播放期间检测来电者语音。检测到后执行两项操作:首先,停止转发 TTS 块;speak 中的 agentSpeaking 标志已通过跳出循环实现此功能。其次,向 Twilio 发送 clear 消息,清空其端已排队的音频。
如果跳过 clear,即使停止发送音频,Twilio 仍会继续播放已缓冲的音频,因此智能体看起来会盖过来电者说话。
记录、监控并优雅地处理失败
为每个阶段添加监测,以便在通话感觉缓慢时定位延迟来源。记录从最终转录到第一个 LLM token、从第一个 LLM token 到第一个 TTS 字节,以及从第一个 TTS 字节到发送至 Twilio 的帧之间的时间。大部分可变延迟通常出现在 LLM 阶段;STT 和 TTS 阶段则相对稳定。
还要为局部失败做好准备。LLM 可能超时,STT 流可能中断;根据地理位置不同,经公共互联网到 ElevenLabs 的网络往返时间约为 20 至 200ms。服务器应部署在靠近来电者的位置,而不只是靠近 ElevenLabs,因为 ElevenLabs 已会路由至北美、欧洲和东南亚集群中最近的节点。
某一阶段失败时,不要让来电者陷入静默:合成一句简短的回退提示(“抱歉,请再说一遍好吗?”),并保持通话。为每个阶段设置超时和 try/catch,避免单轮失败关闭整个 WebSocket。
上线前,还应设置以下默认项:
- 如上所示,在系统提示词中限制回复长度,使每轮简短且可打断。
- 限制对话历史,避免长时间通话导致 LLM 上下文无限增长。
- 设置最大通话时长,防止卡住的会话持续占用并发。
如需继续优化可控部分,延迟文档 说明了首段音频生成时间的来源,模型概览 介绍速度与质量的权衡,而 实时 TTS WebSocket 指南 则展示如何通过增量文本输入进一步降低合成延迟。
使用 ElevenAPI 构建生产级语音智能体
20 分钟后,你已具备生产级 语音智能体 的每一层能力。Twilio 负责电话通信,Scribe v2 Realtime 负责转录,LLM 生成回复,Flash v2.5 则通过同一个 WebSocket 进行语音回复。
如果不想自行维护级联链路,ElevenAgents 可提供托管服务,包括轮次管理、打断处理和电话通信集成,底层使用的正是刚才手动连接的相同模型。
如需继续优化可控的技术栈,可查看 ElevenAPI 产品页面,了解计划、并发限制和 声音库。或者,注册,立即开始拨打第一通电话。



