Files
AI-VideoAssistant/docs/content/api-reference/websocket.md
Xin Wang a8fa66e9cc Update documentation to reflect changes in WebSocket API message formatting and knowledge base
- 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.
2026-03-04 10:32:56 +08:00

881 lines
21 KiB
Markdown
Raw 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.
# 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)。