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]               # 可选,位置信息

URL 参数说明 ​

参数必选说明
botId是设备唯一标识,见 4.2 设备注册与绑定
sessionId是会话 ID。推荐:多轮对话场景复用同一 sessionId,保持上下文连续
requestId是一次 WS 建连的唯一标识,用于全链路追踪
uid否用户唯一标识。注意:请勿重复使用
needManualCall否按键对话模式(依赖客户端控制),必须传值:true,如不传则默认为false,为连续对话模式,该参数会决定不同对话模式下的串场词话术。
feature否场景扩展参数,如有声书 AUDIO_BOOK_V2,完整取值见 第6章 业务场景接入
location否位置信息

建连 Header ​

Authorization: Bearer ${accessToken}

Header 参数说明 ​

参数必选类型说明
Authorization是string鉴权头,格式固定为 Bearer ${accessToken},其中 accessToken 为通过 getToken 接口获取的访问令牌。Token 获取方式见 4.1 Token 鉴权。

建连时序 ​

⚠ 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格式, 推荐单个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_TRIGGER 的 contentType 为 ACTIVITY,content.activityType 固定 VOICE_CHAT_TRIGGER,自定义消息体放 content.kvData(具体结构见 附录 B.4.9)。具体下行为智能体正常对话回复流程,按实际触发指令可能含 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.字段} 取用。


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

设备端上报经纬度更新城市,主要用于查询天气等场景,服务端据此经纬度更新当前城市信息,后续在查询天气等场景下可准确返回该城市的天气。服务端收到后下发 CLIENT_UPDATE_COORDINATES_CONTEXT 确认。

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

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

contentType 为 ACTIVITY;content.coordinates.lat / content.coordinates.lng 传纬度 / 经度。城市地址更新后,后续对话中涉及天气等位置相关查询会使用新地址。

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

事件名称eventType
经纬度更新成功CLIENT_UPDATE_COORDINATES_CONTEXT

场景十:心跳保活 ​

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


场景十二:唤醒词打断 ​

设备端具有唤醒模型,在设备唤醒后(含用户与设备正常对话过程中),设备端有本地音频作为唤醒回复,不需要JoyInside 云端再对唤醒词做回复,避免出现唤醒词重复应答的问题场景。

方案一
设备端主动控制:对于唤醒词的音频不进行上传,可以避免JoyInside云端重复回复。

方案二
设备端主动控制:在唤醒词触发的对话轮次中,当收到JoyInside 云端下发的CALL_AGENT_START_EVENT事件后,主动上传CLIENT_INTERRUPT事件,打断后续JoyInside 云端回答。对于已经下发到端侧的TTS, 端侧要忽略。(根据ASR识别结果下发的roundId和TTS的roundId匹配,如果是同一个roundId,则忽略该TTS)

方案三
JoyInside 云端配置: 在运营平台闲聊提示词中,对"你好东东"(示例)唤醒词(含同音词或叠加重复)做特殊约束,让模型只回复"在的","来了"等简短回复,设备端如果无本地音频做回复,可以这么使用。不足之处就是唤醒回复耗时相对较长。

  • 完整闲聊提示词约束示例
markdown
## 唤醒词约束:当用户输入“你好丫丫”、“你好,丫丫”、“你好呀呀”、“你好,呀呀”唤醒词或多个唤醒词叠加重复时,请在这4个回答:“嗯?”、“在呢”、“请说”、“我在”之中,每次挑选一个不同的进行回复,不要每次都是相同的回复,文字内容不允许改变,不得多加文字,不允许自由发挥。如果整句话中除了唤醒词(含多个重复叠加的唤醒词)外,还有其他非唤醒词内容,则跳过唤醒词约束。"

下一步 ​