- Introduced `output.audio.played` message type for client acknowledgment of audio playback completion. - Updated `DuplexPipeline` to track client playback state and handle playback completion events. - Enhanced session handling to route `output.audio.played` messages to the pipeline. - Revised API documentation to include details about the new message type and its fields. - Updated schema documentation to reflect the addition of `output.audio.played` in the message flow.
21 KiB
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
客户端 -> 服务端消息
1. Session Start: session.start
客户端连接后发送的第一个消息,用于启动对话会话。
{
"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- 历史记录用户 IDoverrides- 可覆盖字段(仅限安全白名单)dynamicVariables- 动态变量(支持{{variable}}占位符)
metadata.overrides 白名单字段:
systemPromptgreetingfirstTurnModegeneratedOpenerEnabledoutputbargeInknowledgeBaseIdknowledgetoolsopenerAudio
限制:
metadata.workflow会被忽略(不触发 workflow 事件)- 禁止提交
metadata.services - 禁止提交
assistantId/appId/app_id/configVersionId/config_version_id - 禁止提交包含密钥语义的字段(如
apiKey/token/secret/password/authorization)
2. Text Input: input.text
发送文本输入,跳过 ASR 识别,直接触发 LLM 回复。
{
"type": "input.text",
"text": "你能做什么?"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
string | 是 | 固定为 "input.text" |
text |
string | 是 | 用户文本内容 |
3. Response Cancel: response.cancel
请求中断当前回答。
{
"type": "response.cancel",
"graceful": false
}
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
type |
string | 是 | - | 固定为 "response.cancel" |
graceful |
boolean | 否 | false |
false 立即打断 |
4. Output Audio Played: output.audio.played
客户端回执音频已在本地播放完成(含本地 jitter buffer / 播放队列)。
{
"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 | 否 | 本次播放耗时(毫秒) |
5. Tool Call Results: tool_call.results
回传客户端执行的工具结果。
{
"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 | 是 | 状态描述 |
6. Session Stop: session.stop
结束对话会话。
{
"type": "session.stop",
"reason": "client_disconnect"
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
type |
string | 是 | 固定为 "session.stop" |
reason |
string | 否 | 结束原因 |
7. Binary Audio
在 session.started 之后可持续发送二进制 PCM 音频。
- 格式:
pcm_s16le - 采样率:16000 Hz
- 声道:1(单声道)
- 帧长:20ms = 640 bytes
服务端 -> 客户端事件
事件包络
所有 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
会话启动成功,客户端收到此事件后可以开始发送音频。
{
"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 时发送。
{
"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/configVersionIdservices(provider/model/baseUrl 等)- 系统提示词原文及其它内部编排细节
heartbeat
保活心跳事件,默认每 50 秒发送一次。
{
"type": "heartbeat",
"timestamp": 1730000050000,
"sessionId": "ea34e1ca-b417-4a57-b03e-f752cb82e97d",
"seq": 10
}
| 字段 | 类型 | 说明 |
|---|---|---|
timestamp |
number | 心跳时间戳 |
session.stopped
会话结束确认。
{
"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)。
{
"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)。
{
"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 增量识别文本(实时转写)。
{
"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 最终识别文本(语句结束)。
{
"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
助手增量文本输出(流式生成)。
{
"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
助手完整文本输出(回复结束)。
{
"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 请求调用工具。
{
"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
工具执行结果通知。
{
"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 音频播放开始标记。
{
"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 音频播放结束标记。
{
"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
回答被打断(用户插话)。
{
"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)。
{
"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
统一错误事件。
{
"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 增量文本 |
错误处理
详细错误码请参考 错误码。