基于火山RTC实现AI语音聊天(VoiceAgent)

133 阅读7分钟

实现一个AI语音聊天应用(VoiceAgent)有两种方式,分别是:链式(Chained)和端到端(Speech-to-Speech)。

链式,也有称之为级联,即从ASR到LLM再到TTS。这种方式灵活性强,中间任何一个步骤都可以根据自己的实际业务加入订制化的处理,如从聊天记录中提取关键信息。而缺点也很明显,那就是延时,因为它们的每一步都是单独处理,往返来回的响应时间叠加起来,在某些场景下——譬如智能外呼——非常影响体验。

而端到端的多模态大模型则正好相反,一次性实现了从语音输入到语音输出的处理。虽然大幅降低了延时,但有两个难以忽视的缺点:一是受限于模型本身的性能难以胜任高复杂度的任务,二是中间步骤难以做订制化处理。

综合来看,常规的链式仍是优先选项。不过,这并不意味着必须忍受它那非常影响体验的延时,通过优化架构,仍然可以享受相对良好的体验,那就是本文将会介绍的基于火山RTC实现的VoiceAgent。

为什么选择火山RTC架构?

正如标题所言,基于RTC的架构能够实现高并发、低延时的实时音视频传输能力,且服务端深度融合了ASR、LLM和TTS,进一步提升了传输效率。

另外,除了延时问题以外,在实现VoiceAgent的过程中,如何处理弱网、降噪、语音打断、语音活动检测(VAD)等问题,也是提升用户体验不可忽视的因素。而这些,火山都提供了配套的解决方案,方便开发者快速接入。

参考文档:www.volcengine.com/docs/6348/1…

如何对接火山RTC?

整体的对接过程还是比较复杂的,尤其是像我这种完全没有相关经验的后端开发者,光是处理APP端的逻辑就够我头痛了好一阵子。所以接下来,我会逐步拆分讲解,尽可能保证过程有序、透明。

大致流程

  1. 创建RTC房间
  2. 本地客户端加入房间
  3. 在房间中引入AI智能体

