- Updated the WebSocket API reference to improve clarity by removing unnecessary headings and emphasizing message types. - Revised the index.md to specify 'chroma' as the knowledge base, enhancing the overview of the platform's architecture.
881 lines
21 KiB
Markdown
881 lines
21 KiB
Markdown
# WebSocket 协议
|
||
|
||
WebSocket 端点提供双向实时语音对话能力,支持音频流输入输出和文本消息交互。
|
||
|
||
## 连接地址
|
||
|
||
```
|
||
ws://<host>/ws?assistant_id=<assistant_id>
|
||
```
|
||
|
||
- `assistant_id` 为必填 query 参数,用于从数据库加载该助手的运行时配置。
|
||
|
||
## 传输规则
|
||
|
||
- **文本帧**:JSON 格式控制消息
|
||
- **二进制帧**:PCM 音频数据(`pcm_s16le`, 16kHz, 单声道)
|
||
- 帧长度必须是 640 字节的整数倍(20ms 音频 = 640 bytes)
|
||
|
||
---
|
||
|
||
## 消息流程
|
||
|
||
```
|
||
Client -> session.start
|
||
Server <- session.started
|
||
Server <- (optional) config.resolved
|
||
Client -> (binary pcm frames...)
|
||
Server <- input.speech_started / transcript.delta / transcript.final
|
||
Server <- assistant.response.delta / assistant.response.final
|
||
Server <- output.audio.start
|
||
Server <- (binary pcm frames...)
|
||
Server <- output.audio.end
|
||
Client -> output.audio.played (optional)
|
||
Client -> session.stop
|
||
Server <- session.stopped
|
||
```
|
||
|
||
---
|
||
|
||
## 客户端 -> 服务端消息
|
||
|
||
`session.start`
|
||
|
||
客户端连接后发送的第一个消息,用于启动对话会话。
|
||
|
||
```json
|
||
{
|
||
"type": "session.start",
|
||
"audio": {
|
||
"encoding": "pcm_s16le",
|
||
"sample_rate_hz": 16000,
|
||
"channels": 1
|
||
},
|
||
"metadata": {
|
||
"channel": "web",
|
||
"source": "web_debug",
|
||
"history": {
|
||
"userId": 1
|
||
},
|
||
"overrides": {
|
||
"systemPrompt": "你是简洁助手",
|
||
"greeting": "你好,我能帮你什么?",
|
||
"output": {
|
||
"mode": "audio"
|
||
}
|
||
},
|
||
"dynamicVariables": {
|
||
"customer_name": "Alice",
|
||
"plan_tier": "Pro"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | 是 | 固定为 `"session.start"` |
|
||
| `audio` | object | 否 | 音频格式描述 |
|
||
| `audio.encoding` | string | 否 | 固定为 `"pcm_s16le"` |
|
||
| `audio.sample_rate_hz` | number | 否 | 固定为 `16000` |
|
||
| `audio.channels` | number | 否 | 固定为 `1` |
|
||
| `metadata` | object | 否 | 运行时配置 |
|
||
|
||
**metadata 支持的字段**:
|
||
- `channel` - 渠道标识
|
||
- `source` - 来源标识
|
||
- `history.userId` - 历史记录用户 ID
|
||
- `overrides` - 可覆盖字段(仅限安全白名单)
|
||
- `dynamicVariables` - 动态变量(支持 `{{variable}}` 占位符)
|
||
|
||
**`metadata.overrides` 白名单字段**:
|
||
- `systemPrompt`
|
||
- `greeting`
|
||
- `firstTurnMode`
|
||
- `generatedOpenerEnabled`
|
||
- `output`
|
||
- `bargeIn`
|
||
- `knowledgeBaseId`
|
||
- `knowledge`
|
||
- `tools`
|
||
- `openerAudio`
|
||
|
||
**限制**:
|
||
- `metadata.workflow` 会被忽略(不触发 workflow 事件)
|
||
- 禁止提交 `metadata.services`
|
||
- 禁止提交 `assistantId` / `appId` / `app_id` / `configVersionId` / `config_version_id`
|
||
- 禁止提交包含密钥语义的字段(如 `apiKey` / `token` / `secret` / `password` / `authorization`)
|
||
|
||
---
|
||
|
||
`input.text`
|
||
|
||
发送文本输入,跳过 ASR 识别,直接触发 LLM 回复。
|
||
|
||
```json
|
||
{
|
||
"type": "input.text",
|
||
"text": "你能做什么?"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | 是 | 固定为 `"input.text"` |
|
||
| `text` | string | 是 | 用户文本内容 |
|
||
|
||
---
|
||
|
||
`response.cancel`
|
||
|
||
请求中断当前回答。
|
||
|
||
```json
|
||
{
|
||
"type": "response.cancel",
|
||
"graceful": false
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|
||
|---|---|---|---|---|
|
||
| `type` | string | 是 | - | 固定为 `"response.cancel"` |
|
||
| `graceful` | boolean | 否 | `false` | `false` 立即打断 |
|
||
|
||
---
|
||
|
||
`output.audio.played`
|
||
|
||
客户端回执音频已在本地播放完成(含本地 jitter buffer / 播放队列)。
|
||
|
||
```json
|
||
{
|
||
"type": "output.audio.played",
|
||
"tts_id": "tts_001",
|
||
"response_id": "resp_001",
|
||
"turn_id": "turn_001",
|
||
"played_at_ms": 1730000018450,
|
||
"played_ms": 2520
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | 是 | 固定为 `"output.audio.played"` |
|
||
| `tts_id` | string | 是 | 已完成播放的 TTS 段 ID |
|
||
| `response_id` | string | 否 | 所属回复 ID(建议回传) |
|
||
| `turn_id` | string | 否 | 所属轮次 ID(建议回传) |
|
||
| `played_at_ms` | number | 否 | 客户端本地播放完成时间戳(毫秒) |
|
||
| `played_ms` | number | 否 | 本次播放耗时(毫秒) |
|
||
|
||
---
|
||
|
||
`tool_call.results`
|
||
|
||
回传客户端执行的工具结果。
|
||
|
||
```json
|
||
{
|
||
"type": "tool_call.results",
|
||
"results": [
|
||
{
|
||
"tool_call_id": "call_abc123",
|
||
"name": "weather",
|
||
"output": { "temp_c": 21, "condition": "sunny" },
|
||
"status": { "code": 200, "message": "ok" }
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | 是 | 固定为 `"tool_call.results"` |
|
||
| `results` | array | 否 | 工具结果列表 |
|
||
| `results[].tool_call_id` | string | 是 | 工具调用 ID |
|
||
| `results[].name` | string | 是 | 工具名称 |
|
||
| `results[].output` | any | 否 | 工具输出 |
|
||
| `results[].status` | object | 是 | 执行状态 |
|
||
| `results[].status.code` | number | 是 | HTTP 状态码(200-299 表示成功) |
|
||
| `results[].status.message` | string | 是 | 状态描述 |
|
||
|
||
---
|
||
|
||
`session.stop`
|
||
|
||
结束对话会话。
|
||
|
||
```json
|
||
{
|
||
"type": "session.stop",
|
||
"reason": "client_disconnect"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|---|---|---|---|
|
||
| `type` | string | 是 | 固定为 `"session.stop"` |
|
||
| `reason` | string | 否 | 结束原因 |
|
||
|
||
---
|
||
|
||
`Binary Audio`
|
||
|
||
在 `session.started` 之后可持续发送二进制 PCM 音频。
|
||
|
||
- **格式**:`pcm_s16le`
|
||
- **采样率**:16000 Hz
|
||
- **声道**:1(单声道)
|
||
- **帧长**:20ms = 640 bytes
|
||
|
||
---
|
||
|
||
## 服务端 -> 客户端事件
|
||
|
||
### 事件包络
|
||
|
||
所有 JSON 事件都包含统一包络字段:
|
||
|
||
```json
|
||
{
|
||
"type": "event.name",
|
||
"timestamp": 1730000000000,
|
||
"sessionId": "sess_xxx",
|
||
"seq": 42,
|
||
"source": "asr",
|
||
"trackId": "audio_in",
|
||
"data": {}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `type` | string | 事件类型 |
|
||
| `timestamp` | number | 事件时间戳(Unix 毫秒) |
|
||
| `sessionId` | string | 会话 ID |
|
||
| `seq` | number | 递增序号(用于重放/恢复) |
|
||
| `source` | string | 事件来源:`asr` / `llm` / `tts` / `tool` / `system` / `client` / `server` |
|
||
| `trackId` | string | 事件轨道:`audio_in` / `audio_out` / `control` |
|
||
| `data` | object | 业务数据(可选) |
|
||
|
||
**轨道 ID 说明**:
|
||
|
||
| trackId | 说明 | 相关事件 |
|
||
|---------|------|---------|
|
||
| `audio_in` | ASR/VAD 输入侧事件 | `input.*`, `transcript.*` |
|
||
| `audio_out` | 助手输出侧事件 | `assistant.*`, `output.audio.*`, `response.interrupted`, `metrics.ttfb` |
|
||
| `control` | 会话控制事件 | `session.*`, `error`, `heartbeat`, `(optional) config.resolved` |
|
||
|
||
---
|
||
|
||
### 会话控制类事件
|
||
|
||
#### `session.started`
|
||
|
||
会话启动成功,客户端收到此事件后可以开始发送音频。
|
||
|
||
```json
|
||
{
|
||
"type": "session.started",
|
||
"timestamp": 1730000000000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 1,
|
||
"trackId": "control",
|
||
"tracks": {
|
||
"audio_in": "audio_in",
|
||
"audio_out": "audio_out",
|
||
"control": "control"
|
||
},
|
||
"audio": {
|
||
"encoding": "pcm_s16le",
|
||
"sample_rate_hz": 16000,
|
||
"channels": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `sessionId` | string | 会话唯一标识符 |
|
||
| `trackId` | string | 固定为 `"control"` |
|
||
| `tracks` | object | 可用轨道列表 |
|
||
| `tracks.audio_in` | string | 输入轨道 ID |
|
||
| `tracks.audio_out` | string | 输出轨道 ID |
|
||
| `tracks.control` | string | 控制轨道 ID |
|
||
| `audio` | object | 音频格式配置 |
|
||
| `audio.encoding` | string | 编码格式 |
|
||
| `audio.sample_rate_hz` | number | 采样率 |
|
||
| `audio.channels` | number | 声道数 |
|
||
|
||
---
|
||
|
||
#### `config.resolved`
|
||
|
||
服务端返回的**公开配置快照**。
|
||
默认不发送(SaaS 公网模式建议关闭);仅在 `WS_EMIT_CONFIG_RESOLVED=true` 时发送。
|
||
|
||
```json
|
||
{
|
||
"type": "config.resolved",
|
||
"timestamp": 1730000000001,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 2,
|
||
"trackId": "control",
|
||
"config": {
|
||
"channel": "web_debug",
|
||
"output": {
|
||
"mode": "audio"
|
||
},
|
||
"tools": {
|
||
"enabled": true,
|
||
"count": 2
|
||
},
|
||
"tracks": {
|
||
"audio_in": "audio_in",
|
||
"audio_out": "audio_out",
|
||
"control": "control"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"control"` |
|
||
| `config` | object | SaaS 安全的公开配置快照 |
|
||
| `config.channel` | string | 回显 `session.start.metadata.channel`(如提供) |
|
||
| `config.output` | object | 输出配置 |
|
||
| `config.output.mode` | string | 输出模式:`"audio"` / `"text"` |
|
||
| `config.tools.enabled` | boolean | 是否启用工具能力 |
|
||
| `config.tools.count` | number | 可用工具数量(不暴露工具清单) |
|
||
| `config.tracks` | object | 可用轨道列表 |
|
||
|
||
**不会返回以下内部字段**:
|
||
- `assistantId` / `appId` / `configVersionId`
|
||
- `services`(provider/model/baseUrl 等)
|
||
- 系统提示词原文及其它内部编排细节
|
||
|
||
---
|
||
|
||
#### `heartbeat`
|
||
|
||
保活心跳事件,默认每 50 秒发送一次。
|
||
|
||
```json
|
||
{
|
||
"type": "heartbeat",
|
||
"timestamp": 1730000050000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 10
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `timestamp` | number | 心跳时间戳 |
|
||
|
||
---
|
||
|
||
#### `session.stopped`
|
||
|
||
会话结束确认。
|
||
|
||
```json
|
||
{
|
||
"type": "session.stopped",
|
||
"timestamp": 1730000100000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 50,
|
||
"reason": "client_requested"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `reason` | string | 结束原因:`"client_requested"` / `"timeout"` / `"error"` |
|
||
|
||
---
|
||
|
||
### ASR 识别事件
|
||
|
||
#### `input.speech_started`
|
||
|
||
检测到语音开始(VAD)。
|
||
|
||
```json
|
||
{
|
||
"type": "input.speech_started",
|
||
"timestamp": 1730000010000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 5,
|
||
"source": "asr",
|
||
"trackId": "audio_in",
|
||
"probability": 0.95
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_in"` |
|
||
| `probability` | number | 语音检测置信度(0-1) |
|
||
|
||
---
|
||
|
||
#### `input.speech_stopped`
|
||
|
||
检测到语音结束(VAD)。
|
||
|
||
```json
|
||
{
|
||
"type": "input.speech_stopped",
|
||
"timestamp": 1730000012000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 8,
|
||
"source": "asr",
|
||
"trackId": "audio_in",
|
||
"probability": 0.92
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_in"` |
|
||
| `probability` | number | 静音检测置信度(0-1) |
|
||
|
||
---
|
||
|
||
#### `transcript.delta`
|
||
|
||
ASR 增量识别文本(实时转写)。
|
||
|
||
```json
|
||
{
|
||
"type": "transcript.delta",
|
||
"timestamp": 1730000011000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 6,
|
||
"source": "asr",
|
||
"trackId": "audio_in",
|
||
"text": "你好",
|
||
"data": {
|
||
"text": "你好",
|
||
"turn_id": "turn_001",
|
||
"utterance_id": "utt_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_in"` |
|
||
| `text` | string | 增量识别文本 |
|
||
| `data.text` | string | 增量识别文本(同 `text`) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
| `data.utterance_id` | string | 当前语句 ID |
|
||
|
||
**节流说明**:服务端默认每 300ms 合并一次 delta 事件。
|
||
|
||
---
|
||
|
||
#### `transcript.final`
|
||
|
||
ASR 最终识别文本(语句结束)。
|
||
|
||
```json
|
||
{
|
||
"type": "transcript.final",
|
||
"timestamp": 1730000012500,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 9,
|
||
"source": "asr",
|
||
"trackId": "audio_in",
|
||
"text": "你好,请问今天天气怎么样",
|
||
"data": {
|
||
"text": "你好,请问今天天气怎么样",
|
||
"turn_id": "turn_001",
|
||
"utterance_id": "utt_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_in"` |
|
||
| `text` | string | 最终识别文本 |
|
||
| `data.text` | string | 最终识别文本(同 `text`) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
| `data.utterance_id` | string | 当前语句 ID |
|
||
|
||
---
|
||
|
||
### LLM/TTS 输出事件
|
||
|
||
#### `assistant.response.delta`
|
||
|
||
助手增量文本输出(流式生成)。
|
||
|
||
```json
|
||
{
|
||
"type": "assistant.response.delta",
|
||
"timestamp": 1730000013000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 12,
|
||
"source": "llm",
|
||
"trackId": "audio_out",
|
||
"text": "今天天气",
|
||
"data": {
|
||
"text": "今天天气",
|
||
"turn_id": "turn_001",
|
||
"response_id": "resp_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 固定为 `"llm"` |
|
||
| `text` | string | 增量文本内容 |
|
||
| `data.text` | string | 增量文本内容(同 `text`) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
| `data.response_id` | string | 当前回复 ID |
|
||
|
||
**节流说明**:服务端默认每 80ms 合并一次 delta 事件。
|
||
|
||
---
|
||
|
||
#### `assistant.response.final`
|
||
|
||
助手完整文本输出(回复结束)。
|
||
|
||
```json
|
||
{
|
||
"type": "assistant.response.final",
|
||
"timestamp": 1730000015000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 18,
|
||
"source": "llm",
|
||
"trackId": "audio_out",
|
||
"text": "今天天气晴朗,气温25度,适合外出。",
|
||
"data": {
|
||
"text": "今天天气晴朗,气温25度,适合外出。",
|
||
"turn_id": "turn_001",
|
||
"response_id": "resp_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 固定为 `"llm"` |
|
||
| `text` | string | 完整回复文本 |
|
||
| `data.text` | string | 完整回复文本(同 `text`) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
| `data.response_id` | string | 当前回复 ID |
|
||
|
||
---
|
||
|
||
#### `assistant.tool_call`
|
||
|
||
工具调用通知,通知客户端 LLM 请求调用工具。
|
||
|
||
```json
|
||
{
|
||
"type": "assistant.tool_call",
|
||
"timestamp": 1730000014000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 14,
|
||
"source": "llm",
|
||
"trackId": "audio_out",
|
||
"tool_call_id": "call_abc123",
|
||
"tool_name": "weather",
|
||
"arguments": {
|
||
"city": "北京"
|
||
},
|
||
"executor": "server",
|
||
"timeout_ms": 30000,
|
||
"data": {
|
||
"tool_call": {
|
||
"id": "call_abc123",
|
||
"name": "weather",
|
||
"arguments": "{\"city\":\"北京\"}"
|
||
},
|
||
"turn_id": "turn_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 固定为 `"llm"` |
|
||
| `tool_call_id` | string | 工具调用唯一 ID |
|
||
| `tool_name` | string | 工具名称 |
|
||
| `arguments` | object | 工具参数(已解析的 JSON) |
|
||
| `executor` | string | 执行方:`"server"` 服务端执行 / `"client"` 客户端执行 |
|
||
| `timeout_ms` | number | 超时时间(毫秒) |
|
||
| `data.tool_call` | object | 原始工具调用信息 |
|
||
| `data.tool_call.id` | string | 工具调用 ID |
|
||
| `data.tool_call.name` | string | 工具名称 |
|
||
| `data.tool_call.arguments` | string | 工具参数(JSON 字符串) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
|
||
**注意**:当 `executor = "client"` 时,客户端需要执行工具并返回 `tool_call.results`。
|
||
|
||
---
|
||
|
||
#### `assistant.tool_result`
|
||
|
||
工具执行结果通知。
|
||
|
||
```json
|
||
{
|
||
"type": "assistant.tool_result",
|
||
"timestamp": 1730000014500,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 15,
|
||
"source": "server",
|
||
"trackId": "audio_out",
|
||
"tool_call_id": "call_abc123",
|
||
"tool_name": "weather",
|
||
"tool_display_name": "天气查询",
|
||
"ok": true,
|
||
"error": null,
|
||
"result": {
|
||
"tool_call_id": "call_abc123",
|
||
"name": "weather",
|
||
"output": {
|
||
"temperature": 25,
|
||
"condition": "晴",
|
||
"humidity": 40
|
||
},
|
||
"status": {
|
||
"code": 200,
|
||
"message": "ok"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 执行方:`"server"` / `"client"` |
|
||
| `tool_call_id` | string | 工具调用 ID |
|
||
| `tool_name` | string | 工具名称 |
|
||
| `tool_display_name` | string | 工具显示名称 |
|
||
| `ok` | boolean | 执行是否成功(状态码 200-299 为 true) |
|
||
| `error` | object \| null | 错误信息(`ok=false` 时存在) |
|
||
| `error.code` | number | 错误状态码 |
|
||
| `error.message` | string | 错误描述 |
|
||
| `error.retryable` | boolean | 是否可重试 |
|
||
| `result` | object | 原始执行结果 |
|
||
| `result.output` | any | 工具返回数据 |
|
||
| `result.status` | object | 执行状态 |
|
||
| `result.status.code` | number | HTTP 状态码 |
|
||
| `result.status.message` | string | 状态描述 |
|
||
|
||
---
|
||
|
||
#### `output.audio.start`
|
||
|
||
TTS 音频播放开始标记。
|
||
|
||
```json
|
||
{
|
||
"type": "output.audio.start",
|
||
"timestamp": 1730000015500,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 19,
|
||
"source": "tts",
|
||
"trackId": "audio_out",
|
||
"data": {
|
||
"tts_id": "tts_001",
|
||
"turn_id": "turn_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 固定为 `"tts"` |
|
||
| `data.tts_id` | string | TTS 播放段 ID |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
|
||
**说明**:此事件后服务端将发送二进制 PCM 音频帧。
|
||
|
||
---
|
||
|
||
#### `output.audio.end`
|
||
|
||
TTS 音频播放结束标记。
|
||
|
||
```json
|
||
{
|
||
"type": "output.audio.end",
|
||
"timestamp": 1730000018000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 25,
|
||
"source": "tts",
|
||
"trackId": "audio_out",
|
||
"data": {
|
||
"tts_id": "tts_001",
|
||
"turn_id": "turn_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `source` | string | 固定为 `"tts"` |
|
||
| `data.tts_id` | string | TTS 播放段 ID |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
|
||
**说明**:`output.audio.end` 表示服务端已发送完成,不代表客户端扬声器已播完。若需要“真实播完”信号,客户端应发送 `output.audio.played`。
|
||
|
||
---
|
||
|
||
#### `response.interrupted`
|
||
|
||
回答被打断(用户插话)。
|
||
|
||
```json
|
||
{
|
||
"type": "response.interrupted",
|
||
"timestamp": 1730000016000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 20,
|
||
"source": "system",
|
||
"trackId": "audio_out",
|
||
"data": {
|
||
"turn_id": "turn_001",
|
||
"response_id": "resp_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `data.turn_id` | string | 被打断的对话轮次 ID |
|
||
| `data.response_id` | string | 被打断的回复 ID |
|
||
|
||
---
|
||
|
||
#### `metrics.ttfb`
|
||
|
||
首包音频时延指标(Time To First Byte)。
|
||
|
||
```json
|
||
{
|
||
"type": "metrics.ttfb",
|
||
"timestamp": 1730000015600,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 21,
|
||
"source": "system",
|
||
"trackId": "audio_out",
|
||
"latencyMs": 1520,
|
||
"data": {
|
||
"latencyMs": 1520,
|
||
"turn_id": "turn_001"
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `trackId` | string | 固定为 `"audio_out"` |
|
||
| `latencyMs` | number | 首包音频时延(毫秒) |
|
||
| `data.latencyMs` | number | 首包音频时延(同 `latencyMs`) |
|
||
| `data.turn_id` | string | 当前对话轮次 ID |
|
||
|
||
**说明**:从用户输入结束到第一个音频包发送的时间。
|
||
|
||
---
|
||
|
||
### 错误事件
|
||
|
||
#### `error`
|
||
|
||
统一错误事件。
|
||
|
||
```json
|
||
{
|
||
"type": "error",
|
||
"timestamp": 1730000020000,
|
||
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
|
||
"seq": 30,
|
||
"sender": "server",
|
||
"code": "llm.timeout",
|
||
"message": "LLM request timeout",
|
||
"stage": "llm",
|
||
"retryable": true,
|
||
"trackId": "audio_out",
|
||
"data": {
|
||
"error": {
|
||
"stage": "llm",
|
||
"code": "llm.timeout",
|
||
"message": "LLM request timeout",
|
||
"retryable": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `sender` | string | 错误来源:`"server"` / `"client"` |
|
||
| `code` | string | 错误码 |
|
||
| `message` | string | 错误描述 |
|
||
| `stage` | string | 错误阶段:`"protocol"` / `"asr"` / `"llm"` / `"tts"` / `"tool"` / `"audio"` |
|
||
| `retryable` | boolean | 是否可重试 |
|
||
| `trackId` | string | 错误关联的轨道 |
|
||
| `data.error` | object | 结构化错误信息 |
|
||
| `data.error.stage` | string | 错误阶段 |
|
||
| `data.error.code` | string | 错误码 |
|
||
| `data.error.message` | string | 错误描述 |
|
||
| `data.error.retryable` | boolean | 是否可重试 |
|
||
|
||
**trackId 约定**:
|
||
- `audio_in`:ASR/音频输入相关错误
|
||
- `audio_out`:LLM/TTS/工具相关错误
|
||
- `control`:协议/会话控制相关错误
|
||
|
||
---
|
||
|
||
## 关联 ID 说明
|
||
|
||
事件中的关联 ID 用于追踪对话流程:
|
||
|
||
| ID 类型 | 说明 | 生命周期 |
|
||
|---------|------|---------|
|
||
| `turn_id` | 对话轮次 ID | 一次用户-助手交互 |
|
||
| `utterance_id` | 语句 ID | 一次 ASR 最终识别结果 |
|
||
| `response_id` | 回复 ID | 一次助手回复生成 |
|
||
| `tool_call_id` | 工具调用 ID | 一次工具调用 |
|
||
| `tts_id` | TTS 播放段 ID | 一段语音合成播放 |
|
||
|
||
---
|
||
|
||
## 心跳与超时
|
||
|
||
- **心跳间隔**:默认 50 秒(`heartbeat_interval_sec`)
|
||
- **空闲超时**:默认 60 秒(`inactivity_timeout_sec`)
|
||
- 客户端应持续发送音频或轻量消息避免被判定闲置
|
||
|
||
## 事件节流
|
||
|
||
为保持客户端渲染和服务端负载稳定,v1 协议对部分事件进行节流:
|
||
|
||
| 事件 | 默认节流间隔 | 说明 |
|
||
|------|-------------|------|
|
||
| `transcript.delta` | 300ms | ASR 增量文本 |
|
||
| `assistant.response.delta` | 80ms | LLM 增量文本 |
|
||
|
||
## 错误处理
|
||
|
||
详细错误码请参考 [错误码](errors.md)。
|