Files
story/docs/device-registration-to-chat-api.md
Codex Local 0c2151ace9
ci / verify (push) Canceled after 0s
chore: checkpoint 第一版
2026-09-06 17:04:11 +08:00

858 lines
29 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设备注册到聊天记录上传接口文档
## 1. 设备请求平台时的请求头
运营后台和用户 App 调用 `/api/v1/*` 时使用:
```http
Authorization: Bearer <用户访问令牌>
```
设备调用 `/device/*` 时使用以下 HMAC 请求头:
| 请求头 | 中文含义 | 计算方式 |
|---|---|---|
| `X-Device-Id` | 平台设备编号 | 平台登记设备时返回的 `credential.device_id` |
| `X-Device-Timestamp` | 请求时间 | 当前 UTC 时间,RFC3339 格式,例如 `2026-09-05T09:20:00Z` |
| `X-Device-Nonce` | 防重放随机数 | 每个请求重新生成,长度 16–128 字符,不得重复 |
| `X-Device-Content-SHA256` | 请求正文摘要 | `hex_lower(SHA256(rawBody))`;GET 使用空字节正文 |
| `X-Device-Signature` | 请求签名 | `base64url_no_padding(HMAC-SHA256(platformDeviceSecret, canonical))` |
| `Idempotency-Key` | 业务幂等键 | 仅指定接口需要;同一次请求重试必须保持不变,长度 16–128 字符 |
| `Content-Type` | 正文类型 | 有 JSON 正文时为 `application/json` |
签名原文 `canonical`:
```text
{大写HTTP方法}\n{请求路径和查询参数}\n{X-Device-Timestamp}\n{X-Device-Nonce}\n{X-Device-Content-SHA256}
```
计算示例:
```text
body = UTF8('{"hardware_id":"echoear-a1b2c3"}')
bodySha256 = hex_lower(SHA256(body))
timestamp = "2026-09-05T09:20:00Z"
nonce = "d8b81b916ddd4f4584a80a82"
canonical =
"POST\n" +
"/device/v3/hardware-agent-credentials:provision\n" +
timestamp + "\n" +
nonce + "\n" +
bodySha256
signature = base64url_no_padding(
HMAC-SHA256(platformDeviceSecret, UTF8(canonical))
)
```
注意:
- 摘要必须针对实际发送的原始字节计算,签名后不能再次格式化 JSON。
- 查询参数属于签名内容,例如 `/device/v2/commands?limit=10`。
- GET 空正文 SHA-256 固定为 `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`。
- `platformDeviceSecret` 是平台注册时返回的设备密钥,不是火山 `Product Secret`,也不是火山 `device_secret`。
- 平台统一响应:成功为 `{"request_id":"...","data":...}`;失败为 `{"request_id":"...","error":...}`。
## 2. 完整流程
| 顺序 | 调用方 | 动作 | 接口/地址 | 结果 |
|---:|---|---|---|---|
| 1 | 运营平台 | 登记设备 | `POST /api/v1/ops/devices` | 返回平台设备 ID、平台设备密钥和绑定码 |
| 2 | 用户 App | 绑定设备 | `POST /api/v1/device-claims` | 状态变为 `BOUND` |
| 3 | 设备 | 获取火山运行凭据 | `POST /device/v3/hardware-agent-credentials:provision` | 平台动态注册火山设备,消耗一个 License,状态变为 `ACTIVE` |
| 4 | 设备 | 检查固件 | `GET /device/v2/bootstrap` | 得到 `firmware_compatible` |
| 5 | 设备 | 获取设备配置 | `GET /device/v2/config` | 得到配置版本和运行时配置 |
| 6 | 设备 | 判断是否继续 | `firmware_compatible=false` 时停止聊天并进入 OTA 流程 | 只有兼容固件可以继续 |
| 7 | 设备 | 创建对话会话 | `POST /device/v3/agent-sessions` | 获取 Bot ID、系统提示词、人设和授权记忆 |
| 8 | 设备 | 连接火山 | 火山 WebSocket + `session.update` | 注入本次人设并建立 16 kHz PCM 对话 |
| 9 | 设备 | 上报会话开始 | `POST /device/v3/agent-sessions/{id}/events` | 平台会话变为 `ACTIVE` |
| 10 | 设备 | 上传最终聊天文本 | `POST /device/v3/agent-sessions/{id}/turns:batch` | 用户和智能体最终文本入库 |
| 11 | 设备 | 上报会话结束 | 同一个 events 接口,`session_ended` | 平台会话变为 `COMPLETED` |
激活状态:
```text
FACTORY → CLAIMABLE → BOUND → PROVISIONING → ACTIVE → LOST/REVOKED
```
设备重置流程:
```text
停止语音 → 上传剩余聊天记录 → 上报session_ended → POST /device/v3/factory-reset
→ 平台解除绑定并暂停火山凭据 → 设备按local_actions清理 → 重启进入CLAIMABLE
```
## 3. 接口
### 3.1 登记设备
地址:
```http
POST /api/v1/ops/devices
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| `model` | string | 是 | 设备型号 |
| `hardware_revision` | string | 否 | 硬件版本 |
| `minimum_security_version` | string | 否 | 最低安全版本 |
| `minimum_firmware_version` | string | 否 | 最低固件版本 |
| `household_id` | UUID | 否 | 直接绑定的家庭编号;走绑定码时不传 |
| `subject_id` | UUID | 否 | 直接绑定的服务对象编号;走绑定码时不传 |
| `voice_runtime` | enum | 是 | 传 `volc_hardware_agent` |
| `hardware_agent_bot_id` | string | 否 | 兼容字段;当前方案不传,由平台产品配置决定 |
| `hardware_agent_transport` | enum | 否 | `low_load` 或 `high_quality`;建议由平台配置 |
| `hardware_agent_minimum_sdk_version` | string | 否 | 最低硬件智能体 SDK 版本 |
| `activation_state` | enum | 是 | `FACTORY`、`CLAIMABLE` 或 `BOUND`;一般传 `CLAIMABLE` |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `device.id` | UUID | 平台设备编号 |
| `device.status` | string | 设备业务状态 |
| `device.activation_state` | enum | 激活状态 |
| `device.activation_revision` | integer | 激活状态版本 |
| `device.model` | string | 设备型号 |
| `device.hardware_revision` | string/null | 硬件版本 |
| `device.minimum_security_version` | string/null | 最低安全版本 |
| `device.minimum_firmware_version` | string/null | 最低固件版本 |
| `device.voice_runtime` | enum | 语音运行时 |
| `device.version` | integer | 设备记录版本 |
| `credential.device_id` | UUID | 平台设备编号 |
| `credential.secret` | string | 设备调用平台所用的 HMAC 密钥,仅明文返回一次 |
| `credential.key_version` | integer | 平台设备密钥版本 |
| `credential.issued_at` | datetime | 密钥签发时间 |
| `claim.claim_code` | string/null | 一次性绑定码 |
| `claim.issued_at` | datetime/null | 绑定码签发时间 |
| `claim.expires_at` | datetime/null | 绑定码过期时间 |
示例:
```http
POST /api/v1/ops/devices
Authorization: Bearer <ops-token>
Content-Type: application/json
{
"model": "echoear-esp32s3",
"hardware_revision": "v1.0",
"minimum_firmware_version": "1.0.0",
"voice_runtime": "volc_hardware_agent",
"activation_state": "CLAIMABLE"
}
```
```json
{
"request_id": "req-register-001",
"data": {
"device": {
"id": "10000000-0000-0000-0000-000000000512",
"status": "registered",
"activation_state": "CLAIMABLE",
"activation_revision": 1,
"model": "echoear-esp32s3",
"voice_runtime": "volc_hardware_agent",
"version": 1
},
"credential": {
"device_id": "10000000-0000-0000-0000-000000000512",
"secret": "<platform-device-secret>",
"key_version": 1,
"issued_at": "2026-09-05T09:00:00Z"
},
"claim": {
"claim_code": "ABCD-EFGH-IJKL-MNOP",
"issued_at": "2026-09-05T09:00:00Z",
"expires_at": "2026-09-06T09:00:00Z"
}
}
}
```
### 3.2 用户绑定设备
地址:
```http
POST /api/v1/device-claims
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| `claim_code` | string | 是 | 一次性绑定码,不能填写设备 UUID |
| `household_id` | UUID | 是 | 家庭编号 |
| `subject_id` | UUID | 是 | 设备服务对象编号 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `binding_id` | UUID | 设备绑定关系编号 |
| `device_id` | UUID | 平台设备编号 |
| `household_id` | UUID | 家庭编号 |
| `subject_id` | UUID | 服务对象编号 |
| `bound_at` | datetime | 绑定时间 |
示例:
```http
POST /api/v1/device-claims
Authorization: Bearer <user-token>
Content-Type: application/json
{
"claim_code": "ABCD-EFGH-IJKL-MNOP",
"household_id": "10000000-0000-0000-0000-000000000211",
"subject_id": "10000000-0000-0000-0000-000000000411"
}
```
```json
{
"request_id": "req-bind-001",
"data": {
"binding_id": "10000000-0000-0000-0000-000000000611",
"device_id": "10000000-0000-0000-0000-000000000512",
"household_id": "10000000-0000-0000-0000-000000000211",
"subject_id": "10000000-0000-0000-0000-000000000411",
"bound_at": "2026-09-05T09:10:00Z"
}
}
```
### 3.3 占用 License 并获取火山设备凭据
地址:
```http
POST /device/v3/hardware-agent-credentials:provision
```
输入:
| 字段 | 位置 | 类型 | 必填 | 中文含义 |
|---|---|---|---:|---|
| `Idempotency-Key` | Header | string | 是 | 凭据申请幂等键,16–128 字符 |
| `hardware_id` | Body | string | 是 | 不随固件升级变化的物理硬件标识 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `instance_id` | string | 火山硬件智能体实例编号 |
| `product_key` | string | 火山产品标识 |
| `device_name` | string | 火山设备名称 |
| `hardware_id` | string | 已绑定的物理硬件标识 |
| `device_secret` | string | 火山设备专属密钥,需要加密保存 |
| `credential_ref` | string | 平台内部凭据引用 |
| `credential_version` | integer | 火山设备凭据版本 |
| `credential_source` | enum | `platform_dynamic_registration` 为新注册,`platform_stored` 为复用已有凭据 |
| `issued_at` | datetime | 凭据签发时间 |
示例:
```http
POST /device/v3/hardware-agent-credentials:provision
Idempotency-Key: provision-device-00000512-v1
Content-Type: application/json
<设备HMAC请求头>
{"hardware_id":"echoear-esp32s3-a1b2c3d4"}
```
```json
{
"request_id": "req-provision-001",
"data": {
"instance_id": "6a92945cc0c4689234aef656",
"product_key": "6a9568047dd4b23eacab9b1a",
"device_name": "dt-10000000000000000000000000000512",
"hardware_id": "echoear-esp32s3-a1b2c3d4",
"device_secret": "<volc-device-secret>",
"credential_ref": "volc-device-secret://10000000-0000-0000-0000-000000000512",
"credential_version": 1,
"credential_source": "platform_dynamic_registration",
"issued_at": "2026-09-05T09:12:00Z"
}
}
```
### 3.4 设备恢复出厂
地址:
```http
POST /device/v3/factory-reset
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| Header `Idempotency-Key` | string | 是 | 本次重置幂等键,16–128 字符;重试保持不变 |
| `reset_id` | UUID | 是 | 本次重置唯一编号 |
| `reset_counter` | integer | 是 | 设备安全存储中的单调递增重置次数 |
| `occurred_at` | datetime | 是 | 设备确认重置的 UTC 时间;不得晚于请求时间 5 分钟 |
| `trigger` | enum | 是 | `physical_long_press`、`device_menu` 或 `service_tool` |
| `firmware_version` | string | 是 | 当前固件版本 |
| `pending_turn_count` | integer | 是 | 未上传聊天记录数;必须为 `0` |
| `active_conversation_id` | UUID | 否 | 设备认为仍在运行的会话编号 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `reset_id` | UUID | 重置编号 |
| `device_id` | UUID | 平台设备编号 |
| `activation_state` | enum | 重置后固定为 `CLAIMABLE` |
| `reset_counter` | integer | 平台接受的重置次数 |
| `binding_removed` | boolean | 是否关闭了原用户绑定 |
| `previous_binding_id` | UUID/null | 原绑定关系编号 |
| `provider_credential_status` | enum | 火山设备凭据状态,通常为 `SUSPENDED` |
| `license_action` | enum | 固定为 `RETAINED`,重置不释放火山 License |
| `ended_conversation_count` | integer | 平台强制结束的残留会话数 |
| `cancelled_command_count` | integer | 平台取消的待执行命令数 |
| `config_revision` | integer | 重置后的设备配置版本 |
| `claim.code` | string | 新的一次性绑定码 |
| `claim.issued_at` | datetime | 绑定码签发时间 |
| `claim.expires_at` | datetime | 绑定码过期时间 |
| `local_actions` | string[] | 设备收到成功响应后必须执行的本地清理动作 |
| `reset_at` | datetime | 平台完成重置的时间 |
平台同时把旧用户侧的设备状态置为离线,并推送 `device.binding.reset` 事件。设备收到成功响应后,按 `local_actions` 清理本地火山凭据、用户上下文、会话与网络配置;设备平台身份和 HMAC 密钥不得删除。
示例:
```http
POST /device/v3/factory-reset
Idempotency-Key: factory-reset-00000512-0001
Content-Type: application/json
<设备HMAC请求头>
{
"reset_id": "10000000-0000-0000-0000-000000000901",
"reset_counter": 1,
"occurred_at": "2026-09-05T09:30:00Z",
"trigger": "physical_long_press",
"firmware_version": "1.1.0",
"pending_turn_count": 0
}
```
```json
{
"request_id": "req-reset-001",
"data": {
"reset_id": "10000000-0000-0000-0000-000000000901",
"device_id": "10000000-0000-0000-0000-000000000512",
"activation_state": "CLAIMABLE",
"reset_counter": 1,
"binding_removed": true,
"previous_binding_id": "10000000-0000-0000-0000-000000000611",
"provider_credential_status": "SUSPENDED",
"license_action": "RETAINED",
"ended_conversation_count": 0,
"cancelled_command_count": 0,
"config_revision": 9,
"claim": {
"code": "QRST-UVWX-YZ23-4567",
"issued_at": "2026-09-05T09:30:01Z",
"expires_at": "2026-09-06T09:30:01Z"
},
"local_actions": [
"clear_volc_credential",
"clear_user_context",
"clear_session_state",
"clear_network_credentials",
"reboot_to_claimable"
],
"reset_at": "2026-09-05T09:30:01Z"
}
}
```
设备必须保留平台 `device_id`、平台 HMAC `secret` 和 `hardware_id`;其余用户上下文、火山凭据、会话状态和网络配置按 `local_actions` 清除。
### 3.5 检查固件版本
地址:
```http
GET /device/v2/bootstrap
```
输入:
| 字段 | 位置 | 类型 | 必填 | 中文含义 |
|---|---|---|---:|---|
| `firmware_version` | Query | string | 是 | 当前固件版本 |
| `device_time` | Query | datetime | 是 | 设备 UTC 时间,RFC3339 |
| `board_rev` | Query | string | 否 | 硬件版本 |
| `rtc_sdk_version` | Query | string | 否 | 火山 SDK 版本 |
| `codec` | Query | string | 否 | 音频格式,例如 `pcm16` |
| `sample_rate` | Query | integer | 否 | 音频采样率,当前为 `16000` |
| `network` | Query | string | 否 | 网络类型,例如 `wifi` |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `server_time` | datetime | 平台 UTC 时间 |
| `clock_skew_ms` | integer | 平台时间减设备时间的毫秒差 |
| `device.device_id` | UUID | 平台设备编号 |
| `device.status` | string | 设备业务状态 |
| `device.model` | string | 设备型号 |
| `device.hardware_revision` | string/null | 硬件版本 |
| `device.minimum_security_version` | string/null | 最低安全版本 |
| `device.minimum_firmware_version` | string/null | 最低固件版本 |
| `device.firmware_compatible` | boolean | 当前固件是否允许继续聊天 |
| `device.config_revision` | integer | 当前平台配置版本 |
| `device.binding.bound` | boolean | 是否已绑定 |
| `device.binding.household_id` | UUID/null | 家庭编号 |
| `device.binding.subject_id` | UUID/null | 服务对象编号 |
| `device.binding.subject_name` | string/null | 服务对象名称 |
示例:
```http
GET /device/v2/bootstrap?firmware_version=1.0.0&board_rev=v1.0&rtc_sdk_version=1.1.0&codec=pcm16&sample_rate=16000&network=wifi&device_time=2026-09-05T09%3A15%3A00Z
<设备HMAC请求头>
```
```json
{
"request_id": "req-bootstrap-001",
"data": {
"server_time": "2026-09-05T09:15:01Z",
"clock_skew_ms": 1000,
"device": {
"device_id": "10000000-0000-0000-0000-000000000512",
"status": "registered",
"model": "echoear-esp32s3",
"minimum_firmware_version": "1.0.0",
"firmware_compatible": true,
"config_revision": 8,
"binding": {
"bound": true,
"household_id": "10000000-0000-0000-0000-000000000211",
"subject_id": "10000000-0000-0000-0000-000000000411",
"subject_name": "测试用户"
}
}
}
}
```
### 3.6 获取设备配置
地址:
```http
GET /device/v2/config
```
输入:
| 字段 | 位置 | 类型 | 必填 | 中文含义 |
|---|---|---|---:|---|
| `If-None-Match` | Header | string | 否 | 本地配置 ETag;未变化时返回 `304` |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| Response Header `ETag` | string | 当前配置 ETag |
| `device_id` | UUID | 平台设备编号 |
| `revision` | integer | 配置版本 |
| `desired_config` | object | 平台期望设备应用的配置 |
| `desired_config.voice_runtime` | enum | 语音运行时 |
| `desired_config.hardware_agent_transport` | enum | 火山传输方式 |
| `desired_config.minimum_sdk_version` | string | 最低 SDK 版本 |
| `reported_revision` | integer/null | 设备此前报告的配置版本 |
| `binding` | object | 当前绑定信息,字段与 bootstrap 相同 |
示例:
```http
GET /device/v2/config
If-None-Match: "7"
<设备HMAC请求头>
```
```json
{
"request_id": "req-config-001",
"data": {
"device_id": "10000000-0000-0000-0000-000000000512",
"revision": 8,
"desired_config": {
"voice_runtime": "volc_hardware_agent",
"hardware_agent_transport": "low_load",
"minimum_sdk_version": "1.1.0"
},
"reported_revision": 7,
"binding": {
"bound": true,
"household_id": "10000000-0000-0000-0000-000000000211",
"subject_id": "10000000-0000-0000-0000-000000000411"
}
}
}
```
### 3.7 创建会话并获取系统提示词
地址:
```http
POST /device/v3/agent-sessions
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| Header `Idempotency-Key` | string | 是 | 创建会话幂等键,16–128 字符 |
| `device_time` | datetime | 是 | 设备 UTC 时间 |
| `monotonic_ms` | integer | 否 | 开机后的单调时钟毫秒数 |
| `capabilities.hardware_agent` | boolean | 是 | 必须为 `true` |
| `capabilities.sdk_version` | string | 是 | 硬件智能体 SDK 版本 |
| `capabilities.transports` | string[] | 是 | 支持的传输方式 |
| `capabilities.input_audio_format` | string | 是 | 上行音频能力,当前为 `pcm_s16le_16000_mono` |
| `capabilities.output_audio_format` | string | 是 | 下行音频能力,当前为 `pcm_s16le_16000_mono` |
| `capabilities.server_event_callback` | boolean | 是 | 当前传 `false` |
| `capabilities.transcript_upload` | boolean | 是 | 必须为 `true` |
| `capabilities.dynamic_session_config` | boolean | 是 | 必须为 `true` |
| `capabilities.credential_rotation` | boolean | 是 | 必须为 `true` |
| `capabilities.near_pcm_recording` | boolean | 是 | 是否支持近端录音 |
| `capabilities.far_pcm_recording` | boolean | 是 | 是否支持远端录音 |
| `network.type` | string | 是 | 网络类型 |
| `network.rssi` | integer | 否 | Wi-Fi RSSI |
| `requested_goal` | string | 是 | 固定为 `open_conversation` |
| `config_revision` | integer | 是 | `/device/v2/config` 返回的配置版本 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `conversation_id` | UUID | 平台会话编号 |
| `status` | string | 正常为 `SESSION_READY` |
| `runtime_type` | string | `volc_hardware_agent` |
| `runtime.provider` | string | 服务商,当前为 `volcengine` |
| `runtime.bot_id` | string | 本次使用的 Bot ID |
| `runtime.transport` | string | 本次传输方式 |
| `runtime.provider_session_ref` | string | 火山会话引用 |
| `runtime.session_nonce` | string | 会话证明随机值 |
| `runtime.config_digest` | string | 本次动态配置摘要 |
| `runtime.wait_for_session_update` | boolean | 是否必须先应用动态人设 |
| `session_config.dialog_id` | string | 本次对话标识 |
| `session_config.system_prompt` | string | 系统提示词/核心人设 |
| `session_config.persona_tone` | string | 人设语气和表达风格 |
| `session_config.authorized_context` | string | 授权的用户背景、近期摘要和长期记忆 |
| `session_config.prompt_bundle_version` | string | 提示词版本 |
| `session_config.tool_url` | URL/null | 工具调用地址 |
| `session_config.tool_scope_token` | string/null | 本次会话工具令牌 |
| `privacy.retention_mode` | string | 数据保留模式 |
| `privacy.record_near` | boolean | 是否允许近端录音 |
| `privacy.record_far` | boolean | 是否允许远端录音 |
| `privacy.upload_allowed` | boolean | 是否允许上传聊天记录 |
| `session.engine_mode` | string | 会话引擎模式 |
| `session.heartbeat_interval_sec` | integer | 会话状态上报间隔 |
| `session.allocation_expires_at` | datetime | 会话分配过期时间 |
示例:
```http
POST /device/v3/agent-sessions
Idempotency-Key: session-device-00000512-0001
Content-Type: application/json
<设备HMAC请求头>
{
"device_time": "2026-09-05T09:20:00Z",
"monotonic_ms": 812345,
"capabilities": {
"hardware_agent": true,
"sdk_version": "1.1.0-echoear",
"transports": ["low_load"],
"input_audio_format": "pcm_s16le_16000_mono",
"output_audio_format": "pcm_s16le_16000_mono",
"server_event_callback": false,
"transcript_upload": true,
"dynamic_session_config": true,
"credential_rotation": true,
"near_pcm_recording": false,
"far_pcm_recording": false
},
"network": {"type":"wifi","rssi":-52},
"requested_goal": "open_conversation",
"config_revision": 8
}
```
```json
{
"request_id": "req-session-001",
"data": {
"conversation_id": "10000000-0000-0000-0000-000000000801",
"status": "SESSION_READY",
"runtime_type": "volc_hardware_agent",
"runtime": {
"provider": "volcengine",
"bot_id": "botM4gWQ9A9y",
"transport": "low_load",
"provider_session_ref": "provider-session-ref",
"session_nonce": "<session-nonce>",
"config_digest": "<config-digest>",
"wait_for_session_update": true
},
"session_config": {
"dialog_id": "dialog-00000801",
"system_prompt": "你是面向长者的陪伴助手……",
"persona_tone": "率真、自然",
"authorized_context": "经授权的背景和记忆……",
"prompt_bundle_version": "persona-v12"
},
"privacy": {
"retention_mode": "transcript_only",
"record_near": false,
"record_far": false,
"upload_allowed": true
},
"session": {
"engine_mode": "hardware_agent",
"heartbeat_interval_sec": 30,
"allocation_expires_at": "2026-09-05T09:25:00Z"
}
}
}
```
### 3.8 连接火山并注入人设
地址:
```text
wss://ai-gateway.vei.volces.com/v1/realtime?bot={bot_id}&wait_for_session_update=true
```
输入:
| 字段 | 位置 | 中文含义 | 来源/计算 |
|---|---|---|---|
| `bot` | Query | Bot ID | 创建会话返回的 `runtime.bot_id` |
| `X-Auth-Type` | Header | 鉴权类型 | 固定为 `1` |
| `X-Product-Key` | Header | 火山产品标识 | provision 返回的 `product_key` |
| `X-Device-Name` | Header | 火山设备名称 | provision 返回的 `device_name` |
| `X-Random-Num` | Header | 随机数 | 每次连接重新生成 |
| `X-Timestamp` | Header | Unix 秒 | 当前时间 |
| `X-Instance-Id` | Header | 火山实例编号 | provision 返回的 `instance_id` |
| `X-Hardware-Id` | Header | 物理硬件标识 | provision 使用的 `hardware_id` |
| `X-Signature` | Header | 火山鉴权签名 | `base64(HMAC-SHA256(device_secret, canonical))` |
| `session.update` | WebSocket JSON | 动态会话配置 | 注入平台返回的人设和记忆 |
输出:
| 事件 | 中文含义 |
|---|---|
| `session.created` / `session.updated` | 火山会话创建/动态人设生效 |
| `conversation.item.input_audio_transcription.completed` | 用户最终识别文本 |
| `response.audio_transcript.done` | 智能体最终回复文本 |
| 音频数据 | 智能体返回的 PCM16 音频,按 16 kHz 单声道播放 |
| error 事件 | 火山连接或会话失败 |
示例:
```text
canonical = "auth_type=1&device_name={device_name}&random_num={random_num}&product_key={product_key}&timestamp={timestamp}&instance_id={instance_id}"
X-Signature = base64(HMAC-SHA256(device_secret, canonical))
```
```json
{
"event_id": "evt-session-update-001",
"type": "session.update",
"session": {
"object": "realtime.session",
"model": "",
"input_audio_format": "pcm16",
"output_audio_format": "pcm16",
"config": {
"ASRConfig": {"TurnDetectionMode": 0},
"LLMConfig": {
"SystemMessages": [
"<system_prompt>\n表达风格:<persona_tone>\n授权上下文:<authorized_context>\n会话标识:<dialog_id>"
]
}
},
"agent_config": {"EnableConversationStateCallback": false}
}
}
```
### 3.9 上报会话事件
地址:
```http
POST /device/v3/agent-sessions/{conversation_id}/events
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| Path `conversation_id` | UUID | 是 | 平台会话编号 |
| `event_id` | string | 是 | 事件唯一编号 |
| `sequence_no` | integer | 是 | 事件顺序号,从 1 开始递增 |
| `event_type` | enum | 是 | `session_started`、`session_updated`、`session_ended` 或 `session_failed` |
| `occurred_at` | datetime | 是 | 事件发生时间 |
| `provider_session_ref` | string | 是 | 火山会话引用 |
| `session_nonce` | string | 是 | 会话证明随机值 |
| `provider_connection_ref` | string | 启动时必填 | WebSocket 连接标识 |
| `bot_id` | string | 启动时必填 | 实际连接的 Bot ID |
| `transport` | string | 启动时必填 | 实际传输方式 |
| `config_digest` | string | 启动时必填 | 已应用的人设配置摘要 |
| `dynamic_config_applied` | boolean | 启动时必填 | 必须为 `true` |
| `error_code` | string | 失败时填写 | 失败错误码 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `conversation_id` | UUID | 平台会话编号 |
| `status` | string | 会话状态,开始后为 `ACTIVE`,正常结束后为 `COMPLETED` |
| `action` | enum | `continue` 或 `stop_session` |
| `duplicate` | boolean | 是否为重复事件 |
| `accepted_at` | datetime | 平台接收时间 |
示例:
开始请求:
```json
{
"event_id": "device-event-start-001",
"sequence_no": 1,
"event_type": "session_started",
"occurred_at": "2026-09-05T09:20:03Z",
"provider_session_ref": "provider-session-ref",
"session_nonce": "<session-nonce>",
"provider_connection_ref": "ws-connection-001",
"bot_id": "botM4gWQ9A9y",
"transport": "low_load",
"config_digest": "<config-digest>",
"dynamic_config_applied": true
}
```
结束示例:
```json
{
"event_id": "device-event-end-001",
"sequence_no": 2,
"event_type": "session_ended",
"occurred_at": "2026-09-05T09:25:00Z",
"provider_session_ref": "provider-session-ref",
"session_nonce": "<session-nonce>"
}
```
响应示例:
```json
{
"request_id": "req-event-001",
"data": {
"conversation_id": "10000000-0000-0000-0000-000000000801",
"status": "ACTIVE",
"action": "continue",
"duplicate": false,
"accepted_at": "2026-09-05T09:20:03Z"
}
}
```
### 3.10 上传聊天记录
地址:
```http
POST /device/v3/agent-sessions/{conversation_id}/turns:batch
```
输入:
| 字段 | 类型 | 必填 | 中文含义 |
|---|---|---:|---|
| Path `conversation_id` | UUID | 是 | 平台会话编号 |
| Header `Idempotency-Key` | string | 是 | 本批记录幂等键,16–128 字符 |
| `turns` | array | 是 | 最终聊天记录数组,每批 1–500 条 |
| `turns[].turn_key` | string | 是 | 会话内唯一的轮次标识,最长 255 字符 |
| `turns[].sequence_no` | integer | 是 | 对话顺序号,会话内唯一且大于 0 |
| `turns[].speaker` | enum | 是 | 用户为 `subject`,智能体为 `agent` |
| `turns[].start_at` | datetime | 是 | 本轮开始时间 |
| `turns[].end_at` | datetime | 是 | 本轮结束时间,不早于开始时间 |
| `turns[].final_text` | string | 是 | 最终文本,最长 20000 字符 |
| `turns[].interrupted` | boolean | 是 | 本轮是否被打断 |
输出:
| 字段 | 类型 | 中文含义 |
|---|---|---|
| `conversation_id` | UUID | 平台会话编号 |
| `accepted_count` | integer | 本次新写入的记录数 |
| `duplicate_count` | integer | 本次幂等重复的记录数 |
示例:
```http
POST /device/v3/agent-sessions/10000000-0000-0000-0000-000000000801/turns:batch
Idempotency-Key: turns-conversation-00000801-0001
Content-Type: application/json
<设备HMAC请求头>
{
"turns": [
{
"turn_key": "user-final-001",
"sequence_no": 1,
"speaker": "subject",
"start_at": "2026-09-05T09:20:10Z",
"end_at": "2026-09-05T09:20:12Z",
"final_text": "今天天气怎么样?",
"interrupted": false
},
{
"turn_key": "agent-final-001",
"sequence_no": 2,
"speaker": "agent",
"start_at": "2026-09-05T09:20:12Z",
"end_at": "2026-09-05T09:20:16Z",
"final_text": "我来帮你看看。",
"interrupted": false
}
]
}
```
```json
{
"request_id": "req-turns-001",
"data": {
"conversation_id": "10000000-0000-0000-0000-000000000801",
"accepted_count": 2,
"duplicate_count": 0
}
}
```