主题
4.4 WebSocket语音通道
语音对话的核心通道。本章定义建连参数、对话模式、上下行事件全集,以及一次完整轮次的事件序列。
建连
建连 URL
wss://ws.joyinside.com/soulmate/voiceChat/v1
?botId={botId}
&sessionId={botId + random} # 推荐多轮对话场景复用同一sessionId
&requestId={uuid}
[&uid=xxx] # 可选,用户uid
[&needManualCall=true] # 可选,按键对话模式
[&feature=AUDIO_BOOK_V2] # 可选,场景扩展,见第6章
[&location=xxx] # 可选,位置信息| 参数 | 必选 | 说明 |
|---|---|---|
| botId | 是 | 设备唯一标识,见 4.2 设备注册与绑定 |
| sessionId | 是 | 会话 ID。推荐:多轮对话场景复用同一 sessionId,保持上下文连续 |
| requestId | 是 | 一次 WS 建连的唯一标识,用于全链路追踪 |
| uid | 否 | 用户唯一标识。注意:请勿重复使用 |
| needManualCall | 否 | 按键对话模式(依赖客户端控制),必须传值:true,如不传则默认为false,为连续对话模式,该参数会决定不同对话模式下的串场词话术。 |
| feature | 否 | 场景扩展参数,如有声书 AUDIO_BOOK_V2,完整取值见 第6章 业务场景接入 |
| location | 否 | 位置信息 |
建连 Header
Authorization: Bearer ${accessToken}建连时序
⚠ Token 8 小时有效期:建议在 WS 建连前重新获取 Token,避免建连时已过期。详见 Token 生命周期。
一次完整轮次的事件序列
AGENT 与 TTS 的关系:智能体回复文本(AGENT)与智能体回复音频(TTS)会交错下发,端侧按到达顺序处理即可。文本可当字幕展示,音频按顺序播放。TTS 音频下发前会先收到
TTS_SENTENCE_START字幕事件。
对话模式
自由对话模式(默认)
云端支持 VAD(语音活动检测)+ ASR,设备端需支持 AEC(声学回声消除)。
打断逻辑:设备端接收云端下发的 CALL_AGENT_INTERRUPTED 事件,停止当前 TTS 播放,清空播放队列,准备播放新的 TTS。
手动对话模式(needManualCall=true)
类似「对讲机」:用户主动按住按键收音,松开结束。
| 行为 | 必须发送的事件 |
|---|---|
| 音频发送结束 | CLIENT_AUDIO_FINISH |
| 打断 TTS 播放 | CLIENT_INTERRUPT |
⚠ 手动模式下,新一轮对话开始时若 TTS 还在输出,必须先发
CLIENT_INTERRUPT打断,否则轮次判定异常。
详见 5.2 三种交互模式选型 的三种交互模式说明。
模式区别与打断逻辑示意
下面两个时序图聚焦展示两种模式在音频结束、文本回复、打断三个环节的差异,便于直观对照。
① 按键模式 vs 自由对话模式:音频结束与文本回复
按键模式在音频上传后需显式发送 CLIENT_AUDIO_FINISH 通知结束;自由对话模式由云端 VAD 自动判定语音结束,无需额外事件。识别结果若为有效文本,下发 AGENT + TTS 回复;若未识别到有效文本,则下发 EMPTY_CONTENT 空文本事件。
② 两种模式的打断逻辑
无论哪种模式,打断后云端都会下发 CALL_AGENT_INTERRUPTED,端侧据此停止 TTS 播放、清空播放队列。区别在于触发方式:按键模式由端侧主动发 CLIENT_INTERRUPT;自由对话模式由云端在识别到新的 AUDIO 输入后自动打断。
音频上传规范
流式上传(不使用二进制)
json
{
"mid": "uuid",
"contentType": "AUDIO",
"content": {
"audioBase64": "base64编码的音频片段",
"index": 1
}
}二进制传输(推荐)
开启二进制,具体参见事件协议明细附录 B。音频通过 WebSocket binary frame 传输,替代上行 AUDIO 事件和下行 TTS 事件。其他事件仍用 text。
javascript
ws.send(bytes); // 二进制通道仅用于音频输入输出若返回
{"code":400,"msg":"audio binary not support."}表示未启用二进制但传了 bytes。
切包规则
| 格式 | 单包限制 |
|---|---|
| pcm | 单包时长 ≤300ms,16k/16bit/单声道 |
| opus | 单包 ≤480 字节,帧长 ≤120ms(建议 60ms);多包拼接总时长 ≤300ms |
均匀发送:间隔应等于音频时长(如 120ms 包就 sleep 120ms),否则回复延时异常。详见 4.5 音频格式与分片。
重要提示: 若设备端上传opus格式, 建议一帧一帧上传;若设备端上传的音频有多包拼接的情况,一定要在更新语音对话配置中设置audio.input.frameSize=单帧字节数
场景化上下行事件对照
本节按用户使用场景组织,直观呈现「每个场景下设备端发送了哪些上行事件、服务端会返回哪些下行事件」。每个事件只列事件名称 + eventType,点击事件名称可跳转到 附录 B 查看完整 JSON 结构与字段说明。
建连参数、对话模式、完整时序总览见本页上文「建连」「对话模式」章节;上下行事件全集(JSON 明细)见 附录 B。
场景一:用户建连后
设备端完成 WebSocket 建连(携带 botId / sessionId / requestId + Bearer accessToken)后,服务端主动下发默认语音对话配置,告知设备端默认音色、TTS 音频格式(编码/位深/通道/采样率)、设备型号等。设备端据此初始化音频播放参数。
上行事件(设备端 → 云端)
此场景无上行事件 —— 建连握手由 WebSocket 协议层完成,不属于业务事件。
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 默认语音对话配置 | CFG_BOT_EVENT |
⚠ 默认配置不一定满足业务需求(如需自定义音频编码/采样率/音色),收到
CFG_BOT_EVENT后需发送「更新语音对话配置」覆盖。
场景二:用户更新配置
建连后设备端只需上传一次 CLIENT_VOICE_CHAT_UPDATE,用于更新语音链路配置(音频编码/采样率、二进制传输开关、输入输出帧参数、音色语速音量、对话角色等)。服务端处理成功后下发 SERVER_VOICE_CHAT_UPDATED 响应,必须收到该响应才算配置成功。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 更新语音对话配置 | CLIENT_VOICE_CHAT_UPDATE |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 更新配置成功响应 | SERVER_VOICE_CHAT_UPDATED |
⚠ 每连接只发一次:重复发送会打断对话;对话中切换角色也需重发此事件。一定要收到
SERVER_VOICE_CHAT_UPDATED才算成功。
场景三:用户上传语音
用户说话时,设备端流式上行音频片段 AUDIO。按对话模式不同,音频结束的判定方式不同:
- 按键模式(
needManualCall=true):用户松开按键后,设备端需显式上行CLIENT_AUDIO_FINISH通知服务端音频上传结束。 - 自由对话模式(默认):无需显式结束事件,服务端 VAD 自动判定语音结束。
两种模式识别成功后的下行事件完全一致;若未识别到有效音频,服务端下发 EMPTY_CONTENT 而非开启对话。
3.1 按键模式
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 流式上传音频 | AUDIO |
| 结束音频上传 | CLIENT_AUDIO_FINISH |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 音频识别内容 | ASR |
| 智能体对话开始 | CALL_AGENT_START_EVENT |
| 音频字幕 | TTS_SENTENCE_START |
| 智能体回复文本 | AGENT |
| 智能体回复音频 | TTS |
| 语音音频回复完成 | TTS_COMPLETE |
| 语音当轮对话完成 | COMPLETE |
边界情况(以下事件按命中条件择一下发,不在正常流程内):
- 若命中设备控制 / IOT 指令,额外下发 指令事件
CALL_SKILL_EVENT。- 若未识别到有效音频,下发 智能体对话忽略
EMPTY_CONTENT,不开启对话。- 若输入音频命中安全审核,下发 审核命中
USER_AUDIT_FAIL,同时AGENT的finishReason=audit。
3.2 自由对话模式
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 流式上传音频 | AUDIO |
无需
CLIENT_AUDIO_FINISH—— 服务端 VAD 自动判定语音结束。
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 音频识别内容 | ASR |
| 智能体对话开始 | CALL_AGENT_START_EVENT |
| 音频字幕 | TTS_SENTENCE_START |
| 智能体回复文本 | AGENT |
| 智能体回复音频 | TTS |
| 语音音频回复完成 | TTS_COMPLETE |
| 语音当轮对话完成 | COMPLETE |
边界情况同 3.1:可能额外下发
CALL_SKILL_EVENT/EMPTY_CONTENT/USER_AUDIT_FAIL。AGENT 与 TTS 交错下发:回复文本与回复音频会交错到达,端侧按到达顺序处理即可(文本当字幕、音频按序播放)。
TTS_SENTENCE_START一定先于对应的TTS音频下发。
场景四:用户上传文本对话
设备端直接上行文本 TEXT 与智能体对话(不经语音识别)。服务端收到后流式回复文本 + 音频,下行事件集与「上传语音并识别成功」后的下行一致(但不会有 ASR,因为文本无需识别)。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 发送文本对话内容 | TEXT |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 智能体对话开始 | CALL_AGENT_START_EVENT |
| 音频字幕 | TTS_SENTENCE_START |
| 智能体回复文本 | AGENT |
| 智能体回复音频 | TTS |
| 语音音频回复完成 | TTS_COMPLETE |
| 语音当轮对话完成 | COMPLETE |
边界情况:若命中设备控制 / IOT 指令,额外下发 指令事件
CALL_SKILL_EVENT;若命中安全审核,下发 审核命中USER_AUDIT_FAIL。
场景五:语音打断
智能体正在输出 TTS 回复时,用户再次开口需要打断当前输出。两种模式触发方式不同,但服务端都会下发 CALL_AGENT_INTERRUPTED,设备端收到后必须停止当前 TTS 播放并清空播放队列,准备播放新的 TTS。
5.1 按键模式
设备端主动上行 CLIENT_INTERRUPT 打断。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 打断智能体 | CLIENT_INTERRUPT |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 智能体打断事件 | CALL_AGENT_INTERRUPTED |
⚠ 按键模式下,新一轮对话开始时若 TTS 还在输出,必须先发
CLIENT_INTERRUPT打断,否则轮次判定异常。
5.2 自由对话模式
无需设备端主动打断 —— 服务端识别到新的 AUDIO 输入后自动打断当前输出。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 流式上传音频 | AUDIO(新一轮输入) |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 智能体打断事件 | CALL_AGENT_INTERRUPTED |
⚠ 收到
CALL_AGENT_INTERRUPTED后,端侧处理:停止 TTS 播放、清空播放队列,等待新 TTS。
场景六:请求音频合成
设备端主动提交一段文字,请求服务端合成对应 TTS 语音。本场景不触发智能体对话(不会下发 AGENT / CALL_AGENT_START_EVENT / COMPLETE),只流式生成 TTS 音频片段。若提交时智能体正在输出语音,会被中断。
典型用途:本地播报固定文案、提示音、合成自定义话术等。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 请求音频合成 | CLIENT_INPUT_TEXT_TO_SPEECH |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 音频字幕 | TTS_SENTENCE_START |
| 智能体回复音频 | TTS |
| 语音音频回复完成 | TTS_COMPLETE |
文本会被切分并分段生成音频;
TTS_SENTENCE_START先于对应TTS下发,TTS_COMPLETE标记本轮合成完成。文本长度限制 (0, 1000) 字节。
场景七:主动触发对话
设备端主动触发智能体执行指令(如外部事件驱动的主动交互),通过 VOICE_CHAT_TRIGGER 携带自定义消息体 kvData 上行。服务端据此触发智能体,后续按对话流程回复;若触发的是设备控制指令,会下发 CALL_SKILL_EVENT。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 主动触发对话 | VOICE_CHAT_TRIGGER |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 智能体对话开始 | CALL_AGENT_START_EVENT |
| 指令事件 | CALL_SKILL_EVENT |
| 音频字幕 | TTS_SENTENCE_START |
| 智能体回复文本 | AGENT |
| 智能体回复音频 | TTS |
| 语音音频回复完成 | TTS_COMPLETE |
| 语音当轮对话完成 | COMPLETE |
VOICE_CHAT_TRIGGER的contentType为ACTIVITY,content.activityType固定VOICE_CHAT_TRIGGER,自定义消息体放content.kvData。具体下行为智能体正常对话回复流程,按实际触发指令可能含CALL_SKILL_EVENT。
场景八:主动更新上下文
设备端主动更新智能体的短期记忆(如注入用户偏好、会话上下文),在后续对话中生效。需向 JoyInside 运营团队申请权限。服务端收到后下发 CLIENT_CHAT_CONTEXT_RECEIVED 确认。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 主动更新对话上下文 | CLIENT_UPDATE_CHAT_CONTEXT |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 上下文更新成功 | CLIENT_CHAT_CONTEXT_RECEIVED |
contentType为ACTIVITY;effectiveTimeMinutes(1~1440 分钟)指定记忆时长;kvData为自定义扩展信息,在提示词中用${extInfo.字段}取用。
场景九:上报经纬度更新城市地址
设备端上报经纬度,服务端据此设置/更新当前城市地址,后续在查天气等场景下可返回对应城市的天气。需向 JoyInside 运营团队申请权限。服务端收到后下发 CLIENT_CHAT_CONTEXT_RECEIVED 确认。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 上报经纬度更新城市地址 | CLIENT_UPDATE_COORDINATES_CONTEXT |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 上下文更新成功 | CLIENT_CHAT_CONTEXT_RECEIVED |
contentType为ACTIVITY;content.coordinates.lat/content.coordinates.lng传纬度 / 经度。城市地址更新后,后续对话中涉及天气等位置相关查询会使用新地址。
场景十:心跳保活
WebSocket 长连接保活。设备端定期上行 PING,服务端响应 PONG。建议 30 秒间隔;有声书场景间隔 ≤2s(见 6.2 有声书)。
上行事件(设备端 → 云端)
| 事件名称 | eventType |
|---|---|
| 心跳 | PING |
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 心跳响应 | PONG |
场景十一:双连接互踢
同一 botId 同时建立多个 WebSocket 连接(如设备重复建联、调试实例未关、多端同时登录)时,后建的连接会踢掉先建的连接,先建的连接会收到 REPEAT_CLIENT_SESSION 事件并被服务端关闭。该事件由服务端主动下发,用于通知被踢方连接已被取代。
上行事件(设备端 → 云端)
此场景无业务上行事件 —— 互踢由「同 botId 再建一条 WebSocket 连接」的建连动作触发(WebSocket 协议层),不属于上行业务事件。
下行事件(云端 → 设备端)
| 事件名称 | eventType |
|---|---|
| 互踢 | REPEAT_CLIENT_SESSION |
⚠ 端侧收到
REPEAT_CLIENT_SESSION后连接会被关闭,不要立即用同一 botId 重连(否则可能与刚建连的新连接再次互踢,形成抖动)。先排查重复建联来源:设备是否重复触发建连、是否有调试实例在跑、是否有多端同时登录。根本原因与排查见 双连接互踢。
下一步
- 全场景事件速查与功能维度对照:B. WebSocket 语音协议全览
- 理解事件协议明细:附录 B 事件协议明细
- 音频格式细节:4.5 音频格式与分片
- 打断失效排查:10.3 打断失效类