# Product WebSocket 使用说明 Product WebSocket(`/ws-product`)面向浏览器、移动端等产品客户端,使用稳定的 JSON/base64 协议 `va.ws.v1`。 ## 连接地址 ``` ws://:/ws-product ``` 生产环境请使用 `wss://`。可通过 `GET /health` 查看当前支持的协议与能力。 ## 音频参数 | 参数 | 默认值 | |------|--------| | 编码 | PCM signed 16-bit little-endian(`pcm_s16le`) | | 采样率 | 16000 Hz | | 声道 | 1(单声道) | ## 基本流程 1. 建立 WebSocket 连接 2. 发送 `session.start` 握手 3. 持续发送音频或文本输入 4. 接收服务端事件(转写、回复文本、TTS 音频等) 5. 结束时发送 `session.stop` ### 1. 启动会话 ```json { "type": "session.start", "protocol": "va.ws.v1", "audio": { "encoding": "pcm_s16le", "sample_rate": 16000, "channels": 1 } } ``` ### 2. 发送音频 **JSON + base64**(便于调试): ```json { "type": "input.audio", "audio": "", "sample_rate": 16000, "channels": 1 } ``` **二进制帧**:也可直接发送原始 PCM16 字节,延迟和带宽更优。字段 `audio` 与 `data` 均可作为 base64 字段名。 建议按约 20 ms 一帧分片发送(16 kHz 单声道下每帧约 640 字节)。 ### 3. 发送文本(可选) ```json { "type": "input.text", "text": "你好", "interrupt": true } ``` `interrupt` 默认为 `true`,会打断当前正在播放的 bot 语音。 ### 4. 发送摄像头图片(可选) 客户端可发送一帧摄像头截图,让 LLM 基于图片内容回复。服务端会把图片追加到 Pipecat LLM context,并立即触发一次 LLM 推理;后续回复仍通过现有的 `response.text.*` 与 `response.audio.*` 事件返回。 ```json { "type": "input.image", "image": "", "mime_type": "image/jpeg", "width": 640, "height": 360, "text": "请根据这张摄像头画面回答用户的问题。", "user_id": "product-user", "append_to_context": true } ``` 字段说明: | 字段 | 说明 | |------|------| | `image` / `data` | 必填,图片字节的 base64;也兼容 `data:image/...;base64,...` data URL | | `mime_type` / `media_type` | 可选,默认 `image/jpeg`;支持 `image/jpeg`、`image/png`、`image/webp` | | `width`、`height` | 必填,图片像素尺寸,必须为正整数 | | `text` | 可选,随图片一起进入 LLM 的问题/提示词 | | `user_id` | 可选,默认 `product-user` | | `append_to_context` | 可选,默认 `true`;为 `true` 时图片会进入 LLM context 并触发回复 | 建议发送压缩后的截图而不是持续视频流,例如 640px 宽、JPEG quality 0.75 左右。单张图片最大 8 MB。 ### 5. 取消当前回复 ```json {"type": "response.cancel"} ``` ### 6. 结束会话 ```json {"type": "session.stop", "reason": "done"} ``` ## 服务端事件 所有出站消息均为 JSON,包含 `type`、`protocol`(`va.ws.v1`)和递增的 `seq` 字段。 | 事件类型 | 说明 | |----------|------| | `input.transcript.interim` | 用户语音中间转写(流式 ASR 时) | | `input.transcript.final` | 用户语音最终转写 | | `response.state` | 可选;当 `agent.response_state.enabled` 开启且 LLM 回复以 `...` 开头时发送 | | `response.text.started` | 助手开始回复 | | `response.text.delta` | 助手文本流式片段(通常早于 TTS 音频) | | `response.text.final` | 助手本轮回复结束,见下方 `interrupted` 说明 | | `response.audio.started` | 开始播放 TTS | | `response.audio.delta` | TTS 音频片段(base64 PCM16) | | `response.audio.stopped` | TTS 播放结束 | 音频事件示例: ```json { "type": "response.audio.delta", "protocol": "va.ws.v1", "seq": 1, "audio": "", "bytes": 6400, "sample_rate": 16000, "channels": 1 } ``` ### `response.text.final` 与 `interrupted` 每轮助手回复结束时都会发送一条 `response.text.final`: ```json { "type": "response.text.final", "text": "今天北京天气晴朗,", "interrupted": true } ``` - **`interrupted: false`** — 助手正常说完本轮内容(文本流与 TTS 自然结束)。 - **`interrupted: true`** — 本轮被打断,`text` 只包含**已流式输出、尚未说完**的部分,不是完整原定回复。 以下情况会置 `interrupted` 为 `true`,并停止当前 TTS: 1. **文本打断** — 发送 `input.text` 且 `interrupt` 为 `true`(默认值)。 2. **主动取消** — 发送 `response.cancel`。 3. **语音插话(barge-in)** — 助手正在说话时,用户开口且满足打断条件:转写文本达到最短字数门槛,或命中常见短答词(如「是的」「行」「可以」)。过短的背景噪声通常不会触发打断。 客户端可用 `interrupted` 区分「完整回复」与「说到一半被截断」,例如在 UI 上对未说完的气泡做标记。 ## 浏览器示例 引擎可在 `config.json` 中开启内置演示页: ```json "server": { "serve_webpage": true, "webpage_mount": "/voice-demo" } ``` 启动服务后访问 `http://localhost:8000/voice-demo/`,默认连接同源的 `/ws-product`。参考实现见 `examples/webpage/`。