WebSocket 通信协议

本协议基于 xiaozhi-esp32 与 vanling 服务端的实际实现整理。


1. Protocol Overview

connect
  request
    hello
  response
    hello

listen
  Auto / Realtime
    request
      [audio]                                // 唤醒词音频
      detect-text                            // {"text":"Hi XiaoZhi"}
      <vad>
        //asr
        //llm
          <mcp>
    response
      <tts>                                  // 问候

    request
      listen_start                           // {"mode":"auto"}
      [audio]                                // 对话音频
        <vad>
        //asr
        //llm
          <mcp>
    response
      <tts>                                  // auto: 播放后回到 idle
                                             // realtime: 播放后继续监听

  Manual
    request
      listen_start                           // {"mode":"manual"}
      [audio]                                // 录音
        //asr
        //llm
          <mcp>
      listen_stop
    response
      <tts>

  Text
    request
      text                                   // {"text":"你好"}
        //llm
          <mcp>
    response
      <tts>

abort                                        // 打断 TTS
  request
    abort

---

vad                                          // 服务端内部
  vad pass
    //silent checking stop
    //to asr
  vad ignore
    abort

tts                                          // 服务端 → 客户端
  response
    start
      llm                                    // {"emotion":"happy"}
      sentence_start
      [audio]
      sentence_end
    stop

mcp                                          // tools/call
  request
    tool_call
  response
    tool_result

2. 连接建立

2.1 WebSocket Headers

Header必选说明
Authorization否Bearer <token>
Protocol-Version是二进制协议版本,目前为 1
Device-Id是MAC 地址
Client-Id是UUID v4

2.2 Hello

客户端 → 服务端:

{
  "type": "hello",
  "version": 1,
  "transport": "websocket",
  "audio_params": {
    "format": "opus",
    "sample_rate": 16000,
    "channels": 1,
    "frame_duration": 60
  },
  "features": {
    "mcp": true,
    "aec": true
  }
}

服务端 → 客户端:

{
  "type": "hello",
  "transport": "websocket",
  "session_id": "xxx",
  "audio_params": {
    "sample_rate": 24000,
    "frame_duration": 60
  }
}

transport 为必选,缺失或不匹配 "websocket" 时客户端会断开连接。session_id 为会话标识,后续所有消息中携带。audio_params 方向独立:客户端 Hello 表示上行参数(设备→服务端),服务端 Hello 表示下行参数(服务端→设备)。


3. 会话时序

Auto 模式

sequenceDiagram
  participant C as Client
  participant S as Server

  Note over C,S: Phase 1: 唤醒 → 问候
  C->>S: [Opus 音频] (唤醒词)
  C->>S: listen_detect
  S->>S: VAD → ASR → LLL → MCP(可选)
  S->>C: tts start
  S->>C: llm
  S->>C: tts sentence_start
  S->>C: [Opus 音频] (TTS)
  S->>C: tts stop

  Note over C,S: Phase 2: 对话
  C->>S: listen_start (mode=auto)
  C->>S: [Opus 音频] (对话)
  S->>S: VAD → 静音超时 → ASR → LLM → MCP(可选)
  S->>C: tts start
  S->>C: llm
  S->>C: tts sentence_start
  S->>C: [Opus 音频]
  S->>C: tts stop
  Note over C: 回到 idle (auto)<br/>继续监听 (realtime)

Manual 模式

sequenceDiagram
  participant C as Client
  participant S as Server

  C->>S: listen_start (mode=manual)
  C->>S: [Opus 音频] (录音)
  C->>S: listen_stop
  S->>S: ASR → LLM → MCP(可选)
  S->>C: tts start
  S->>C: llm
  S->>C: tts sentence_start
  S->>C: [Opus 音频]
  S->>C: tts stop

文本模式

sequenceDiagram
  participant C as Client
  participant S as Server

  C->>S: text ({"text":"你好"})
  S->>S: LLM → MCP(可选)
  S->>C: tts start
  S->>C: llm
  S->>C: tts sentence_start
  S->>C: [Opus 音频]
  S->>C: tts stop

状态机

stateDiagram
  Idle --> Connecting: 唤醒/按键
  Connecting --> ListenDetect: hello 完成
  ListenDetect --> Listening: listen_start
  ListenDetect --> Listening: detect (唤醒)
  Listening --> Speaking: tts start
  Speaking --> Listening: tts stop (realtime)
  Speaking --> Idle: tts stop (auto/manual)
  Listening --> Idle: 超时/断开

空输入(各模式)

