news 2026/8/16 7:41:55

Voice Agent 实战:用 OpenAI Realtime API + Twilio 复刻一个“全双工”的 AI 电话客服

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Voice Agent 实战:用 OpenAI Realtime API + Twilio 复刻一个“全双工”的 AI 电话客服

🚀 前言:为什么现在的电话机器人这么“傻”?

如果你接过营销电话,那种机械感是掩盖不住的。传统的架构是ASR (识别) -> NLP (处理) -> TTS (合成)的“回合制”游戏。

OpenAI Realtime API (GPT-4o Audio)将这三步合二为一。它直接处理音频输入,直接输出音频,中间不再有文本转换的损耗。这不仅让延迟降低到了毫秒级,更重要的是:它可以听出你的语气(愤怒、犹豫),也能用带有情感的语调回复你。

今天,我们将构建这样一个系统:

  1. 用户:拨打一个真实的电话号码。
  2. Twilio:接听电话,并将音频流(Media Stream)通过 WebSocket 推送给我们的服务器。
  3. Server:作为中继,将 Twilio 的音频转发给 OpenAI,并将 OpenAI 的回复音频转发回 Twilio。
  4. OpenAI:实时思考并说话。

🏗️ 一、 架构设计:WebSocket 是核心

这是一个典型的**双向流式(Bi-directional Streaming)**架构。

数据流向图 (Mermaid):

中继服务器 (Python/Node)

WebSocket (G.711音频)

WebSocket (PCM音频)

用户 (PSTN电话)

Twilio 电话网关

中间件逻辑

OpenAI Realtime API

关键难点

  • 协议转换:Twilio 输出的是mulaw格式音频(Base64 编码),OpenAI 需要的是pcm16
  • 打断机制 (Interruption):当用户说话时,服务器必须通过 OpenAI 的input_audio_buffer.speech_started事件,立刻发送指令告诉 Twilio“清空播放缓存”,实现打断效果。

🛠️ 二、 准备工作

  1. OpenAI API Key:需要有访问gpt-4o-realtime-preview的权限。
  2. Twilio 账号:注册并购买一个电话号码(试用号也可以,大概 $1)。
  3. 公网服务器:或者使用ngrok将本地端口暴露到公网(Twilio 需要回调)。

📞 三、 第一步:配置 Twilio TwiML

当电话打进来时,我们需要告诉 Twilio:“别自己处理,把音频流通过 WebSocket 扔给我的服务器。”

在 Twilio 后台创建一个TwiML Bin,或者直接在代码中返回以下 XML:

<Response><Connect><Streamurl="wss://your-domain.com/media-stream"><Parametername="customerId"value="12345"/></Stream></Connect></Response>

🐍 四、 第二步:编写中继服务器 (Python FastAPI)

我们需要一个能够同时处理Twilio WebSocketOpenAI WebSocket的服务。

核心代码 (server.py):

importosimportjsonimportasyncioimportwebsocketsfromfastapiimportFastAPI,WebSocketfromstarlette.websocketsimportWebSocketDisconnect# 你的 OpenAI KeyOPENAI_API_KEY=os.getenv("OPENAI_API_KEY")VOICE="alloy"# AI 的声音SYSTEM_PROMPT="你是一个专业、幽默的电话客服。请用中文简短回答。"app=FastAPI()@app.websocket("/media-stream")asyncdefhandle_media_stream(websocket:WebSocket):awaitwebsocket.accept()print("Twilio 连接成功")# 1. 连接 OpenAI Realtime APIurl="wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01"headers={"Authorization":f"Bearer{OPENAI_API_KEY}","OpenAI-Beta":"realtime=v1",}asyncwithwebsockets.connect(url,extra_headers=headers)asopenai_ws:# 2. 初始化 Sessionsession_update={"type":"session.update","session":{"voice":VOICE,"instructions":SYSTEM_PROMPT,"input_audio_format":"g711_ulaw",# Twilio 默认格式"output_audio_format":"g711_ulaw","turn_detection":{"type":"server_vad"}# 开启服务端语音活动检测}}awaitopenai_ws.send(json.dumps(session_update))# 3. 定义双向转发任务stream_sid=Noneasyncdefreceive_from_twilio():nonlocalstream_sidtry:whileTrue:message=awaitwebsocket.receive_text()data=json.loads(message)ifdata["event"]=="media":# 收到 Twilio 音频 -> 转发给 OpenAIaudio_append={"type":"input_audio_buffer.append","audio":data["media"]["payload"]}awaitopenai_ws.send(json.dumps(audio_append))elifdata["event"]=="start":stream_sid=data["start"]["streamSid"]print(f"Stream 开始:{stream_sid}")exceptWebSocketDisconnect:print("Twilio 断开连接")asyncdefreceive_from_openai():try:asyncformessageinopenai_ws:response=json.loads(message)# A. 收到 AI 音频 -> 转发给 Twilio 播放ifresponse["type"]=="response.audio.delta"andresponse.get("delta"):audio_payload={"event":"media","streamSid":stream_sid,"media":{"payload":response["delta"]}}awaitwebsocket.send_text(json.dumps(audio_payload))# B. 关键点:用户打断处理# 当 OpenAI 检测到用户开始说话时,我们需要让 Twilio 立刻闭嘴ifresponse["type"]=="input_audio_buffer.speech_started":print("检测到用户插话,清空播放缓存...")clear_msg={"event":"clear","streamSid":stream_sid,}awaitwebsocket.send_text(json.dumps(clear_msg))# 同时也告诉 OpenAI 别继续生成刚才没说完的话了awaitopenai_ws.send(json.dumps({"type":"response.cancel"}))exceptExceptionase:print(f"OpenAI 错误:{e}")# 并发运行两个任务awaitasyncio.gather(receive_from_twilio(),receive_from_openai())if__name__=="__main__":importuvicorn# 必须运行在 0.0.0.0 才能被 ngrok 访问uvicorn.run(app,host="0.0.0.0",port=5000)