准备工作

  1. 创建项目(console.volcengine.com/iam/resourc…
  2. 创建API访问密钥(console.volcengine.com/iam/keymana…
  3. 新建角色,确保当前账号有足够的访问权限(console.volcengine.com/iam/identit…)。例如,添加一个名为“VoiceChatRoleForRTC”的角色,且拥有如下几种权限:
  4. 在实时音视频页面创建应用(console.volcengine.com/rtc/listRTC…
  5. 在语音技术页面创建应用(console.volcengine.com/speech/app
  6. 创建推理接入点,也就是需要接入的大模型入口(console.volcengine.com/ark/region:…

本地客户端集成RTC SDK

火山提供了sdk和示例代码(https://www.volcengine.com/docs/6348/1455709)支持多种平台,方便开发者快速接入。其功能丰富,但对于一个Demo而言,这里仅需关心如何创建rtc房间、加入rtc房间、监听事件、启用智能体,确保基本可用即可。

(图片来自火山官方文档: www.volcengine.com/docs/6348/6…)

后续我会以ReactJs代码呈现。不过不用担心,只需关注函数名称及调用顺序即可明白。

(需要吐槽一下的是,我是改了node_moudle/下引入的RTC SDK中的一行代码才跑通的,这行代码是引入的一个安卓依赖包,被开发者注释掉了。我不知道为什么开发者会注释掉它,也许是为了让我们在外层自行引用,但是React Native这块文档并未提及,再加上我也尝试过,可是没有成功。有清楚的小伙伴,还请麻烦解答一下)

下面的代码中,除了最后的“启用智能体”这部分外,都可以通过直接调用RTC SDK的内部函数实现。

/** 获取权限 */
await requestDevicePermission();

/** 初始化引擎 */
await RTCClient.createEngine({
  appID: RtcConfig.APP_ID,
});

/** 创建房间实例 */
RTCClient.createRoom(RtcConfig.ROOM_ID);

/** 设置相关回调函数 */
roomEventListeners.onUserJoined = userInfo => {
  console.log('onUserJoined: ', userInfo);
};
roomEventListeners.onRoomBinaryMessageReceived = (
  userId,
  message,
) => {
  console.log('onRoomBinaryMessageReceived: ', userId, message);
};
roomEventListeners.onUserBinaryMessageReceived = (
  userId,
  message,
) => {
  console.log('onUserBinaryMessageReceived: ', userId, message);
};
RTCClient.setRTCRoomEventHandler(roomEventListeners);

/** 加入房间 */
const resultCode = RTCClient.joinRoom({
  userId: RtcConfig.CLIENT_USER_ID,
  token: RtcConfig.CLIENT_USER_TEMP_TOKEN,
});
if (resultCode !== 0) {
  console.error('加入房间失败: ', resultCode);
  return;
}

/** 开启本地音频采集 */
const startAudioCode = RTCClient.startAudioCapture();
if (startAudioCode !== 0) {
  console.error('开始采集音频流失败: ', startAudioCode);
  return;
}
/** 开启本地视频采集 */
// const startVideoCode = RTCClient.startVideoCapture();
// if (startVideoCode !== 0) {
//   console.error('开始采集视频流失败: ', startVideoCode);
//   return;
// }

// 启用智能体
try {
  const response = await startVoiceChat();
  console.log('StartVoiceChat response: ', response);
} catch (error) {
  console.error('Error starting chat bot: ', error);
}

通过上面的代码注释,我们大致了解了如何使用RTC SDK创建并加入房间,接下来说说如何在RTC房间中启用智能体。

启用智能体

官方文档:https://www.volcengine.com/docs/6348/1404673

我们只需通过HTTP请求官方文档中启用智能体这个API即可,除此之外并无其他操作,其难点主要在于配置请求体中的相关参数,那是真的麻烦,叫人头大。回头看看前面我们在“准备工作”中创建的各种配置,那些都是会在启用智能体过程中用到的。

请求参数调试

在构建代码之前,可以先在官方提供的测试调用页面(https://api.volcengine.com/api-explorer?action=StartVoiceChat&groupName=%E6%99%BA%E8%83%BD%E4%BD%93&serviceCode=rtc&version=2024-12-01)进行调试,并将调试成功的请求体复制到你的代码中。该页面有对各个参数的描述,也包含所需配置的对应的跳转链接。

(注意在实际代码中,还需要生成接口签名,官方同样提供了对应的sdk和示例代码。)

接入智能体的4种方式

在接口文档(https://www.volcengine.com/docs/6348/1404673#thirdpartyllmconfig)中有提到,接入大模型有4种方式:
  1. 接入点接入。对应请求字段EndPointId,即前面“准备工作”中提到的第6点
  2. 智能体接入。对应请求字段BotId,相对于第一种这种方式更加自由一些
  3. Coze平台接入。不用多说。
  4. 自定义入口接入。这种方式就需要考虑你的部署节点了,避免火山服务端请求你的第三方接入点时响应时间过长。

语音合成计费模式

在TTS(后面称“语音合成”)相关配置项中,有一个voice_type参数(后面称“音色”)是非必选项,默认值是BV001_streaming。需要注意的是,BV001_streaming这个音色是需要在语音合成页面中开通服务的,开通后才会给默认免费两个音色,即BV001_streaming和BV002_streaming。但开通后,使用语音合成就需要计费了。

使用语音合成有两种方式,分别是:音频生成-语音合成、音频生成大模型-语音合成大模型,对应两种不同的计费模式。前者是按调用次数计费,后者是按字符数计费。

在测试阶段中,通常调用频次高而字符数少,所以在了解到后我切换到了语音合成大模型。切换方式是,仅需填入对应的音色参数即可,即voice_type。

查看房间

在你成功跑通后,可以在监控台查看已经创建好的房间和加入用户:https://console.volcengine.com/rtc/callQualityRTC/diagnosis

进阶玩法

视觉理解

适用于需要进行实时视觉分析的场景,参见文档:https://www.volcengine.com/docs/6348/1408245

基本逻辑是,每隔指定时间段(可配置)拍摄图片,然后将图片提交给视觉大模型去做分析。

RTC房间内开启视频还是挺贵的,360P的清晰度是每分钟0.028元,但真要用起来,起码得是720P吧。玩不起,所以后面我打算采用工具调用的方式,主动开启拍照,将图片手动上传给视觉模型分析,然后将分析结果交给RTC房间内的聊天模型去做汇总,然后合成语音输出。