chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
+857
View File
@@ -0,0 +1,857 @@
# 设备注册到聊天记录上传接口文档
## 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
}
}
```