AI交互协议
约 3902 字大约 13 分钟
2026-03-02
联犀平台支持通过 MQTT 协议进行设备端 AI 交互,包括纯文字对话、实时语音对话、多模态输入(文字+图片/视频/文件)等多种交互方式。
1. 协议概述
AI 交互使用独立的 MQTT Topic 类型 ai,与物模型的 property/event/action 类型分离,语义更清晰。
MQTT Topic
上行(设备 → 云端): $thing/up/ai/{ProductID}/{DeviceName}
下行(云端 → 设备): $thing/down/ai/{ProductID}/{DeviceName}通用消息信封
所有 AI 协议消息使用统一的 JSON 信封格式:
{
"msgToken": "msg-001",
"method": "sessionCreate",
"params": {},
"data": {},
"code": 0
}| 字段 | 类型 | 说明 |
|---|---|---|
| msgToken | string | 消息令牌,用于请求-响应匹配 |
| method | string | 方法名(小驼峰命名) |
| params | object | 请求参数(上行消息使用) |
| data | object | 响应数据(下行消息使用) |
| code | int | 响应码,0 表示成功(下行消息使用) |
2. Agent-Device 绑定机制
设备进行 AI 交互时,会自动关联到对应的智能体(Agent)和数字分身(Clone)。平台支持两种绑定方式:
2.1 AgentGroup 用途分类
AgentGroup 通过 purpose 字段区分用途:
| Purpose | 说明 |
|---|---|
default | 默认通用智能体 |
device | 设备专用智能体 |
user | 用户专用智能体 |
platform | 平台级智能体 |
设备 AI 功能需要使用 purpose=device 的 AgentGroup 下的 Agent。
2.2 产品绑定默认 Agent
通过在产品(Product)上配置 defaultAgentID,该产品下的所有设备在首次绑定时会自动创建对应的 Clone:
配置步骤:
- 在控制台创建 AgentGroup,设置
purpose=device - 在该 Group 下创建 Agent,配置系统 Prompt 和 LLM 参数
- 创建产品时设置
defaultAgentID,或在产品详情页更新绑定关系
2.3 设备 Clone 自动创建
设备首次被用户绑定时(调用 deviceInfo/bind API),系统会自动执行以下逻辑:
- 检查设备是否已有 Clone(
dm_device_info.clone_id) - 若无且产品有
defaultAgentID,自动调用 aisvr 的 Clone RPC 创建 Clone - Clone 代码格式:
device_{productID}_{deviceName}(全局唯一) - Clone 绑定到设备,记录
cloneID
Product.defaultAgentID → 设备首次绑定 → 自动创建 Clone → dm_device_info.clone_idClone 的作用:
- 每个设备拥有独立的 Clone,AI 记忆完全隔离
- Clone 继承 Agent 的 Prompt 和配置,但记忆独立
- 不同设备间的 AI 对话历史、偏好设置互不干扰
2.4 完整绑定流程
3. 上行方法(设备 → 云端)
| Method | 说明 |
|---|---|
sessionCreate | 创建 AI 会话 |
sessionClose | 关闭 AI 会话 |
audioStart | 启动音频流 |
audioStop | 停止音频流 |
respCancel | 取消当前响应(打断) |
inputSend | 发送多模态输入 |
3.1 sessionCreate - 创建会话
请求示例(语音对话):
{
"msgToken": "msg-001",
"method": "sessionCreate",
"params": {
"modalities": ["text", "audio"],
"audioParams": {
"format": "opus",
"sampleRate": 16000,
"channels": 1,
"frameDuration": 60
}
}
}请求示例(纯文字对话):
{
"msgToken": "msg-001",
"method": "sessionCreate",
"params": {
"modalities": ["text"]
}
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| modalities | []string | 是 | 请求模态:text(纯文字)、audio(语音) |
| audioParams | object | 否 | 音频参数(包含 audio 模态时需要) |
| audioParams.format | string | 否 | 音频格式:opus(推荐)、pcm、wav、mp3 |
| audioParams.sampleRate | int | 否 | 采样率:8000 / 16000 / 24000 / 48000(Hz) |
| audioParams.channels | int | 否 | 声道数:1(单声道,推荐)、2(立体声) |
| audioParams.frameDuration | int | 否 | 帧时长(ms):20 / 40 / 60 |
纯文字对话时
modalities设为["text"],无需audioParams。 设备使用的 Agent 和 Clone 由云端自动解析(基于 2.3 节绑定关系),无需设备指定。 音频传输方式目前固定为 UDP,无需额外指定。
3.2 inputSend - 发送多模态输入
请求示例(纯文字):
{
"msgToken": "msg-002",
"method": "inputSend",
"params": {
"contents": [
{"type": "text", "text": "你好,请介绍一下自己"}
]
}
}请求示例(文字+图片):
{
"msgToken": "msg-003",
"method": "inputSend",
"params": {
"contents": [
{"type": "text", "text": "这张图片里有什么?"},
{"type": "image_url", "imageUrl": "https://oss.example.com/photo.jpg"}
]
}
}参数说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| contents | []ContentPart | 是 | 多模态内容列表(见第 5 节) |
| modalities | []string | 否 | 期望输出模态(不填则使用会话默认值) |
3.3 其他上行方法
audioStart、audioStop、respCancel、sessionClose 均无需 params:
{"msgToken": "msg-004", "method": "audioStart"}
{"msgToken": "msg-005", "method": "audioStop"}
{"msgToken": "msg-006", "method": "respCancel"}
{"msgToken": "msg-007", "method": "sessionClose"}| Method | 触发时机 |
|---|---|
audioStart | 设备开始采集麦克风数据,通知云端开始接收 UDP 音频帧 |
audioStop | 设备停止采集,通知云端结束本轮语音输入 |
respCancel | 用户打断 AI 回复(如再次按下按钮),触发 TTS 中止 |
sessionClose | 对话结束,释放会话资源 |
4. 下行方法(云端 → 设备)
| Method | 说明 |
|---|---|
sessionCreated | 会话已创建,返回连接凭证 |
respCreated | 新一轮 AI 响应开始 |
respSttDelta | STT 增量识别结果(流式) |
respSttDone | STT 最终识别结果 |
respTextDelta | LLM 增量文本输出(流式) |
respTextDone | LLM 完整文本输出 |
audioSpeechStarted | TTS 语音播放开始 |
audioSpeechStopped | TTS 语音播放结束 |
audioSpeechInterrupted | TTS 语音被打断(respCancel 触发) |
respAudioStart | TTS 音频流开始(UDP 下推前通知) |
respAudioDone | TTS 音频流结束 |
respMedia | 媒体内容(图片/视频等) |
error | 错误通知 |
4.1 sessionCreated - 会话创建响应
响应示例(语音模式):
{
"method": "sessionCreated",
"msgToken": "msg-001",
"code": 0,
"data": {
"sessionId": "sess-abc123",
"transport": "udp",
"modalities": ["text", "audio"],
"audioParams": {
"format": "opus",
"sampleRate": 24000,
"channels": 1,
"frameDuration": 60
},
"udp": {
"server": "192.168.1.100",
"port": 6789,
"key": "base64-encoded-aes-key==",
"nonce": "base64-encoded-nonce=="
},
"uploadUrl": "/api/v1/things/device/edge/upload-file",
"supportedModalities": ["text", "audio", "image"]
}
}响应示例(纯文字模式):
{
"method": "sessionCreated",
"msgToken": "msg-001",
"code": 0,
"data": {
"sessionId": "sess-xyz789",
"modalities": ["text"],
"uploadUrl": "/api/v1/things/device/edge/upload-file"
}
}data 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| sessionId | string | 会话 ID,后续所有关联消息的标识 |
| transport | string | 实际使用的传输方式(udp 或 mqtt) |
| modalities | []string | 本次会话支持的模态列表 |
| audioParams | object | 服务端实际使用的音频参数(可能与请求有所调整) |
| audioParams.format | string | 音频格式 |
| audioParams.sampleRate | int | 采样率(Hz) |
| audioParams.channels | int | 声道数 |
| audioParams.frameDuration | int | 帧时长(ms) |
| udp | object | UDP 连接信息(仅语音模式返回) |
| udp.server | string | UDP 服务器地址(IP 或域名) |
| udp.port | int | UDP 端口号 |
| udp.key | string | AES-CTR 加密密钥(Base64 编码,16 字节) |
| udp.nonce | string | 加密 Nonce(Base64 编码,16 字节) |
| uploadUrl | string | 文件上传端点,用于多模态输入的文件预上传 |
| supportedModalities | []string | 服务端全部支持的模态列表(参考值) |
udp.key和udp.nonce用于 AES-CTR 加密音频数据包,详见 UDP 音频通道。
4.2 respCreated - 响应开始
每次 AI 开始生成新一轮响应时下发:
{
"method": "respCreated",
"data": {
"respId": "resp_001"
}
}| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID,用于关联后续的 delta/done 消息 |
4.3 respTextDelta / respTextDone - 流式文本输出
LLM 回复通过 respTextDelta 逐 token 流式下发,最后通过 respTextDone 发送完整文本:
{"method": "respTextDelta", "data": {"respId": "resp_001", "contentIndex": 0, "delta": "你好"}}
{"method": "respTextDelta", "data": {"respId": "resp_001", "contentIndex": 0, "delta": ",我是"}}
{"method": "respTextDelta", "data": {"respId": "resp_001", "contentIndex": 0, "delta": "AI助手"}}
{"method": "respTextDone", "data": {"respId": "resp_001", "contentIndex": 0, "text": "你好,我是AI助手"}}respTextDelta 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID(与 respCreated 对应) |
| contentIndex | int | 内容索引(多模态响应时区分不同内容块) |
| delta | string | 本次增量文本片段 |
respTextDone 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID |
| contentIndex | int | 内容索引 |
| text | string | 完整的文本内容(所有 delta 的拼接结果) |
| contents | []ContentPart | 多模态内容(可选,LLM 同时输出图片等时使用) |
4.4 respSttDelta / respSttDone - 语音识别结果
{"method": "respSttDelta", "data": {"respId": "resp_001", "delta": "今天天"}}
{"method": "respSttDelta", "data": {"respId": "resp_001", "delta": "今天天气"}}
{"method": "respSttDone", "data": {"respId": "resp_001", "text": "今天天气怎么样"}}| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID |
| delta | string | 增量识别文本(实时更新,可能不稳定) |
| text | string | 最终识别文本(稳定结果,仅 Done 消息有) |
4.5 audioSpeechStarted / Stopped / Interrupted - TTS 状态
{"method": "audioSpeechStarted"}
{"method": "audioSpeechStopped"}
{"method": "audioSpeechInterrupted"}这三个消息均无 data 字段:
audioSpeechStarted:TTS 音频开始从 UDP 下推,设备应开始播放audioSpeechStopped:TTS 全部播完,设备可进入下一轮监听audioSpeechInterrupted:用户打断(发送respCancel),设备应立即停止播放
4.6 respAudioStart / respAudioDone - TTS 音频流
{"method": "respAudioStart", "data": {"respId": "resp_001"}}
{"method": "respAudioDone", "data": {"respId": "resp_001"}}| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID |
respAudioStart 在 UDP 音频帧开始下推前发送,respAudioDone 在最后一帧发送后发送。
4.7 respMedia - 媒体内容
{
"method": "respMedia",
"data": {
"respId": "resp_001",
"mediaType": "image",
"url": "https://oss.example.com/ai-gen-image.png",
"mimeType": "image/png",
"desc": "根据您的描述生成的图片"
}
}| 字段 | 类型 | 说明 |
|---|---|---|
| respId | string | 响应 ID |
| mediaType | string | 媒体类型:image、video、file |
| url | string | 媒体资源 URL |
| mimeType | string | MIME 类型(可选) |
| desc | string | 媒体描述(可选) |
4.8 error - 错误通知
{"method": "error", "data": {"code": "invalidInput", "message": "modalities 字段缺失"}}
{"method": "error", "data": {"code": "sessionExpired", "message": "会话已过期,请重新创建", "respId": "resp_001"}}
{"method": "error", "data": {"code": "modelTimeout", "message": "LLM 响应超时"}}| 字段 | 类型 | 说明 |
|---|---|---|
| code | string | 错误码(见第 7 节) |
| message | string | 可读的错误描述 |
| respId | string | 关联的响应 ID(可选,错误发生在特定响应时携带) |
5. ContentPart 多模态内容类型
inputSend 的 contents 数组中每个元素为一个 ContentPart:
| type | 说明 | 关键字段 | 示例 |
|---|---|---|---|
text | 纯文本 | text | {"type":"text","text":"你好"} |
image_url | 图片 URL | imageUrl | {"type":"image_url","imageUrl":"https://..."} |
audio | 音频数据 | audioData, audioFmt | {"type":"audio","audioData":"base64...","audioFmt":"wav"} |
video_url | 视频 URL | videoUrl | {"type":"video_url","videoUrl":"https://..."} |
file_url | 文件 URL | fileUrl, fileMime | {"type":"file_url","fileUrl":"https://...","fileMime":"application/pdf"} |
完整字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| type | string | 内容类型(必填) |
| text | string | 文本内容(type=text 时使用) |
| imageUrl | string | 图片 URL(type=image_url 时使用) |
| audioData | string | 音频数据(Base64 编码,type=audio 时使用) |
| audioFmt | string | 音频格式(type=audio 时使用,如 wav、mp3) |
| videoUrl | string | 视频 URL(type=video_url 时使用) |
| fileUrl | string | 文件 URL(type=file_url 时使用) |
| fileMime | string | 文件 MIME 类型(type=file_url 时使用) |
| fileName | string | 文件名(可选,便于展示) |
文件预上传
设备端可通过以下接口上传文件(图片/视频/文档),获取 URL 后在 inputSend 中引用。
完整接口规范参见:设备文件上传
- URL:
/api/v1/things/device/edge/upload-file - Method:
POST - Content-Type:
multipart/form-data - 认证:Basic Auth,使用 MQTT 格式生成的账号密码
curl --location --request POST 'http://{host}/api/v1/things/device/edge/upload-file' \
--header 'Authorization: Basic {base64(mqtt_username:mqtt_password)}' \
--form 'file=@"/path/to/photo.jpg"'响应示例:
{
"code": 200,
"msg": "success",
"data": {
"filePath": "edge/{productID}/{deviceName}/250225/125609/photo.jpg",
"fileUri": "https://xxx.oss.com/edge/{productID}/{deviceName}/250225/125609/photo.jpg"
}
}上传成功后,将 data.fileUri 作为 imageUrl 或 fileUrl 填入 inputSend 的 contents 中。
禁止上传 html、php、jsp 等危险文件类型。MQTT 账号密码格式参见 MQTT 认证。
6. UDP 音频通道
当 sessionCreate 的 transport 为 udp(默认)时,语音数据通过 UDP 传输而非 MQTT,以降低延迟。
UDP 通道建立流程:
- 设备发送
sessionCreate(含transport: "udp") - 云端返回
sessionCreated,其中udp字段包含连接信息 - 设备建立 UDP Socket,记录
server、port、key、nonce - 设备发送
audioStart,开始上传加密音频帧 - 云端通过同一 UDP 通道下推 TTS 音频帧
UDP 音频包需使用 AES-CTR 加密,完整的包格式、加密规范和音频编码要求,请参见:
7. AI 物模型联动
在 AI 对话过程中,如果用户请求控制设备(如"打开客厅的灯"、"调低温度到 22 度"),AI 服务会通过物模型 Action 下行来执行设备控制,而不是直接操作硬件。
7.1 联动原理
用户语音/文字
↓
AI 解析意图 + 调用 MCP 工具
↓
AI 服务生成 thing_action 消息
↓
dmsvr 通过物模型 action 通道下推到设备
↓
设备执行动作,通过 thing_action_reply 上报结果
↓
AI 服务接收结果,生成最终回复文本7.2 下行消息路由
handleAiDownlinkMsg 函数处理来自 AI 服务的下行消息,支持三类 kind:
| kind | 说明 | 下推通道 |
|---|---|---|
thing_event | AI 触发设备事件 | $thing/down/thing/{ProductID}/{DeviceName}(event) |
thing_action | AI 调用设备行为 | $thing/down/thing/{ProductID}/{DeviceName}(action) |
thing_action_resp | AI 返回行为执行结果 | $thing/down/thing/{ProductID}/{DeviceName}(action) |
ai_msg | AI 协议下行消息 | $thing/down/ai/{ProductID}/{DeviceName} |
设备通过标准物模型 Topic 接收 thing_action,执行完毕后通过 $thing/up/thing/.../action 上报结果。
7.3 设备端处理建议
设备端实现时,需要同时订阅以下两个 Topic:
$thing/down/ai/{ProductID}/{DeviceName}— AI 协议消息(会话管理、文本、TTS 状态)$thing/down/thing/{ProductID}/{DeviceName}— 物模型下行(AI 发起的设备控制)
物模型协议详情参见:物模型协议文档
8. 交互流程
8.1 纯文字对话
8.2 实时语音对话
8.3 多模态输入(文字+图片)
8.4 用户打断 AI 回复
8.5 AI 联动设备控制
8.6 完整设备 AI 交互生命周期
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 产品创建 │────>│ Agent 绑定 │────>│ 设备绑定 │────>│ 自动创建 │
│ Product │ │ Product. │ │ DeviceInfo │ │ Clone │
│ │ │ defaultAgent│ │ .bind │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ AI 响应 │<────│ LLM/TTS │<────│ aicenter │<────│ MQTT │
│ MQTT 下行 │ │ 处理 │ │ RPC 调用 │ │ sessionCreate│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘9. 错误码
| 错误码 | 说明 |
|---|---|
invalidInput | 无效输入(参数缺失、格式错误) |
modelOverloaded | 模型过载 |
modelTimeout | 模型超时 |
sessionExpired | 会话已过期(需重新 sessionCreate) |
quotaExceeded | 配额超限 |
internalError | 内部错误 |
unsupportedModality | 不支持的模态类型 |
错误处理建议:
| 错误码 | 设备端处理方式 |
|---|---|
invalidInput | 检查参数格式后重试 |
sessionExpired | 重新发送 sessionCreate 创建新会话 |
modelOverloaded | 等待 1-3 秒后重试 |
modelTimeout | 等待 3-5 秒后重试,或通知用户 |
quotaExceeded | 停止请求,等待配额恢复 |
internalError | 等待后重试,多次失败则上报 |
更新日志
2026/3/12 18:14
查看所有更新日志
69dce-docs: 新增设备接入-联犀协议-AI交互文档于
