背景问题:语音 Agent 的难点不是”能说话”,而是”能接住真实对话”
文本 Agent 的主要问题通常发生在上下文、工具参数和输出格式上。实时语音 Agent 多了一层更难控制的变量:用户不是按回车提交请求,而是在噪声、停顿、改口、插话、犹豫和情绪中持续说话。
一个演示版语音助手可以只做三件事:录音、发给模型、播放回复。但一旦进入客服、销售、教育、会议、车载或内部运维场景,系统就会遇到更具体的问题:用户还没说完模型就抢答;用户插话后旧回复仍在播放;噪声被当成新问题;工具调用已经开始但用户临时改口;转写和音频回答不一致;通话结束后没有可审计的证据链。
因此,实时语音 Agent 的生产治理重点不是单纯选择一个语音模型,而是围绕 传输、端点检测、打断恢复、转写缓冲、工具调用、人工接管和审计日志 建一条可控链路。
先做架构选择:speech-to-speech 与串联管线
OpenAI 的 Voice Agents 文档把语音 Agent 分成两类:一种是 live audio 的 speech-to-speech 会话,由模型直接处理实时音频输入和输出;另一种是显式串联 speech-to-text、文本 Agent、text-to-speech 的 chained voice pipeline。前者适合自然、低延迟、支持 barge-in 的对话;后者适合需要可见中间文本、审批、策略检查和可替换模块的工作流。
生产选型可以按下面的边界判断:
| 场景类型 | 推荐架构 | 原因 |
|---|---|---|
| 陪练、导购、问答、轻量客服、语音搜索、低风险助理 | speech-to-speech | 低延迟、自然打断、统一会话事件 |
| 金融、医疗、政企客服、投诉处理、工单变更、身份核验 | 串联管线 | 可见中间文本、策略检查、审批、审计 |
| 混合场景 | 普通问答走 speech-to-speech,高风险动作走文本化确认 | 兼顾体验与安全 |
WebRTC、WebSocket 与 SIP:不要把传输层混在一起治理
实时语音 Agent 至少有三种常见接入方式:
- WebRTC:适合浏览器和移动端直接采集、播放音频。OpenAI 的 Realtime WebRTC 文档示例中,浏览器创建
RTCPeerConnection,采集麦克风音轨,建立数据通道,并通过应用服务器完成 SDP 交换。优势是低延迟、天然面向实时媒体、浏览器支持较好。 - WebSocket:更适合服务端已经拿到原始音频流的场景,比如电话系统、会议录制、边缘网关或媒体 worker。Google Gemini Live API 也使用有状态 WebSocket 连接,支持音频、图像和文本输入。
- SIP:更适合传统电话网络,通常是呼叫中心、语音网关或云通信平台集成的一部分。
工程上要避免一个误区:不要把 WebRTC、WebSocket、SIP 当成只是”连接方式不同”。它们决定了鉴权边界、日志采集方式、断线恢复策略、媒体质量指标、网络问题定位和安全模型。
VAD 是语音 Agent 的节拍器
VAD(Voice Activity Detection) 用于判断用户何时开始说话、何时结束一轮发言。OpenAI Realtime VAD 文档说明,启用 VAD 后系统会发送 input_audio_buffer.speech_started 和 input_audio_buffer.speech_stopped 之类事件,应用可以据此管理会话状态或按片段处理转写。
VAD 不是一个简单开关,而是语音 Agent 的节拍器。它直接影响三件事:
- 首包响应延迟:如果系统等待太久才认为用户说完,用户会觉得助手反应慢。
- 截断风险:如果系统过早判断结束,用户的后半句话会被切掉,模型容易基于半句话回答。
- 打断体验:如果用户插话时系统不能及时识别,旧回复会继续播放,造成”机器不听人说话”的体验。
OpenAI 文档里区分了 server_vad 和 semantic_vad。server_vad 基于静音片段切分音频,可以调 threshold、prefix_padding_ms、silence_duration_ms;semantic_vad 则根据用户话语是否语义完成来判断是否结束,有助于减少模型过早打断用户。
生产建议是把 VAD 参数按场景分组,而不是全局固定一套。例如:
{
"quiet_office": {
"type": "server_vad",
"threshold": 0.45,
"prefix_padding_ms": 300,
"silence_duration_ms": 450
},
"noisy_call_center": {
"type": "server_vad",
"threshold": 0.65,
"prefix_padding_ms": 400,
"silence_duration_ms": 700
},
"slow_speaker_or_elderly_user": {
"type": "semantic_vad",
"eagerness": "low"
}
}
这只是示例,不应直接照搬上线。真实参数要用实际录音样本、噪声、设备、语言、口音和业务容忍度压测。
打断恢复:barge-in 不是”停止播放”这么简单
实时语音产品经常强调 barge-in,即用户可以在模型说话时打断。Google Gemini Live API 也把 Barge-in 列为 Live API 的关键能力之一。
但工程上,barge-in 至少包含四个状态动作:
- 检测用户开始说话:依赖 VAD 或前端音频能量检测。
- 停止或淡出当前 TTS/模型音频输出:否则用户会听到旧答案继续播放。
- 取消或冻结当前 response:否则旧 response 可能继续生成文本、继续调用工具、继续写入状态。
- 恢复上下文:系统要知道哪些内容已经播放给用户、哪些内容只是生成但未播放、哪些工具调用已经执行、哪些工具调用必须撤销或补偿。
一个更稳妥的做法是把每轮语音交互拆成事件日志:
type VoiceTurnEvent =
| { type: "user_speech_started"; ts: number }
| { type: "user_speech_stopped"; ts: number; audioSegmentId: string }
| { type: "transcript_delta"; ts: number; text: string; final: boolean }
| { type: "assistant_audio_started"; ts: number; responseId: string }
| { type: "assistant_audio_played"; ts: number; responseId: string; offsetMs: number }
| { type: "user_barge_in"; ts: number; interruptedResponseId: string }
| { type: "response_cancelled"; ts: number; responseId: string }
| { type: "tool_call_requested"; ts: number; tool: string; argsHash: string }
| { type: "tool_call_confirmed"; ts: number; confirmationText: string }
| { type: "handoff_to_human"; ts: number; reason: string };
有了事件日志,打断恢复不再依赖”当前内存里是什么状态”,而是可以重建:用户在什么时间插话,助手说到了哪里,哪些内容用户已经听到,哪些动作还不能算完成。
转写缓冲:不要只保存最终文本
很多团队只保存最终转写文本,这在调试语音 Agent 时不够。实时语音系统更需要保存三类材料:
- 音频片段索引:不一定长期保存原始音频,但至少要能定位片段、时长、采样率、设备信息和噪声指标。
- 增量转写 delta:保存 partial transcript 与 final transcript 的差异。这样才能排查模型是否在 partial 文本上过早行动。
- 播放进度:记录助手音频实际播放到哪个 offset。模型生成了 30 秒音频,不代表用户听到了 30 秒。
如果业务涉及争议处理、客服质检或合规审计,转写缓冲还要和用户确认事件、工具调用参数、人工接管记录绑定在一起。否则事后只能看到”系统回答了什么”,看不到”用户当时到底听到了什么、确认了什么”。
工具调用:语音场景更需要确认层
实时语音 Agent 很容易把自然语言直接转成工具调用,例如查订单、改地址、取消服务、创建工单、发送短信或预约时间。问题是语音输入比文本输入更容易有误听、口误和上下文指代。
因此,工具调用建议分成三类治理:
| 风险等级 | 示例 | 策略 |
|---|---|---|
| 低风险 | 天气查询、知识查询、FAQ 检索 | 直接执行 |
| 中风险 | 查询账户、生成报价、创建草稿 | 可执行,但记录参数和来源转写 |
| 高风险 | 支付、删除、取消、退款、外呼、修改身份信息 | 必须二次确认,复述关键参数 |
确认时不要只问”是否确认”,而要复述关键参数,例如:“我将取消 7 月 8 日下午 3 点的预约,是否确认?”
可落地的工具策略可以写成配置:
tools:
lookup_order:
risk: low
require_confirmation: false
audit: transcript_and_args
update_shipping_address:
risk: high
require_confirmation: true
confirmation_fields:
- order_id
- new_address
audit: transcript_args_confirmation_audio_offset
refund_payment:
risk: critical
require_confirmation: true
require_human_review: true
audit: full_event_log
人工接管:不要等模型完全失败才转人工
语音 Agent 的人工接管要更主动。文本机器人可以让用户慢慢读错误提示,语音场景里用户的耐心更短,情绪变化更快。
建议设置几个硬触发条件:
- 连续两次 VAD 截断
- 连续两次 ASR 低置信
- 用户明确表达投诉、退款、紧急、风险、法律、医疗或身份相关问题
- 工具调用参数无法确认
- 模型两次要求用户重复同一信息
- 用户打断频率过高
人工接管不应只是”转客服”。更好的做法是把上下文包给人工坐席:最近 N 轮转写、用户确认过的参数、未完成工具调用、模型建议摘要、风险标签、音频片段索引。这样坐席接入时可以直接继续,而不是让用户重新说一遍。
适用场景
实时语音 Agent 适合对低延迟和自然交互有要求的场景,例如语音客服、售前导购、口语陪练、车载助手、会议助手、游戏 NPC、智能硬件、实时翻译和内部运维助手。
但它不适合一开始就覆盖所有复杂业务。涉及高价值交易、严格身份核验、医疗建议、法律判断、金融承诺和不可逆操作时,应保留文本化确认、人工审批或降级到传统流程。
常见误区
- 只压测模型延迟,不压测端到端对话延迟:用户感知的是麦克风采集、网络传输、VAD 等待、模型推理、TTS 生成和播放缓冲的总和。
- 把 VAD 当默认参数使用:不同噪声、设备、口音、语速和业务场景对阈值的要求完全不同。
- 只记录最终文本:实时系统要保存事件流、转写 delta、播放进度、打断点和工具参数,否则很难定位线上问题。
- 把 barge-in 当成 UI 功能:它本质上是会话状态机能力,需要取消旧响应、停止播放、冻结工具调用并恢复上下文。
- 语音工具调用不做确认:语音误听和指代错误会放大副作用风险,尤其是订单、支付、删除、退款和外呼类动作。
上线检查清单
上线前至少检查以下项目:
| 层级 | 检查项 |
|---|---|
| 传输层 | 接入方式是否清晰;浏览器端是否只使用临时凭证;服务端是否保存安全标识;断线重连是否会造成重复回复或重复工具调用 |
| 音频层 | 是否覆盖不同设备、噪声、口音、语速;是否记录采样率、编码、丢包、首音频延迟和播放中断 |
| VAD 层 | 是否按场景配置阈值;是否有误截断、迟迟不结束、噪声误触发和用户插话测试集 |
| 会话层 | 是否能取消旧 response;是否记录用户已听到的音频 offset;是否区分”生成完成”和”播放完成” |
| 工具层 | 是否按风险分级;是否有二次确认;是否记录确认文本、参数 hash 和工具结果;失败后是否能补偿或回滚 |
| 人工接管 | 是否有明确触发条件;是否把转写、风险标签、未完成动作和上下文摘要传给人工 |
| 观测层 | 是否有 VAD 误触发率、barge-in 成功率、首音频延迟、转写修正率、工具确认失败率、人工接管率、用户重复率和通话放弃率 |
FAQ
实时语音 Agent 是否一定要用 WebRTC?
不一定。浏览器和移动端直接采集播放音频时,WebRTC 通常更合适;服务器已经拿到音频流、电话系统或媒体 worker 场景,WebSocket 或 SIP 可能更自然。关键是把传输方式和鉴权、日志、重连、观测一起设计。
semantic VAD 能否完全解决抢答问题?
不能。semantic VAD 可以降低过早切分的概率,但真实对话中的停顿、犹豫、口音、噪声和多人说话仍然会造成误判。它应该和业务状态机、打断恢复、人工接管阈值一起使用。
语音 Agent 是否需要保存原始音频?
不一定。隐私要求高的场景可以只保存片段索引、转写、事件日志和必要的短期缓存。但如果涉及质检、争议处理或合规复核,就要明确音频保存范围、保留周期、访问权限和脱敏策略。