通过 WebSocket 为设备接入对话式 AI
更新时间: 2026/07/13 09:51:10
本文介绍如何通过 WebSocket 协议接入网易云信对话式 AI 服务,实现设备与云端服务的实时通信。通过 WebSocket 接入,您可以快速为智能硬件设备添加语音交互能力,无需深度集成嵌入式 SDK。
前提条件
在开始 WebSocket 接入之前,请确保您已经完成了 准备工作,包括创建应用、获取 App Key 以及配置智能体。
技术原理
WebSocket 是一种全双工通信协议,基于 TCP 连接实现客户端与服务器之间的实时双向数据传输。与传统 HTTP 请求-响应模式不同,WebSocket 连接一旦建立,客户端和服务器都可以主动发送数据,适合对话式 AI 的交互场景。相比较 嵌入式 SDK 接入方式,WebSocket 接入具备以下特性,更适合轻量级语音交互设备、快速原型开发等核心 AI 对话功能场景。
- 持久连接:避免频繁的连接建立和断开开销。
- 低延迟传输:消息推送延迟通常在毫秒级别。
- 帧分离传输:支持 Text Frame 文本帧(控制信令和事件消息)和 Binary Frame 二进制帧(音频流)同时传输,控制帧支持连接管理(Close、Ping、Pong 等)。
- 协议轻量化:相比 HTTP 具有更小的协议开销。
接入方式
请求地址
WebSocket 接入地址格式如下:
textwss://mps.yunxinvcloud.com/?device_id={device_id}
其中,device_id 为在 网易云信智能体管理平台 中绑定的设备标识符,用于唯一标识您的硬件设备。
请求头
WebSocket 建连请求头字段说明如下:
httpapp-key: {app_key}
token: {token}
yunxin-license: {license}
| 请求头 | 是否必填 | 说明 |
|---|---|---|
app-key |
是 | 网易云信应用的 App Key。device_id 归属于对应的 App Key 下,服务端通过 App Key 确定应用归属。 |
token |
否 | 基于 App Secret 生成的动态 Token。传入时,需与 app-key 配合使用。 |
yunxin-license |
否 | 授权码请求头。用于兼容既有授权码传入方式;传入时,服务端会结合授权码信息进行校验。 |
设备授权
设备授权通过为设备绑定授权码(license)实现。设备完成绑定与授权配置后,device_id 会作为设备授权校验和智能体配置查询的依据;设备端建连时传入 device_id 后,服务端会校验对应的绑定关系和授权状态。
授权步骤如下:
- 生成全局唯一的
device_id。 - 在智能体管理平台绑定
device_id、用户身份和智能体。 - 在智能体管理平台完成设备激活和授权配置,使
device_id与授权信息建立关联。 - 设备端接入 WebSocket 请求地址 时,在 URI 中传入
device_id。如需兼容既有授权码传入方式,也可以在请求头中传入yunxin-license。 - 服务端根据
device_id校验设备绑定关系和授权状态,并获取对应的智能体配置;如请求头中传入yunxin-license,服务端会结合授权码信息进行校验。
身份鉴权
WebSocket 接入以 device_id 作为设备授权和智能体配置查询的主要依据,app-key 为必填项,token 为可选项。
出于安全考虑,您可以在请求头中传入 token,开启应用级身份校验。传入后,云信服务会校验 Token 的时效性和签名合法性;校验失败时,本次连接将被拒绝。
token 由服务端根据 App Secret 生成。App Secret 不应下发到设备端,也不应写入设备固件或客户端代码。设备端应从可信服务端获取短期有效的动态 Token。
应用级身份校验流程如下:
- 在 网易云信控制台 申请
appkey,获取鉴权密钥appsecret。 - 服务端安全保存 App Secret,并按 Token 生成算法生成动态 Token。
- 设备端向服务端获取动态 Token。
- 设备端建立 WebSocket 连接,并在请求头中传入
app-key和token。 - 云信服务校验 Token 的时效性和签名合法性。
协议规范
本章节介绍 WebSocket 连接的通信协议规范,包括会话初始化、音频流传输、交互控制等。
会话初始化
客户端建立 WebSocket 连接后,需要先发送 start 信令初始化会话。服务端完成鉴权、设备配置查询和任务创建后,返回 server_ready。
发送开始信令
客户端向服务端发送:
JSON{
"action": "start",
"data": {
"input_audio": {
"format": "pcm",
"sample_rate": 16000,
"channels": 1,
"encoding": "raw"
},
"output_audio": {
"format": "pcm",
"sample_rate": 24000,
"channels": 1,
"frame_duration": 20,
"redundant_duration": 800,
"encoding": "raw"
},
"manual_mode": false,
"alive_timeout": 0,
"pre_context": [
{
"role": "user",
"content": "你好"
},
{
"role": "assistant",
"content": "你好,我在。"
}
],
"cunstom_params": {
"prompt_keywords": {
"scene": "indoor"
},
"tts_config": {
"vendor": 0,
"voice": "default",
"speed": 1,
"volume": 1,
"pitch": 0,
"ignore_bracket_text_set": [1, 2]
}
},
"features": {
"mcp": true
}
}
}
data 参数说明:
| 参数 | 类型 | 是否必需 | 示例值 | 说明 |
|---|---|---|---|---|
-
input_audio |
Object | 否 | {"format":"pcm","sample_rate":16000} |
上行音频格式。未传时使用默认配置。 |
format |
String | 否 | "pcm" |
音频格式,支持 pcm、opus、g711。 |
sample_rate |
Integer | 否 | 16000 |
采样率(Hz)。g711 仅支持 8000 Hz。 |
channels |
Integer | 否 | 1 |
声道数。g711 仅支持单声道。 |
encoding |
String | 否 | "raw" |
编码方式。当前支持 raw。 |
-
output_audio |
Object | 否 | {"format":"pcm","sample_rate":24000} |
下行音频格式。未传时使用默认配置。 |
format |
String | 否 | "pcm" |
音频格式,支持 pcm、opus、g711。 |
sample_rate |
Integer | 否 | 24000 |
采样率(Hz)。g711 仅支持 8000 Hz。 |
channels |
Integer | 否 | 1 |
声道数。g711 仅支持单声道。 |
frame_duration |
Integer | 否 | 20 |
下行音频帧长(毫秒)。决定服务端下行音频编码帧长。 |
redundant_duration |
Integer | 否 | 800 |
每轮开始预发送音频时长(毫秒)。 |
encoding |
String | 否 | "raw" |
编码方式。当前支持 raw。 |
manual_mode |
Boolean | 否 | false |
是否启用按键输入音频模式。启用后仅处理 listen_start 与 listen_stop 之间上传的音频。 |
alive_timeout |
Integer | 否 | 60 |
连接业务保活超时时长(秒)。0 表示不启用。 |
-
pre_context |
Array | 否 | [{"role":"user","content":"你好"}] |
会话级预置上下文,按顺序传入,最多 40 项。每项需包含 role 和 content。 |
role |
String | 是 | "user" |
角色,支持 user 和 assistant。 |
content |
String | 是 | "你好" |
对话内容。 |
-
cunstom_params |
Object | 否 | {"prompt_keywords":"科技","tts_config":{...}} |
自定义参数。 |
prompt_keywords |
String | 否 | "科技" |
会话级环境变量。仅对智能体后台已配置的变量生效。 |
-
tts_config |
Object | 否 | {"vendor":1,"voice":"xiaoyan"} |
会话级 TTS 覆盖配置。 |
vendor |
Integer | 否 | 1 |
平台配置,TTS 供应商。传入非 0 值时覆盖平台配置。 |
voice |
String | 否 | "xiaoyan" |
平台配置,TTS 音色。 |
speed |
Number | 否 | 1.0 |
语速。 |
volume |
Number | 否 | 50 |
音量。 |
pitch |
Integer | 否 | 0 |
语调。 |
ignore_bracket_text_set |
Array | 否 | [1, 2] |
过滤模型回复中指定括号内的文本后再进行 TTS。取值(可组合):1(中文括号 ())、2(英文括号 ())、3(中文方括号 【】)、4(英文方括号 [])、5(英文花括号 {})。未列出的扩展取值以平台配置为准。 |
-
features |
Object | 否 | {"mcp": false} |
功能开关。 |
mcp |
Boolean | 否 | false |
是否启用端侧 MCP 能力。 |
服务端准备完成
服务端向客户端返回:
JSON{
"action": "server_ready",
"data": {
"code": 0,
"msg": "ok",
"connection_id": "c70cde776a074170bb5d270cfd8691f"
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
code |
Integer | 状态码,0 表示成功,非 0 表示失败。 |
msg |
String | 状态描述。 |
connection_id |
String | 当前 WebSocket 会话的连接标识。 |
如初始化失败,服务端返回 error 信令,详见 错误响应。
音频流传输
WebSocket 连接的音频流传输遵循以下规范:
上传音频流
客户端在会话初始化成功后,通过 Binary Frame 持续发送音频数据。音频格式必须与 start.data.input_audio 中声明的格式一致。
接收音频流
服务端通过 Binary Frame 下发合成后的语音数据。音频格式与 start.data.output_audio 中声明的格式一致。
下行音频说明
服务端按照 start 信令中协商的 output_audio 参数进行下行音频编码,并通过 WebSocket Binary Frame 下发音频数据。每个 Binary Frame 对应一帧编码后的音频数据,客户端应以 WebSocket 消息边界作为音频包边界进行处理。
output_audio.frame_duration 用于指定下行音频帧长,单位为毫秒。若未指定,服务端默认按照 20 ms 帧长进行下行音频编码,即每个 Binary Frame 对应约 20 ms 的音频数据。
当下行音频格式为 Opus 时,服务端下发的是裸 Opus packet,不包含额外容器封装。
交互控制
WebSocket 连接的交互控制遵循以下规范。
收音模式
WebSocket 接入支持自动收音和按键收音两种模式,由 start.data.manual_mode 控制。
| 模式 | manual_mode |
说明 |
|---|---|---|
| 自动收音 | false |
默认模式。客户端可持续上传音频,服务端根据语音活动检测自动判断用户开始和结束说话。 |
| 按键收音 | true |
客户端通过 listen_start 和 listen_stop 明确标记本轮用户输入的开始和结束。服务端只处理开始与结束信令之间上传的音频。 |
按键收音模式下,一个完整轮次的推荐时序如下:
- 用户按下按键或开始录音时,客户端发送
listen_start。 - 客户端通过 Binary Frame 上传本轮音频数据。
- 用户松开按键或结束录音时,客户端先发送完本轮剩余音频数据,再发送
listen_stop。 - 服务端根据已收到的音频生成识别结果,并继续后续模型回复流程。
listen_stop 只表示本轮用户输入结束,不会关闭 WebSocket 连接。如需结束整个会话,请发送 stop 信令或关闭 WebSocket 连接。
按键输入音频开始
当 manual_mode 为 true 时,客户端应在本轮音频上传前发送:
JSON{
"action": "listen_start",
"data": {
"id": "event_001"
}
}
按键输入音频结束
当 manual_mode 为 true 时,客户端应在本轮音频上传完成后发送:
JSON{
"action": "listen_stop",
"data": {
"id": "event_002"
}
}
手动打断
客户端可通过 manual_interrupt 打断当前交互:
JSON{
"action": "manual_interrupt",
"data": {
"id": "event_003"
}
}
手动提交对话内容
客户端可通过 manual_message 提交文本、图片或需要播报的助手消息:
JSON{
"action": "manual_message",
"data": {
"id": "event_004",
"role": "user",
"text": "请描述这张图片",
"img_url": "{image_url}",
"img_fragment": 1,
"img_fragment_seq": 1,
"add_context": true,
"interrupt_mode": 1
}
}
| 参数 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
id |
String | 否 | 客户端生成的事件 ID,用于问题定位。 |
role |
String | 否 | 消息角色,支持 user(作为用户输入进入对话,可触发模型回复) 和 assistant(将文本作为助手消息进行语音播报,并可按 add_context 控制是否加入上下文),默认 user。 |
text |
String | 否 | 文本内容。role=user 时,文本和图片至少传入一项。 |
|
|
String |
否 |
图片地址,支持 HTTPS URL 或 Base64 Data URL。
|
img_fragment |
Integer | 否 | 图片分片总数,默认 1。 |
img_fragment_seq |
Integer | 否 | 当前图片分片序号,从 1 开始,默认 1。当本地图片数据较大时,可通过 img_fragment 和 img_fragment_seq 分片上传。 |
add_context |
Boolean | 否 | role=assistant 时,是否将文本加入对话上下文,默认 true。 |
|
|
Integer |
否 |
外部输入处理优先级。
|
业务保活
如 start.data.alive_timeout 设置为非 0,客户端应周期性发送 ping 信令:
JSON{
"action": "ping",
"data": {}
}
服务端返回:
JSON{
"action": "pong"
}
建议客户端发送周期小于 alive_timeout。例如 alive_timeout 为 30 秒时,可每 3 到 10 秒发送一次 ping。
主动关闭
客户端可通过 stop 主动结束会话:
JSON{
"action": "stop",
"data": {}
}
服务端收到后会释放会话资源并关闭 WebSocket 连接。
Function Call
WebSocket 连接的 Function Call 用于同步端侧设备状态、下发工具调用以及透传端侧 MCP 消息。
工具配置
在 网易云信智能体管理平台 配置 tools 时,建议添加 response_success 参数用于操作反馈:
JSON{
"parameters": {
"type": "object",
"properties": {
"response_success": {
"description": "操作成功时的友好回复,关于该设备的操作结果,设备名称尽量使用 description 中的名称",
"type": "string"
}
},
"required": ["response_success"]
}
}
设备状态同步
客户端可通过 iot_status 同步端侧设备状态:
JSON{
"action": "iot_status",
"data": {
"iot_status": [
{
"name": "SetVolume",
"status": {
"volume": 50
}
}
]
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
-
iot_status |
Array | 设备状态列表。 |
iot_status[].name |
String | 对应智能体工具配置中的函数名。 |
iot_status[].status |
Object | 设备状态参数。 |
端侧 MCP 信令上行
如会话启用了 features.mcp,客户端可通过 mcp 信令上报端侧 MCP 消息:
JSON{
"action": "mcp",
"data": {
"jsonrpc": "2.0",
"id": "mcp_001",
"result": {}
}
}
data 为端侧 MCP 协议的 payload。服务端会将该 payload 透传给智能体侧处理。
如工具由端侧 MCP 执行,工具执行结果也通过 mcp 信令回传。tool_calls 中的工具调用 ID 可放入 MCP payload 中,便于服务端关联当前工具调用。
工具调用
服务端通过 tool_calls 下发工具调用请求:
JSON{
"action": "tool_calls",
"data": {
"request": "将音量设置为 80%",
"tool_calls": [
{
"id": "call_001",
"type": "function",
"function": {
"name": "SetVolume",
"arguments": "{\"volume\": 80}"
}
}
]
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
request |
String | 触发工具调用的用户请求文本。 |
-
tool_calls |
Array | 工具调用列表。 |
id |
String | 工具调用 ID。 |
type |
String | 工具类型,当前为 function。 |
-
function |
Object | 函数信息。 |
name |
String | 函数名。 |
arguments |
String | 函数参数,JSON 字符串格式。 |
客户端执行工具后,如结果需要回传服务端,应通过端侧 MCP 信令上报。
事件通知
WebSocket 连接的服务端事件通知遵循以下规范。
语音转文本结果
JSON{
"action": "asr_text",
"data": {
"type": 0,
"content": "今天天气怎么样"
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
type |
Integer | 结果类型,0 表示最终结果,1 表示中间结果。 |
content |
String | 识别文本。 |
大模型回复文本
JSON{
"action": "llm_text",
"data": {
"type": 0,
"content": "今天适合外出,请注意查看当地天气预报。"
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
type |
Integer | 结果类型,0 表示最终结果,1 表示中间结果。 |
content |
String | 模型回复文本。 |
文本转语音状态
TTS 开始:
JSON{
"action": "tts_start",
"data": {}
}
TTS 结束:
JSON{
"action": "tts_stop",
"data": {}
}
情绪状态
JSON{
"action": "llm_emotion",
"data": {
"emotion": "happy"
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
emotion |
String | 情绪标识。具体取值以智能体配置和模型输出为准。 |
控制信令
服务端通过 on_ai_data 下发内容、MCP 或智能体事件:
JSON{
"action": "on_ai_data",
"data": {
"externData": {
"type": "content",
"data": {
"timestamp": 1754654765,
"request": "展示图片",
"content": {
"type": "photoTag",
"message": "happy"
}
}
}
}
}
externData.type 支持以下取值:
| 取值 | 说明 |
|---|---|
content |
内容下发。内容字段位于 externData.data.content。 |
mcp |
MCP 信令下发。MCP payload 位于 externData.data.mcpPayload。 |
agentEvent |
智能体平台事件。事件 payload 位于 externData.data.agentEventPayload。 |
MCP 下发示例:
JSON{
"action": "on_ai_data",
"data": {
"externData": {
"type": "mcp",
"data": {
"timestamp": 1754654765,
"mcpPayload": {
"jsonrpc": "2.0",
"method": "tools/call",
"params": {}
}
}
}
}
}
智能体事件示例:
JSON{
"action": "on_ai_data",
"data": {
"externData": {
"type": "agentEvent",
"data": {
"timestamp": 1754654765,
"agentEventPayload": {}
}
}
}
}
错误响应
JSON{
"action": "error",
"data": {
"code": 400,
"msg": "param error",
"connection_id": "c70cde776a074170bb5d270cfd8691f"
}
}
| 参数 | 类型 | 说明 |
|---|---|---|
code |
Integer | 错误码。 |
msg |
String | 错误描述。 |
connection_id |
String | 连接标识。 |
生成 Token
如需启用应用级身份校验,可参考本节生成 Token,用于 WebSocket 连接 Token 验证。
算法实现
Token 内容为 Base64 编码后的 JSON 字符串,JSON 中包含:
| 字段 | 类型 | 说明 |
|---|---|---|
signature |
String | 签名结果。 |
curTime |
Long | 当前时间戳,单位毫秒。 |
ttl |
Integer | Token 有效期,单位秒。 |
签名计算方式:
textsignature = SHA1(curTime + ttl + appSecret)
以下示例代码中,使用 Java 语言实现 Token 生成算法:
javapublic class TokenGenerator {
private final String appSecret;
public TokenGenerator(String appSecret) {
this.appSecret = appSecret;
}
public String getToken(int ttlSec) throws Exception {
long curTimeMs = System.currentTimeMillis();
return getTokenWithCurrentTime(ttlSec, curTimeMs);
}
public String getTokenWithCurrentTime(int ttlSec, long curTimeMs) throws Exception {
DynamicToken tokenModel = new DynamicToken();
tokenModel.signature = sha1(String.format("%d%d%s", curTimeMs, ttlSec, appSecret));
tokenModel.curTime = curTimeMs;
tokenModel.ttl = ttlSec;
ObjectMapper objectMapper = new ObjectMapper();
String tokenJson = objectMapper.writeValueAsString(tokenModel);
return Base64.getEncoder().encodeToString(tokenJson.getBytes(StandardCharsets.UTF_8));
}
private String sha1(String input) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
byte[] result = digest.digest(input.getBytes(StandardCharsets.UTF_8));
StringBuilder builder = new StringBuilder();
for (byte item : result) {
builder.append(String.format("%02x", item));
}
return builder.toString();
}
public static class DynamicToken {
public String signature;
public long curTime;
public int ttl;
}
}
验证 Token
网易云信服务器接收到 Token 后会进行以下校验:
- 时效性校验:当前时间不得超过
curTime + ttl。 - 签名校验:使用
curTime、ttl和服务端保存的 App Secret 重新计算签名,并与 Token 中的signature比对。
任一校验失败,连接鉴权失败。
下一步
- 了解如何在设备端优化音频处理以提高识别准确率。
- 探索如何结合 Function Call 机制实现设备控制功能。