⚡ 五、 关键技术点解析

1. 为什么不需要转码?

OpenAI Realtime API 最近更新支持了g711_ulaw格式。这正是传统电话网络(PSTN)使用的格式。

  • 以前:Twilio (ulaw) -> Server (转pcm) -> LLM (文本) -> TTS (pcm) -> Server (转ulaw) -> Twilio。
  • 现在:Twilio (ulaw) -> OpenAI (ulaw) -> Twilio。
    这省去了大量的编解码 CPU 开销和延迟。
2. VAD(语音活动检测)的妙用

在代码中,input_audio_buffer.speech_started是核心。
旧的电话机器人最傻的地方就是“抢话”。
有了这个事件,只要用户发出一声“哎等等”,OpenAI 毫秒级检测到,通过response.cancel和 Twilio 的clear指令,机器人会瞬间闭嘴,等待你的新指令。

3. 工具调用 (Function Calling)

Realtime API 同样支持 Function Calling。
你可以在session.update中定义一个check_order_status工具。当用户问“我的快递到哪了”,OpenAI 会暂停生成音频,向你的 Server 发送函数调用请求,你查库后返回结果,OpenAI 再把结果念给用户听。
这一切都在同一个 WebSocket 连接中完成。


🎯 总结

通过OpenAI Realtime API + Twilio,我们构建的不再是一个简单的“IVR 语音导航”,而是一个具有类人交互能力的数字员工。

它没有明显的延迟,它可以被打断,它甚至能听出你感冒了并表示关心。
语音交互(Voice UI)的 iPhone 时刻,可能真的已经到来了。

Next Step:

  1. 申请一个 Twilio 号码。
  2. ngrok http 5000把你的服务暴露出去。
  3. 把 Twilio 的 Voice Webhook 指向你的wss://xxxx.ngrok-free.app/media-stream
  4. 打个电话过去,感受一下未来的样子。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 23:50:02

YOLO与Redis缓存集成:加速高频请求的响应时间

YOLO与Redis缓存集成&#xff1a;加速高频请求的响应时间 在智能监控中心的大屏前&#xff0c;运维人员发现某条产线的视觉质检接口突然出现延迟飙升——每秒数百次的重复图像请求正不断冲击着后端模型服务。GPU利用率一度冲上98%&#xff0c;而检测结果却几乎完全相同。这并非…

作者头像 李华
网站建设 2026/8/8 2:56:40

YOLO目标检测中的上下文信息利用:提升复杂场景表现

YOLO目标检测中的上下文信息利用&#xff1a;提升复杂场景表现 在智能摄像头遍布工厂车间、自动驾驶车辆穿梭于城市街巷的今天&#xff0c;一个共同的技术挑战浮出水面&#xff1a;如何让AI“看得更明白”&#xff1f;尤其是在目标密集、遮挡严重或背景干扰强烈的复杂场景中&am…

作者头像 李华
网站建设 2026/8/3 21:03:36

YOLO与JWT令牌验证:确保每次调用的身份合法性

YOLO与JWT令牌验证&#xff1a;构建安全高效的视觉AI服务 在智能制造车间的边缘服务器上&#xff0c;一台搭载YOLO模型的视觉检测系统正以每秒30帧的速度分析产线上的产品缺陷。与此同时&#xff0c;来自不同车间的数十个终端设备轮番发起调用请求——如果没有一套可靠的身份验…

作者头像 李华
网站建设 2026/8/12 23:10:19

YOLO模型上线前的压力测试:高并发请求如何扛住?

YOLO模型上线前的压力测试&#xff1a;高并发请求如何扛住&#xff1f; 在智能制造工厂的质检线上&#xff0c;数百个摄像头正以每秒30帧的速度持续拍摄产品图像&#xff1b;城市的安防中心里&#xff0c;成千上万路视频流同时触发AI检测任务&#xff1b;自动驾驶车辆穿梭于复…

作者头像 李华
网站建设 2026/8/2 10:43:08

YOLO目标检测中的类别不平衡问题及解决方案

YOLO目标检测中的类别不平衡问题及解决方案 在工业质检线上&#xff0c;一台高速运转的摄像头每秒拍摄数百张PCB板图像。系统使用YOLOv8进行缺陷检测——理论上&#xff0c;这应该是一个成熟可靠的流程。但几周后工程师发现&#xff1a;尽管整体准确率高达92%&#xff0c;产线仍…

作者头像 李华