Skip to content

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

边界情况(以下事件按命中条件择一下发,不在正常流程内):

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_TRIGGERcontentTypeACTIVITY,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

contentTypeACTIVITY;effectiveTimeMinutes(1~1440 分钟)指定记忆时长;kvData 为自定义扩展信息,在提示词中用 ${extInfo.字段} 取用。


场景九:上报经纬度更新城市地址

设备端上报经纬度,服务端据此设置/更新当前城市地址,后续在查天气等场景下可返回对应城市的天气。需向 JoyInside 运营团队申请权限。服务端收到后下发 CLIENT_CHAT_CONTEXT_RECEIVED 确认。

上行事件(设备端 → 云端)

事件名称eventType
上报经纬度更新城市地址CLIENT_UPDATE_COORDINATES_CONTEXT

下行事件(云端 → 设备端)

事件名称eventType
上下文更新成功CLIENT_CHAT_CONTEXT_RECEIVED

contentTypeACTIVITY;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 重连(否则可能与刚建连的新连接再次互踢,形成抖动)。先排查重复建联来源:设备是否重复触发建连、是否有调试实例在跑、是否有多端同时登录。根本原因与排查见 双连接互踢

下一步