# 集成 WebSocket 实时语音 Web SDK WebSocket 实时语音 Web SDK (简称 WsChat SDK) 基于双向流式语音对话 WebSocket OpenAPI 封装 ,为开发者提供开箱即用的语音交互解决方案,详细的接口信息请参见[双向流式语音对话](https://docs.coze.cn/api/open/docs/developer_guides/streaming_chat_api)。 ## 体验 Demo 扣子提供[实时语音 Demo](https://www.coze.cn/open-platform/realtime/websocket) 和 TypeScript 格式的 [实时语音 WebSocket 示例源码](https://github.com/coze-dev/coze-js/tree/main/examples/realtime-websocket),帮助你快速体验实时语音的功能,并根据示例源码快速实现实时语音。 ### Demo 功能简介 Demo 的主要功能包括: * 实时语音识别:将用户语音即时转换为文本。 * 配置管理:支持设置降噪模式、音频配置。 * 设备管理:支持设置不同的音频输入设备。 * 交互控制:支持打断对话、静音、发送文本、发送事件等。 * 会话管理:支持建立和断开语音连接、设置静音等。 ![Image](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/1df953239d60401f9545e71515b4912f~tplv-goo7wpa0wc-image.image) ### 使用 Demo 1. 配置参数。 单击右上角的 **Settings**,配置个人访问令牌和智能体 ID 等参数,具体如下表所示。 | **配置** | **说明** | | --- | --- | | Base WS URL | 保持默认值 `wss://ws.coze.cn`。 | | 个人访问令牌 | 扣子 API & SDK 通过访问令牌进行 API & SDK 请求的鉴权。
个人访问令牌的获取方式可参考[添加个人访问令牌](https://docs.coze.cn/api/open/docs/developer_guides/pat)。
* 应为令牌授予 chat、createVoice 和 listVoice 的权限。
* 令牌授予的访问工作空间中包含了待与其通话的智能体,否则会提示鉴权失败。
| | 智能体 ID | 配置希望与其实时语音聊天的智能体 ID。
进入智能体的开发页面,开发页面 URL 中 `bot` 参数后的数字就是智能体ID。例如`https://www.coze.cn/space/341****/bot/73428668*****`,智能体ID 为`73428668*****`。
* 确保个人访问令牌开通了此智能体所在空间的权限。
* 确保该智能体已发布为 API 服务。
| | 音色 ID | 设置智能体使用的音色。扣子提供一系列系统音色,你可以在[系统音色列表](https://docs.coze.cn/api/open/docs/dev_how_to_guides/sys_voice)查看音色 ID。 | | 工作流 ID | 如果你需要自定义传参,你可以在对话流的开始节点设置自定义参数,然后在本参数中指定相应的对话流 ID。
获取对话流 ID 的方法:通过进入 Workflow 编排页面,在页面 URL 中,`workflow` 参数后的数字就是对话流 ID。例如 `https://www.coze.com/work_flow?space_id=42463***&workflow_id=73505836754923***`,对话流 ID 为 `73505836754923***`。 | 2. 单击**开始对话**,向指定智能体发起实时语音通话。 实时语音通话需要获取设备的麦克风权限,如果页面提示 `[DEVICE_ACCESS_ERROR] Failed to get audio devices`,表示浏览器禁用了麦克风设备。 ## 实现流程 ### 步骤一: 安装依赖 运行以下命令安装 WsChat SDK 及其依赖项。 ```Shell npm install @coze/api ``` ### 步骤二:检查设备权限 在使用语音功能前,先检查并获取麦克风访问权限。调用`WsToolsUtils.checkDevicePermission() `方法,检查浏览器是否已授予麦克风权限。 在 HTTPS 或 localhost 中执行该方法。首次调用时,浏览器会向用户显示权限请求对话框,如果用户拒绝麦克风权限,在应用中提供明确指引,告知用户如何在浏览器设置中启用权限。在 iOS Safari 浏览器中,必须由用户交互操作(如点击按钮)触发权限请求。 ```TypeScript import { WsToolsUtils } from "@coze/api/ws-tools"; const result = await WsToolsUtils.checkDevicePermission(); if (!result.audio) { throw new Error("需要麦克风访问权限"); } ``` ### 步骤三:初始化客户端 调用 `new WsChatClient` 方法创建 WsChatClient 实例,配置 token 和 botId。 ```JavaScript import { WsChatClient } from "@coze/api/ws-tools"; const client = new WsChatClient({ token: 'pat_Qm47PKJR5dvMOP53v6DyzwCbTtvEZHQc2TVINEveg9v1T3iSYlTdScJ8***', botId: 'your bot id', allowPersonalAccessTokenInBrowser: true, // 可选:允许在浏览器中使用个人访问令牌 }); ``` 参数说明: * **token**:访问密钥,用于身份认证与鉴权。体验或调试场景可以生成短期的个人访问令牌(PAT),以快速完成 WsChat SDK 的整体流程。个人访问令牌的获取方法请参见[添加个人访问令牌](https://docs.coze.cn/api/open/docs/developer_guides/pat)。在线上环境中,应使用服务访问令牌(SAT)或 OAuth 鉴权方案,详细说明请参见[鉴权方式概述](https://docs.coze.cn/api/open/docs/developer_guides/authentication)。 扣子 SDK 封装了多种鉴权方式,能够有效简化鉴权流程,你可以参考[鉴权示例代码](https://github.com/coze-dev/coze-py/tree/main/examples)实现不同方式的 OAuth 认证,以获取和管理访问扣子 API 所需的令牌 * **botId**:配置希望与其实时语音聊天的智能体 ID。进入智能体的开发页面,开发页面 URL 中 `bot` 参数后的数字就是智能体ID。例如`https://www.coze.cn/space/341****/bot/73428668*****`,智能体ID 为`73428668*****`。 ### 步骤四:监听事件 在初始化客户端后,通过 `client.on` 方法注册各种事件监听器。详细的事件说明请参见[双向流式对话上行事件](https://docs.coze.cn/api/open/docs/developer_guides/streaming_chat_event)。 ```JavaScript import { WsChatEventNames } from "@coze/api/ws-tools"; // 监听所有事件 client.on(WsChatEventNames.ALL, (eventName, data) => { console.log(eventName, data); }); ``` 支持的事件类型如下,若想了解所有事件的详细情况,可以直接查看[示例项目源码中的 types.ts](https://github.com/coze-dev/coze-js/blob/main/packages/coze-js/src/ws-tools/types.ts)。 ```TypeScript // 事件监听示例 client.on(WsChatEventNames.ALL, (event) => { console.log(`收到事件:`, event); }); // 支持的事件类型 enum WsChatEventNames { // 客户端基础事件 ALL = 'realtime.event', // 所有事件 CONNECTED = 'client.connected', // 客户端已连接 CONNECTING = 'client.connecting', // 客户端连接中 INTERRUPTED = 'client.interrupted', // 客户端已中断 DISCONNECTED = 'client.disconnected', // 客户端已断开 ERROR = 'client.error', // 客户端发生错误 // 音频控制事件 AUDIO_UNMUTED = 'client.audio.unmuted', // 音频已取消静音 AUDIO_MUTED = 'client.audio.muted', // 音频已静音 AUDIO_INPUT_DUMP = 'client.audio.input.dump', // 音频输入数据导出 // 设备变更事件 AUDIO_INPUT_DEVICE_CHANGED = 'client.input.device.changed', // 音频输入设备已改变 AUDIO_OUTPUT_DEVICE_CHANGED = 'client.output.device.changed', // 音频输出设备已改变 // 降噪控制事件 DENOISER_ENABLED = 'client.denoiser.enabled', // 降噪已启用 DENOISER_DISABLED = 'client.denoiser.disabled', // 降噪已禁用 // 服务端对话事件 CHAT_CREATED = 'server.chat.created', // 对话已创建 CHAT_UPDATED = 'server.chat.updated', // 对话已更新 // 会话状态事件 CONVERSATION_CHAT_CREATED = 'server.conversation.chat.created', // 会话对话已创建 CONVERSATION_CHAT_IN_PROGRESS = 'server.conversation.chat.in.progress', // 对话进行中 CONVERSATION_CHAT_COMPLETED = 'server.conversation.chat.completed', // 对话已完成 CONVERSATION_CHAT_FAILED = 'server.conversation.chat.failed', // 对话失败 CONVERSATION_CHAT_CANCELLED = 'server.conversation.chat.cancelled', // 对话已取消 CONVERSATION_CHAT_REQUIRES_ACTION = 'server.conversation.chat.requires_action', // 对话需要端插件响应 // 消息事件 CONVERSATION_MESSAGE_DELTA = 'server.conversation.message.delta', // 文本消息增量返回 CONVERSATION_MESSAGE_COMPLETED = 'server.conversation.message.completed', // 文本消息完成 // 音频事件 CONVERSATION_AUDIO_DELTA = 'server.conversation.audio.delta', // 语音消息增量返回 CONVERSATION_AUDIO_COMPLETED = 'server.conversation.audio.completed', // 语音回复完成 // 语音识别事件 CONVERSATION_AUDIO_TRANSCRIPT_UPDATE = 'server.conversation.audio_transcript.update', // 用户语音识别实时字幕更新 CONVERSATION_AUDIO_TRANSCRIPT_COMPLETED = 'server.conversation.audio_transcript.completed', // 用户语音识别完成 // 语音检测事件 INPUT_AUDIO_BUFFER_SPEECH_STARTED = 'server.input_audio_buffer.speech_started', // 检测到用户开始说话 INPUT_AUDIO_BUFFER_SPEECH_STOPPED = 'server.input_audio_buffer.speech_stopped', // 检测到用户停止说话 // 缓冲区事件 INPUT_AUDIO_BUFFER_COMPLETED = 'server.input_audio_buffer.completed', // 语音输入缓冲区提交完成 INPUT_AUDIO_BUFFER_CLEARED = 'server.input_audio_buffer.cleared', // 语音输入缓冲区已清除 // 其他事件 SERVER_ERROR = 'server.error', // 服务端错误 CONVERSATION_CLEARED = 'server.conversation.cleared', // 对话上下文已清除 DUMP_AUDIO = 'server.dump.audio' // 音频导出 } ``` ### 步骤五:建立连接 在开始对话前,调用 `client.connect` 方法建立客户端和服务端之间的连接。 ```TypeScript try { await client.connect(); } catch (error) { console.log('连接失败', error); } ``` * 连接过程可能因网络问题、权限问题或配置错误而失败,确保妥善处理异常。 * 连接成功后,麦克风会自动开始录音。若 `audioMutedDefault` 设置为 `true`,则会静音。 ### 步骤六:对话控制 在对话过程中支持静音或取消静音、打断对话、发送文本消息。 * **静音/取消静音** 调用`setAudioEnable()` 方法静音或取消静音。该方法是一个异步方法,使用`await` 等待操作完成。在静音状态下,麦克风仍在工作,但不会发送音频数据。 ```JavaScript // 静音与取消静音 await client.setAudioEnable(false); // 静音 await client.setAudioEnable(true); // 取消静音 ``` * **打断对话** 调用 `interrupt()` 方法打断当前对话,使智能体停止回复。 ```JavaScript // 打断对话 client.interrupt(); ``` * **发送文本消息** 使用 `sendTextMessage()` 可以发送文本消息,而不是语音输入。 ```JavaScript // 发送文本消息 client.sendTextMessage("你好,我想了解一下天气情况"); ``` ### 步骤七:断开连接 调用 `disconnect()` 方法关闭连接并释放资源。如果需要重新开始对话,重新调用 `connect()` 方法建立连接。 ```JavaScript // 断开连接 await client.disconnect(); ``` ### 完整示例代码 完整的示例代码如下。你也可以参考[实时语音 WebSocket 示例源码](https://github.com/coze-dev/coze-js/tree/main/examples/realtime-websocket)获取更多示例代码。 ```TypeScript import { WsChatEventNames, WsChatClient, WsToolsUtils, } from '@coze/api/ws-tools'; // 初始化 client & 注册事件 const initClient = async () => { // 检查语音设备权限 const result = await WsToolsUtils.checkDevicePermission(); if (!result.audio) { throw new Error("需要麦克风访问权限"); } // 初始化 client const client = new WsChatClient({ token: "pat_Qm47PKJR5dvMOP53v6DyzwCbTtvEZHQc2TVINEveg9v1T3iSYlTdScJ8***", botId: "your bot id", allowPersonalAccessTokenInBrowser: true, // 可选:允许在浏览器中使用个人访问令牌 }); // 注册事件,一般在初始化 client 后设置 client.on(WsChatEventNames.ALL, (eventName, data) => { console.log(eventName, data); }); return client; }; const handleConnect = async () => { try { const client = await initClient(); // 连接 await client.connect(); } catch (error) { console.log("连接失败", error); } }; ``` ## 进阶功能 ### 客户端配置 在初始化客户端时,你还可以进行高级配置,以满足不同的开发需求,包括:开启调试模式、指定智能体的音色、指定音频输入设备、音频处理及录制、AI 降噪、音频录制等。完整的配置项请查看[示例项目源码中的 types.ts](https://github.com/coze-dev/coze-js/blob/main/packages/coze-js/src/ws-tools/types.ts)。 ```TypeScript interface WsChatClientOptions { // 必填项 token: GetToken; // Personal Access Token (PAT) 或 OAuth2.0 token,或获取 token 的函数 botId: string; // 智能体 ID // 选填项 debug?: boolean; // 是否启用调试模式 headers?: Headers | Record; // 自定义请求头 allowPersonalAccessTokenInBrowser?: boolean; // 是否允许在浏览器环境中使用个人访问令牌 baseWsURL?: string; // WebSocket 基础 URL,默认为 wss://ws.coze.cn websocketOptions?: WebsocketOptions; // WebSocket 配置选项 workflowId?: string; // 工作流 ID voiceId?: string; // 音色 ID deviceId?: string; // 音频输入设备 ID // 音频相关配置 audioCaptureConfig?: { noiseSuppression?: boolean; // 是否启用降噪 echoCancellation?: boolean; // 是否启用回声消除 autoGainControl?: boolean; // 是否启用自动增益控制 }; audioMutedDefault?: boolean; // 是否默认静音 // AI 降噪配置 aiDenoisingConfig?: { mode?: AIDenoiserProcessorMode; // AI 降噪模式:NSNG(非平稳噪声抑制) 或 STATIONARY_NS(平稳噪声抑制) level?: AIDenoiserProcessorLevel; // 降噪级别 assetsPath?: string; // AI 降噪 wasm 文件路径,默认为 '/external' }; // 自定义音频流 mediaStreamTrack?: MediaStreamTrack; // 音频录制配置(仅在 debug = true 时生效) wavRecordConfig?: { enableSourceRecord: boolean; // 是否启用源音频录制 enableDenoiseRecord: boolean; // 是否启用降噪后音频录制 }; } ``` ### 音视频控制 在通话过程中,支持开启或关闭麦克风、设置输入音频设备。 * 开启或关闭麦克风 ```TypeScript // 开启/关闭麦克风 await client.setAudioEnable(true); // 开启麦克风 await client.setAudioEnable(false); // 关闭麦克风 ``` * 设置输入音频设备 你可以通过` WsToolsUtils.getAudioDevices` 方法获取系统的音频输入和输出设备列表,返回的设备列表包含每个设备的 deviceId 和 label 信息。 ```TypeScript import { WsToolsUtils } from "@coze/realtime-api"; const devices = await WsToolsUtils.getAudioDevices(); // 获取输入、输出设备列表 client.setAudioInputDevice(devices.audioInputs[0].deviceId); // 设置音频输入设备 ``` * 获取设备列表需要用户已授予麦克风权限。 * 在用户插入或移除设备后,应重新获取设备列表。 * 切换设备是异步操作,需要使用 await 等待完成。 * 如果指定的设备不可用,用户操作时将抛出异常。 ### 开启或关闭调试模式 在调试模式下可以使用音频 dump 功能,以验证降噪效果。 1. 在创建 WsChatClient 时,将 `debug` 设置为 `true`,开启调试模式,并配置音频录制。 ```Java const client = new WsChatClient({ // ... 其他配置 debug: true, // 必须设置为 true,否则录制配置不生效 wavRecordConfig: { enableSourceRecord: true, // 启用原始音频录制 enableDenoiseRecord: true, // 启用降噪后的音频录制 } }); ``` 2. 监听音频 dump 事件来获取录制的音频数据。 ```TypeScript // 监听音频输入 dump 事件 client.on(WsChatEventNames.AUDIO_INPUT_DUMP, (eventName, event) => { // event.data 包含: // - name: string 文件名 // - wav: Blob 音频数据(WAV格式) const { name, wav } = event.data; console.log(`收到音频dump,文件名: ${name}`); // 可以将 wav 数据保存为文件 const url = URL.createObjectURL(wav); const a = document.createElement('a'); a.href = url; a.download = name; a.click(); }); ``` dump 功能会产生两种音频文件: * 原始音频文件:包含用户的原始输入音频。只有当 `enableSourceRecord = true` 时才会生成原始音频文件。 * 降噪后音频文件:包含经过降噪处理后的音频。只有当 `enableDenoiseRecord = true` 时才会生成降噪后音频文件。 ### AI 降噪 * 降噪处理可能增加 CPU 使用率,在低性能设备上可能影响体验。 * 不同的降噪等级会影响音频质量和降噪效果。 1. 检查浏览器是否支持 AI 降噪。 某些浏览器或设备可能不支持全部降噪功能,当不确定设备支持情况时,可以使用 `WsToolsUtils.checkDenoiserSupport() `方法检查。 ```TypeScript const denoiserSupport = await WsToolsUtils.checkDenoiserSupport(); if (!denoiserSupport) { console.warn("当前浏览器不支持 AI 降噪"); } ``` 2. 在初始化时配置 AI 降噪。 ```TypeScript const client = new WsChatClient({ // 其它配置 aiDenoisingConfig: { mode: 'NSNG', // AI 降噪模式,包括NSNG(非平稳噪声抑制) 或 STATIONARY_NS(平稳噪声抑制) level: 'SOFT', // 降噪等级,包括SOFT(舒缓降噪), AGGRESSIVE(激进降噪。将降噪强度提高到激进降噪会增大损伤人声的概率。) } }); ``` 3. 开启或关闭 AI 降噪,设置 AI 降噪的等级和模式。 ```TypeScript // 设置降噪开关 client.setDenoiserEnabled(true); // 设置降噪等级 client.setDenoiserLevel("SOFT"); // 可选值: SOFT(舒缓降噪), AGGRESSIVE(激进降噪。将降噪强度提高到激进降噪会增大损伤人声的概率。) // 设置降噪模式 client.setDenoiserMode("NSNG"); // NSNG(非平稳噪声抑制) 或 STATIONARY_NS(平稳噪声抑制) ``` ### 配置音色 在初始化 WsChat SDK 时,你可以通过指定 `voiceId` 来自定义智能体在对话中使用的音色。以下是使用说明: 1. 获取可用的音色 ID。 你可以通过[查看音色列表](https://docs.coze.cn/api/open/docs/developer_guides/list_voices) 获取可用的音色列表。 ```TypeScript import { CozeAPI, COZE_CN_BASE_URL } from '@coze/api'; const api = new CozeAPI({ token: 'your-access-token', baseURL: COZE_CN_BASE_URL, }); // 获取可用的音色列表 const voices = await api.audio.voices.list(); ``` 2. 在初始化 WsChat 客户端时,指定智能体使用的音色。 每个音色都有不同的特征,如性别、语言、风格等,如果不指定 `voiceId` 或值为空,将使用默认的`柔美女友`音色,音色 ID 为 7426720361733046281。 ```TypeScript import { WsChatClient } from "@coze/api/ws-tools"; const client = new WsChatClient({ // 其它配置 voiceId: "7426720361733046281", }); ``` ## WebSocket 事件 ### 基本概念 * 上行事件:设备端向服务端发送的请求,如提交插件执行结果、更新对话配置等。 * 下行事件:服务端向设备端发送的通知,如端插件请求、对话状态变化等。 * 会话与对话:会话是用户与智能体的一次完整交互,对话是会话中的一次具体调用。 * 事件 ID:每个事件应有唯一 ID,便于问题定位。 ### 事件的使用流程 信令事件的使用流程如下: 1. 初始化。通过监听 `chat.created` 事件,确认对话初始化完成。 2. 发送请求。使用上行事件(如 `conversation.message.create`)向智能体发送消息。 3. 处理响应。监听下行事件(如 `conversation.message.delta`)获取智能体的增量回复。 4. 插件交互。在收到事件 `conversation.chat.requires_action` 后,执行插件操作并通过事件 `conversation.chat.submit_tool_outputs` 提交结果。根据需求选择 blocking 或 nonblocking 模式,控制插件执行是否阻塞对话。 5. 错误处理。通过监听 `error` 事件捕获和处理异常情况。 使用示例如下: ```TypeScript import { WebsocketsEventType, RoleType } from '@coze/api'; // 使用示例 client.sendMessage({ "id": "1", "event_type": WebsocketsEventType.CONVERSATION_MESSAGE_CREATE, "data": { "role": RoleType.User, "content_type": "text", "content": "你好" } }); ``` ###