Skip to content

Voice Agent 前端接入接口文档 ​

本文档面向前端工程师,描述与后端实时语音代理的 WebSocket 协议、二进制音频格式、事件契约与接入示例。文档基于后端的桥接实现,目标是帮助前端同学快速对接并稳定播放 / 采集音频与显示转写与事件。

1 概要 ​

  • 连接方式:WebSocket(推荐使用 WSS)
  • 默认路径示例:wss://{host}/api/v1/voice/agent(服务器可按需要更改)
  • 消息格式:控制类消息使用 JSON 文本;音频数据使用裸二进制(Int16 LE)发送/接收
  • 单连接对应一条后台会话(bridge);前端负责音频采集、重采样、发送与播放

2 认证与连接(建议) ​

后端应要求鉴权。常见做法(任选其一并与后端约定):

  • 在连接 URL 上加入短期 token: wss://host/api/v1/voice/agent?token=xxxxx
  • 或使用 Sec-WebSocket-Protocol / 子协议传 token
  • 或先通过 HTTP 登录拿到 cookie / bearer token,再建立同源 WSS

前端示例(带 query token):

js
const url = `wss://api.example.com/api/v1/voice/agent?token=${encodeURIComponent(token)}`;
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";

3 消息类型总览 ​

请求(前端 → 后端)和响应(后端 → 前端)各自的常用 type 列表与含义如下。

3.1 前端 → 后端(请求) ​

  • setup:初始化会话(可带 system_prompt / user_prompt)

    json
    { "type": "setup", "system_prompt": "是个简短回答的助手", "user_prompt": "上下文..." }
  • text:发送文本(即时作为用户输入)

    json
    { "type": "text", "text": "今天天气怎样?" }
  • audio_end:告知服务端当前音频流已结束(可选,提示 turn 结尾)

    json
    { "type": "audio_end" }
  • close:客户端主动关闭会话(也可直接关闭 WebSocket)

    json
    { "type": "close" }
  • 二进制流(裸二进制 ArrayBuffer):16 kHz、16-bit PCM、小端序(Int16),每次发送一段音频数据包(不封 JSON)。

3.2 后端 → 前端(响应 / 事件) ​

  • setup_received / setup_sent_to_model / setup_complete:setup 流程相关
  • gemini_connected:后端与模型已连接,通常携带 sessionId
  • transcript_in:用户语音转写片段(text 字段)
  • transcript_out:模型输出转写片段(text 字段)
  • text:模型输出文本(text 字段),可多次增量发送
  • turn_complete:一次对话回合完成
  • go_away:服务端提示将断开(可能含 timeLeft)
  • usage:usage/token 统计(promptTokenCount / responseTokenCount / totalTokenCount)
  • inline_data:当某些 inline 数据不是标准 PCM 时(以 base64 文本)
  • function_call:模型发起函数/工具调用(name/参数)
  • error:错误消息(message / where)

后端也会直接发送二进制音频数据包(ArrayBuffer),表示模型合成的 PCM 音频流(通常 24 kHz、16-bit PCM)。

4 JSON 消息字段定义(Schema 说明) ​

通用响应体(简化):

json
{
  "type": "transcript_in",     // 事件类型
  "text": "部分文本",          // 事件文本(视类型而定)
  "sessionId": "xxx",          // 仅部分事件包含
  "timeLeft": "PT30S",         // go_away
  "promptTokenCount": 12,      // usage
  "responseTokenCount": 34,
  "totalTokenCount": 46,
  "message": "错误说明",        // error
  "where": "位置或模块"         // error
}

前端发送 setup:

json
{
  "type": "setup",
  "system_prompt": "string (optional)",
  "user_prompt": "string (optional)"
}

发送 text:

json
{ "type": "text", "text": "..." }

音频二进制格式说明(非常重要):

  • 前端 → 后端:Int16 PCM,小端(little-endian),采样率:16000 Hz,单声道
  • 后端 → 前端(模型合成):Int16 PCM,小端,采样率常见为 24000 Hz(以实际返回的 mime 为准,客户端应按约定或 mime 处理)

5 WebSocket 生命周期与事件处理(JS 样例) ​

下面给出前端主流程示例(主要片段),便于直接复制到项目中使用。

js
// 创建连接
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";

ws.onopen = () => {
  // 连接建立后发送 setup(可为空)
  ws.send(JSON.stringify({
    type: "setup",
    system_prompt: systemPrompt,
    user_prompt: userPrompt
  }));
};

// 接收消息
ws.onmessage = async (evt) => {
  if (typeof evt.data === "string") {
    const obj = JSON.parse(evt.data);
    handleTextEvent(obj);
  } else if (evt.data instanceof ArrayBuffer) {
    // 二进制:模型合成的 PCM(例如 24k Int16)
    playReceivedAudio(evt.data);
  }
};

function handleTextEvent(obj) {
  switch (obj.type) {
    case "transcript_in": showTranscriptIn(obj.text); break;
    case "transcript_out": showTranscriptOut(obj.text); break;
    case "text": appendModelText(obj.text); break;
    case "turn_complete": handleTurnComplete(); break;
    case "setup_complete": onSetupComplete(); break;
    case "usage": showUsage(obj); break;
    case "go_away": notifyGoAway(obj.timeLeft); break;
    case "error": showError(obj.where, obj.message); break;
    default: console.debug("未知事件", obj);
  }
}