空输入(无有效语音)由中枢(Session)辨别类型并分级提示(Wake/AutoSpoke/Silence 遵循 Rule of three 最多 3 次;Manual 事件驱动每次按键都提示)。判别依据:模式 + 前一 turn。

  • manual 无人声(!is_voice_break_detect 且未检出语音)→ Manual:每次按键播 1 次引导提示语,之后回到监听等待
  • 唤醒词后未说话 → Wake:引导式提示("想让我帮你做什么?"),逐次升级
  • auto 说了但 ASR 空(VAD 触发但文本空)→ AutoSpoke:"没听清,请重说" → 给示例 → 优雅收尾
  • auto/realtime 完全静默(VAD 未触发,ASR 无流)→ Silence:温柔引导不指责,逐次升级
  • realtime 回复后连续监听 → Continuing:静默等待,不反复提示

中枢观察空输入后重新注入 Prompt{kind, count} 对话 Act,由 LLM/Echo(NLG)渲染提示语;Continuing 走静默、GiveUp 回 idle。


附录 A:消息字段参考

A.1 客户端 → 服务端

type:hello
  version: int              必选
  transport: "websocket"    必选
  audio_params: {}
    format: "opus"
    sample_rate: 16000
    channels: 1
    frame_duration: 60      单位 ms
  features: {}
    mcp: bool
    aec: bool

type:listen, state:detect
  session_id: string        必选
  text: string              唤醒词原文,如 "Hi XiaoZhi"

type:listen, state:start
  session_id: string        必选
  mode: "auto"|"manual"     必选

type:listen, state:stop
  session_id: string        必选

type:text
  session_id: string        必选
  text: string              必选,用户输入文本

type:abort
  session_id: string        必选
  reason: string            可选,"wake_word_detected"

type:mcp, payload.method:"initialize"
  payload: {}
    jsonrpc: "2.0"          必选
    method: "initialize"    必选
    params: {}
      capabilities: {}
    id: int                 必选,请求标识

type:mcp, payload.method:"tools/list"
  payload: {}
    jsonrpc: "2.0"          必选
    method: "tools/list"    必选
    params: {}
      cursor: string        可选,分页游标
    id: int                 必选

type:mcp, payload.method:"tools/call"
  payload: {}
    jsonrpc: "2.0"          必选
    method: "tools/call"    必选
    params: {}
      name: string          必选,工具名
      arguments: {}         可选,工具参数
    id: int                 必选

A.2 服务端 → 客户端

type:hello
  transport: "websocket"    必选
  session_id: string        必选
  audio_params: {}
    sample_rate: int        可选,默认 24000
    frame_duration: int     可选,默认 60

type:stt
  session_id: string
  text: string              ASR 识别结果

type:llm
  session_id: string
  emotion: string           "happy"|"sad"|"neutral"|...
  text: string              LLM 输出文本片段(流式)

type:tts, state:start
  session_id: string

type:tts, state:sentence_start
  session_id: string
  text: string              当前句子文本

type:tts, state:sentence_end
  session_id: string

type:tts, state:stop
  session_id: string

type:system
  command: "reboot"

type:mcp
  payload: {}
    jsonrpc: "2.0"
    id: int                 对应请求 id
    result: {}              成功时
    error: {}               失败时
      code: int
      message: string

type:mcp, response to "initialize"
  payload.result: {}
    protocolVersion: string
    capabilities: {}
    serverInfo: {}
      name: string
      version: string

type:mcp, response to "tools/list"
  payload.result: {}
    tools: []
      name: string
      description: string
      inputSchema: {}
    nextCursor: string      可选

type:mcp, response to "tools/call"
  payload.result: {}        成功时
    content: []
      type: "text"
      text: string
    isError: false
  payload.error: {}         失败时
    code: int
    message: string

附录 B:二进制协议

Opus 编码音频通过 WebSocket 二进制帧传输。版本号由 Protocol-Version Header 声明。

版本 1(默认)

裸 Opus 数据,无头部。

[Opus 数据]

版本 2

16 字节头部 + Opus 数据(所有多字节字段使用网络字节序)。

struct BinaryProtocol2 {
    uint16_t version;        // 协议版本
    uint16_t type;           // 0=OPUS, 1=JSON
    uint32_t reserved;
    uint32_t timestamp;      // 时间戳(ms),用于服务端 AEC
    uint32_t payload_size;
    uint8_t  payload[];
};

版本 3

4 字节头部 + Opus 数据。

struct BinaryProtocol3 {
    uint8_t  type;           // 0=OPUS
    uint8_t  reserved;
    uint16_t payload_size;
    uint8_t  payload[];
};

附录 C:变更

日期说明
2026-06重新整理。概览/时序/字段参考分层。新增 type:text,去掉 realtime 独立模式,detect 仅用于唤醒