Unify db api
This commit is contained in:
520
engine/docs/ws_v1_schema_zh.md
Normal file
520
engine/docs/ws_v1_schema_zh.md
Normal file
@@ -0,0 +1,520 @@
|
||||
# WS v1 协议完整说明(中文)
|
||||
|
||||
本文档描述 `/ws` 端点的 WebSocket v1 协议,覆盖:
|
||||
- 客户端输入(JSON 文本消息 + 二进制音频);
|
||||
- 服务端输出(JSON 事件 + 二进制音频);
|
||||
- 每个参数的类型、约束、含义与使用方式;
|
||||
- 握手顺序、状态机、错误语义与实现细节。
|
||||
|
||||
实现对照来源:
|
||||
- `models/ws_v1.py`
|
||||
- `core/session.py`
|
||||
- `core/duplex_pipeline.py`
|
||||
- `app/main.py`
|
||||
|
||||
---
|
||||
|
||||
## 1. 传输与基础规则
|
||||
|
||||
- 连接地址:`ws://<host>/ws`
|
||||
- 单连接双通道承载:
|
||||
- 文本帧:JSON 控制消息(严格校验 schema)
|
||||
- 二进制帧:原始 PCM 音频
|
||||
- JSON 校验策略:
|
||||
- 所有已定义客户端消息都 `extra="forbid"`,即不允许未声明字段;
|
||||
- `hello.version` 固定必须是 `"v1"`;
|
||||
- 缺失 `type` 或未知 `type` 会返回协议错误。
|
||||
|
||||
---
|
||||
|
||||
## 2. 状态机与消息顺序
|
||||
|
||||
### 2.1 服务端状态
|
||||
|
||||
- `WAIT_HELLO`:等待 `hello`
|
||||
- `WAIT_START`:已通过握手,等待 `session.start`
|
||||
- `ACTIVE`:会话运行中,可收发文本/音频
|
||||
- `STOPPED`:会话结束
|
||||
|
||||
### 2.2 正确顺序
|
||||
|
||||
1. 客户端发送 `hello`
|
||||
2. 服务端返回 `hello.ack`
|
||||
3. 客户端发送 `session.start`
|
||||
4. 服务端返回 `session.started`
|
||||
5. 客户端可持续发送:
|
||||
- 二进制音频
|
||||
- `input.text`(可选)
|
||||
- `response.cancel`(可选)
|
||||
- `tool_call.results`(可选)
|
||||
6. 客户端发送 `session.stop` 或直接断开连接
|
||||
|
||||
顺序错误会返回 `error`,`code = "protocol.order"`。
|
||||
|
||||
---
|
||||
|
||||
## 3. 客户端 -> 服务端消息(输入)
|
||||
|
||||
## 3.1 `hello`
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "hello",
|
||||
"version": "v1",
|
||||
"auth": {
|
||||
"apiKey": "optional-api-key",
|
||||
"jwt": "optional-jwt"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 含义 | 使用说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `type` | string | 是 | 固定 `"hello"` | 消息类型 | 握手第一条消息 |
|
||||
| `version` | string | 是 | 固定 `"v1"` | 协议版本 | 版本不匹配会 `protocol.version_unsupported` 并断开 |
|
||||
| `auth` | object \| null | 否 | 仅允许 `apiKey`、`jwt` | 认证载荷 | 认证策略由服务端配置决定 |
|
||||
| `auth.apiKey` | string \| null | 否 | 任意字符串 | API Key | 若服务端配置 `WS_API_KEY`,必须精确匹配 |
|
||||
| `auth.jwt` | string \| null | 否 | 任意字符串 | JWT 字符串 | 当 `WS_REQUIRE_AUTH=true` 时可用于满足“有认证信息”条件 |
|
||||
|
||||
认证行为:
|
||||
- 若设置了 `WS_API_KEY`:必须提供且匹配 `auth.apiKey`,否则 `auth.invalid_api_key` 并关闭连接。
|
||||
- 若 `WS_REQUIRE_AUTH=true` 且未设置 `WS_API_KEY`:`auth.apiKey` 或 `auth.jwt` 至少一个非空,否则 `auth.required` 并关闭连接。
|
||||
|
||||
## 3.2 `session.start`
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.start",
|
||||
"audio": {
|
||||
"encoding": "pcm_s16le",
|
||||
"sample_rate_hz": 16000,
|
||||
"channels": 1
|
||||
},
|
||||
"metadata": {
|
||||
"appId": "assistant_123",
|
||||
"channel": "web",
|
||||
"configVersionId": "cfg_20260217_01",
|
||||
"client": "web-debug",
|
||||
"output": {
|
||||
"mode": "audio"
|
||||
},
|
||||
"systemPrompt": "你是简洁助手",
|
||||
"greeting": "你好,我能帮你什么?"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 含义 | 使用说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `type` | string | 是 | 固定 `"session.start"` | 启动会话 | 握手后第二阶段消息 |
|
||||
| `audio` | object \| null | 否 | 仅支持固定值 | 音频格式描述 | 仅用于声明;MVP 实际只接受固定 PCM |
|
||||
| `audio.encoding` | string | 否 | 固定 `"pcm_s16le"` | 编码格式 | 非该值会在模型校验层报错 |
|
||||
| `audio.sample_rate_hz` | number | 否 | 固定 `16000` | 采样率 | 16kHz |
|
||||
| `audio.channels` | number | 否 | 固定 `1` | 声道数 | 单声道 |
|
||||
| `metadata` | object \| null | 否 | 任意对象(会被白名单过滤) | 运行时配置 | 用于 app/channel/提示词/输出模式等覆盖 |
|
||||
|
||||
`metadata` 白名单策略(关键):
|
||||
- 允许透传的标识字段(ID 类):
|
||||
- `appId` / `app_id`
|
||||
- `channel`
|
||||
- `configVersionId` / `config_version_id`
|
||||
- 允许透传的覆盖字段:
|
||||
- `firstTurnMode`
|
||||
- `greeting`
|
||||
- `generatedOpenerEnabled`
|
||||
- `systemPrompt`
|
||||
- `output`
|
||||
- `bargeIn`
|
||||
- `knowledge`
|
||||
- `knowledgeBaseId`
|
||||
- `history`
|
||||
- `userId`
|
||||
- `assistantId`
|
||||
- `source`
|
||||
- 客户端传入 `metadata.services` 会被忽略(服务端会记录 warning),服务配置由后端/环境变量决定。
|
||||
|
||||
`output.mode` 用法:
|
||||
- `"audio"`(默认语音输出)
|
||||
- `"text"`(纯文本输出)
|
||||
- 纯文本模式下仍会收到 `assistant.response.delta/final`;
|
||||
- 不会收到 TTS 音频帧与 `output.audio.start/end`。
|
||||
|
||||
## 3.3 `input.text`
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "input.text",
|
||||
"text": "你能做什么?"
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 含义 | 使用说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `type` | string | 是 | 固定 `"input.text"` | 文本输入 | 跳过 ASR,直接触发 LLM 回答 |
|
||||
| `text` | string | 是 | 非空字符串为佳 | 用户文本 | 用于文本聊天或调试 |
|
||||
|
||||
## 3.4 `response.cancel`
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "response.cancel",
|
||||
"graceful": false
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认值 | 含义 | 使用说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `type` | string | 是 | - | 固定 `"response.cancel"` | 请求中断当前回答 |
|
||||
| `graceful` | boolean | 否 | `false` | 取消方式 | `false` 立即打断;`true` 当前实现主要用于记录日志,不强制中断 |
|
||||
|
||||
## 3.5 `tool_call.results`
|
||||
|
||||
仅在工具执行端为客户端时使用(`assistant.tool_call.executor == "client"`)。
|
||||
|
||||
示例:
|
||||
|
||||
```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 | 必须与 `assistant.tool_call.tool_call_id` 对应 |
|
||||
| `results[].name` | string | 是 | 任意字符串 | 工具名 | 建议与请求一致 |
|
||||
| `results[].output` | any | 否 | 任意 JSON | 工具输出 | 供模型后续组织回答 |
|
||||
| `results[].status` | object | 是 | 包含 `code`、`message` | 执行状态 | 用于判定成功/失败 |
|
||||
| `results[].status.code` | number | 是 | HTTP 风格状态码 | 状态码 | `200-299` 判定成功 |
|
||||
| `results[].status.message` | string | 是 | 任意字符串 | 状态描述 | 例如 `"ok"` / `"timeout"` |
|
||||
|
||||
处理规则:
|
||||
- 未请求过的 `tool_call_id` 会被忽略(防止伪造/串话);
|
||||
- 重复回传会被忽略;
|
||||
- 超时未回传会由服务端合成超时结果(`504`)。
|
||||
|
||||
## 3.6 `session.stop`
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "session.stop",
|
||||
"reason": "client_disconnect"
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 含义 | 使用说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| `type` | string | 是 | 固定 `"session.stop"` | 结束会话 | 正常结束推荐发送 |
|
||||
| `reason` | string \| null | 否 | 任意字符串 | 结束原因 | 服务端会回传到 `session.stopped.reason` |
|
||||
|
||||
---
|
||||
|
||||
## 4. 二进制音频输入(客户端 -> 服务端)
|
||||
|
||||
在 `session.started` 之后可持续发送二进制音频。
|
||||
|
||||
固定格式(MVP):
|
||||
- 编码:`pcm_s16le`
|
||||
- 采样率:`16000`
|
||||
- 声道:`1`
|
||||
- 帧长:20ms = `640 bytes`
|
||||
|
||||
分包规则:
|
||||
- 单个 WebSocket 二进制消息可包含 1 帧或多帧;
|
||||
- 长度必须是 `640` 的整数倍;
|
||||
- 不是 `640` 倍数会触发 `audio.frame_size_mismatch`,该消息整包丢弃;
|
||||
- 奇数字节长度会触发 `audio.invalid_pcm`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 服务端 -> 客户端事件(输出)
|
||||
|
||||
所有 JSON 事件都包含统一包络字段。
|
||||
|
||||
## 5.1 统一包络(Envelope)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "event.name",
|
||||
"timestamp": 1730000000000,
|
||||
"sessionId": "sess_xxx",
|
||||
"seq": 42,
|
||||
"source": "asr",
|
||||
"trackId": "audio_in",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
| 字段 | 类型 | 含义 | 使用说明 |
|
||||
|---|---|---|---|
|
||||
| `type` | string | 事件类型 | 见下方事件清单 |
|
||||
| `timestamp` | number | 事件时间戳(毫秒) | 由 `ev()` 生成 |
|
||||
| `sessionId` | string | 会话ID | 同一连接固定 |
|
||||
| `seq` | number | 单会话递增序号 | 可用于重放、去重、排序 |
|
||||
| `source` | string | 事件来源 | 常见:`asr`/`llm`/`tts`/`tool`/`system`/`client`/`server` |
|
||||
| `trackId` | string | 事件轨道 | 常用:`audio_in`/`audio_out`/`control` |
|
||||
| `data` | object | 结构化数据 | 顶层业务字段会镜像进 `data` 以兼容旧客户端 |
|
||||
|
||||
关联ID(在 `data` 内自动注入,存在时):
|
||||
- `turn_id`:一次用户-助手对话轮次
|
||||
- `utterance_id`:一次用户语音话语
|
||||
- `response_id`:一次助手生成响应
|
||||
- `tool_call_id`:一次工具调用
|
||||
- `tts_id`:一次 TTS 播放段
|
||||
|
||||
## 5.2 事件类型与参数
|
||||
|
||||
### 5.2.1 会话与控制类
|
||||
|
||||
1. `hello.ack`
|
||||
- 关键字段:`version`
|
||||
- 含义:握手成功,应紧接着发送 `session.start`
|
||||
|
||||
2. `session.started`
|
||||
- 关键字段:
|
||||
- `trackId`
|
||||
- `tracks.audio_in`
|
||||
- `tracks.audio_out`
|
||||
- `tracks.control`
|
||||
- `audio`(回显客户端声明的音频元信息)
|
||||
- 含义:会话进入 ACTIVE,可发音频/文本
|
||||
|
||||
3. `config.resolved`
|
||||
- 关键字段:
|
||||
- `config.appId`
|
||||
- `config.channel`
|
||||
- `config.configVersionId`
|
||||
- `config.prompt.sha256`
|
||||
- `config.output`
|
||||
- `config.services`(去密钥后的有效服务配置)
|
||||
- `config.tools.allowlist`
|
||||
- `config.tracks`
|
||||
- 含义:服务端最终生效配置快照,便于前端展示与排错
|
||||
|
||||
4. `heartbeat`
|
||||
- 关键字段:无业务字段(仅 envelope)
|
||||
- 含义:保活心跳
|
||||
- 默认间隔:`heartbeat_interval_sec`(默认 50s)
|
||||
|
||||
5. `session.stopped`
|
||||
- 关键字段:`reason`
|
||||
- 含义:会话结束确认
|
||||
|
||||
6. `error`
|
||||
- 关键字段:
|
||||
- `sender`
|
||||
- `code`
|
||||
- `message`
|
||||
- `stage`
|
||||
- `retryable`
|
||||
- `trackId`
|
||||
- `data.error`(结构化错误镜像)
|
||||
- 含义:统一错误事件
|
||||
|
||||
### 5.2.2 识别与输入侧(ASR/VAD)
|
||||
|
||||
1. `input.speech_started`
|
||||
- 字段:`probability`
|
||||
- 含义:检测到语音开始
|
||||
|
||||
2. `input.speech_stopped`
|
||||
- 字段:`probability`
|
||||
- 含义:检测到语音结束
|
||||
|
||||
3. `transcript.delta`
|
||||
- 字段:`text`
|
||||
- 含义:ASR 增量识别文本(节流发送)
|
||||
|
||||
4. `transcript.final`
|
||||
- 字段:`text`
|
||||
- 含义:ASR 最终识别文本
|
||||
|
||||
### 5.2.3 输出侧(LLM/TTS/Tool)
|
||||
|
||||
1. `assistant.response.delta`
|
||||
- 字段:`text`
|
||||
- 含义:助手增量文本输出(节流发送)
|
||||
|
||||
2. `assistant.response.final`
|
||||
- 字段:`text`
|
||||
- 含义:助手完整文本输出
|
||||
|
||||
3. `assistant.tool_call`
|
||||
- 字段:
|
||||
- `tool_call_id`
|
||||
- `tool_name`
|
||||
- `arguments`(对象)
|
||||
- `executor`(`client` 或 `server`)
|
||||
- `timeout_ms`
|
||||
- `tool_call`(完整工具调用对象)
|
||||
- 含义:通知客户端发生工具调用(用于可视化或客户端执行)
|
||||
|
||||
4. `assistant.tool_result`
|
||||
- 字段:
|
||||
- `source`(`client` 或 `server`)
|
||||
- `tool_call_id`
|
||||
- `tool_name`
|
||||
- `ok`(boolean)
|
||||
- `error`(失败时 `{code,message,retryable}`)
|
||||
- `result`(原始结果对象)
|
||||
- 含义:工具调用结果回执
|
||||
|
||||
5. `output.audio.start`
|
||||
- 含义:TTS 音频输出开始边界
|
||||
|
||||
6. `output.audio.end`
|
||||
- 含义:TTS 音频输出结束边界
|
||||
|
||||
7. `response.interrupted`
|
||||
- 含义:当前回答被打断(barge-in 或 cancel)
|
||||
|
||||
8. `metrics.ttfb`
|
||||
- 字段:`latencyMs`
|
||||
- 含义:首包音频时延(TTFB)
|
||||
|
||||
### 5.2.4 工作流扩展事件(可选)
|
||||
|
||||
若 `metadata.workflow` 生效,会额外出现:
|
||||
- `workflow.started`
|
||||
- `workflow.node.entered`
|
||||
- `workflow.edge.taken`
|
||||
- `workflow.tool.requested`
|
||||
- `workflow.human_transfer`
|
||||
- `workflow.ended`
|
||||
|
||||
这些事件用于外部可视化工作流状态,不影响基础语音会话协议。
|
||||
|
||||
---
|
||||
|
||||
## 6. 服务端二进制音频输出(服务端 -> 客户端)
|
||||
|
||||
- 音频为 PCM 二进制帧;
|
||||
- 发送单位对齐到 `640 bytes`(不足会补零后发送);
|
||||
- 前端通常结合 `output.audio.start/end` 做播放边界控制;
|
||||
- 收到 `response.interrupted` 后应丢弃队列中未播放完的旧音频。
|
||||
|
||||
---
|
||||
|
||||
## 7. 错误模型与常见错误码
|
||||
|
||||
统一结构(`error` 事件):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "error",
|
||||
"sender": "client",
|
||||
"code": "protocol.invalid_message",
|
||||
"message": "Invalid message: ...",
|
||||
"stage": "protocol",
|
||||
"retryable": false,
|
||||
"trackId": "control",
|
||||
"data": {
|
||||
"error": {
|
||||
"stage": "protocol",
|
||||
"code": "protocol.invalid_message",
|
||||
"message": "Invalid message: ...",
|
||||
"retryable": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
字段语义:
|
||||
- `sender`:错误来源角色(如 `client` / `server` / `auth`)
|
||||
- `code`:机器可读错误码
|
||||
- `message`:人类可读描述
|
||||
- `stage`:阶段(`protocol|audio|asr|llm|tts|tool`)
|
||||
- `retryable`:是否建议重试
|
||||
- `trackId`:错误归属轨道
|
||||
|
||||
常见错误码:
|
||||
- `protocol.invalid_json`
|
||||
- `protocol.invalid_message`
|
||||
- `protocol.order`
|
||||
- `protocol.version_unsupported`
|
||||
- `protocol.unsupported`
|
||||
- `auth.invalid_api_key`
|
||||
- `auth.required`
|
||||
- `audio.invalid_pcm`
|
||||
- `audio.frame_size_mismatch`
|
||||
- `audio.processing_failed`
|
||||
- `server.internal`
|
||||
|
||||
---
|
||||
|
||||
## 8. 心跳与超时
|
||||
|
||||
服务端后台任务逻辑:
|
||||
- 每隔约 5 秒检查一次连接;
|
||||
- 超过 `inactivity_timeout_sec`(默认 60 秒)未收到任何客户端消息则关闭会话;
|
||||
- 每隔 `heartbeat_interval_sec`(默认 50 秒)发送一次 `heartbeat`。
|
||||
|
||||
客户端建议:
|
||||
- 持续上行音频或定期发送轻量文本消息,避免被判定闲置;
|
||||
- 用 `heartbeat` + `seq` 检测连接活性和事件乱序。
|
||||
|
||||
---
|
||||
|
||||
## 9. 实战接入建议
|
||||
|
||||
1. 建连后立即发送 `hello`,收到 `hello.ack` 后再发 `session.start`。
|
||||
2. 语音输入严格按 16k/16bit/mono,并保证每个 WS 二进制消息长度是 `640*n`。
|
||||
3. UI 层把 `assistant.response.delta` 当作流式显示,把 `assistant.response.final` 当作收敛结果。
|
||||
4. 播放器用 `output.audio.start/end` 管理一轮播报生命周期。
|
||||
5. 工具调用场景下,若 `executor=client`,务必按 `tool_call_id` 回传 `tool_call.results`。
|
||||
6. 出现 `error` 时优先按 `code` 分流处理,而不是仅看 `message`。
|
||||
|
||||
---
|
||||
|
||||
## 10. 最小完整时序示例
|
||||
|
||||
```text
|
||||
Client -> hello
|
||||
Server <- hello.ack
|
||||
Client -> session.start
|
||||
Server <- session.started
|
||||
Server <- 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 -> session.stop
|
||||
Server <- session.stopped
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user