6 发送音频(前端采集与发送要点) ​

  • 采样:建议在前端将麦克风采样重采样到 16000 Hz(若设备采样率不同),并转换为 Int16(范围 -32768..32767)。示例代码中使用 AudioWorklet 或 ScriptProcessor。
  • 打包:每次发送建议 20–80 ms 的音频(例如 40 ms → 640 samples @16k),以获得低延迟与良好网络利用率。
  • 发送示例(Int16Array 转 ArrayBuffer):
js
// i16 是 Int16Array
ws.send(i16.buffer);
  • 发送 audio_end 用于显式告知后端本次说话已结束(可触发模型端 turn 结算);若不发送,也可依赖服务端 VAD。

7 播放模型返回音频(接收端重采样与播放要点) ​

  • 接收到后端二进制(Int16Array),先转为 Float32([-1,1]),再重采样到本地 AudioContext.sampleRate,然后用 AudioBuffer 播放。
  • 保持一个简单播放队列(nextPlayTime)来保证连续性;若队列落后过多可丢弃旧段并追赶实时。
  • 示例播放函数(简化):
js
function playReceivedAudio(arrayBuffer) {
  const i16 = new Int16Array(arrayBuffer);
  const f32 = new Float32Array(i16.length);
  for (let i = 0; i < i16.length; i++) f32[i] = i16[i] / 32768;
  const resampled = resampleLinear(f32, 24000, audioCtx.sampleRate);
  const buf = audioCtx.createBuffer(1, resampled.length, audioCtx.sampleRate);
  buf.copyToChannel(resampled, 0);
  const src = audioCtx.createBufferSource();
  src.buffer = buf;
  src.connect(audioCtx.destination);
  src.start(nextPlayTime);
  nextPlayTime += buf.duration;
}
  • 注意:实际输出采样率应以后端返回的 mime 或协议约定为准(示例常见为 24000 Hz)。

8 状态与错误处理建议 ​

  • 在 onclose 中判断 code 与 reason,如果是短期网络问题可尝试重连(带指数退避),但要注意后端会话可能不可恢复(需重新 setup)。
  • 在接收到 go_away 时,提示用户并准备重连或保存本地状态。
  • 对 error 事件友好提示用户并把错误发回 Sentry / 日志以便排查。
  • 对后端 usage 事件进行统计并可在界面显示消耗提醒。

9 性能与带宽优化 ​

  • 前端打包音频帧大小需权衡延迟与包开销(20–80 ms 常用)。
  • 如果网络不稳定,可暂时降低语音发送频率或合并多帧。
  • 可在服务端启用 VAD/活动检测,避免空白静音数据上传(示例后端已启用 AutomaticActivityDetection)。

10 调试方法与常见问题排查清单 ​

  • 无法建立 WebSocket:

    • 检查 WSS/WS 地址、端口、防火墙与证书(WSS 需要有效证书)。
  • 后端收不到二进制:

    • 确认 ws.binaryType = "arraybuffer";确认发送方发送的是 i16.buffer 而非 i16 的副本错误。
  • 转写为空/不连贯:

    • 检查前端是否正确重采样到 16k;检查是否正确发送小端 Int16。
  • 合成音频异常(噪音/不可播放):

    • 确认后端发送的是 Int16 PCM 小端,并与约定采样率一致;前端按正确采样率解码并重采样。
  • 出现 go_away 或 1011/1012 类型关闭:

    • 查看后端日志(可能是模型端配额或超时),后端应将 go_away 的 timeLeft 告知前端。

11 示例完整接入流程(序列) ​

[浏览器] --(WS open)--> [后端]
[浏览器] --JSON setup--> [后端] --(send prompts to model)--> [模型]
[浏览器] --Int16 PCM--> [后端] --(realtime input)--> [模型]
[模型] --audio/text/transcript--> [后端] --JSON/text or binary--> [浏览器]
[浏览器] --audio_end or text--> [后端] --(client content to model)--> [模型]
[模型] --turnComplete/usage--> [后端] --> [浏览器]

12 最佳实践小结(给前端同学) ​

  1. 优先使用 AudioWorklet 做实时采集并重采样到 16k;若不可用,使用 ScriptProcessor 回退。
  2. 发送二进制时确保使用 Int16 小端序的 ArrayBuffer。
  3. 在播放层实现小队列(nextPlayTime)以保证连续播放且在落后时丢弃旧帧。
  4. 在建立连接前完成鉴权(token/cookie),并保护 token。
  5. 实现健壮的错误 / 断连重连策略并保存必要的会话状态(例如最后一条 transcript)。
  6. 与后端约定好 model 返回的输出采样率(若不确定,在二进制消息外协商或在 JSON 事件里包含 mime / sampleRate 字段)。

13 附:常用代码片段集合(发送 setup、发送文本、发送 Int16) ​

发送 setup:

js
ws.send(JSON.stringify({
  type: "setup",
  system_prompt: "是一个简洁的语音助手",
  user_prompt: "context info"
}));

发送文本:

js
ws.send(JSON.stringify({ type: "text", text: "帮我做个总结" }));

发送 Int16 PCM(示例:i16 是 Int16Array):

js
if (ws && ws.readyState === WebSocket.OPEN) {
  ws.send(i16.buffer);
}

发送 audio_end:

js
ws.send(JSON.stringify({ type: "audio_end" }));

MIT Licensed