实现 AI 语音助手
声网提供 AIGCService SDK,借助三方 STT(语音转文字)、TTS(文本转语音)和 LLM(大型语言模型)的能力,能在 App 中实现语音识别、自然语言理解、智能对话等功能。
本文介绍 AI 语音助手场景的实现流程,以及如何集成 AIGCService SDK,并调用核心 API 来实现这个流程。
业务流程
AI 语音助手的实现流程如下图所示:
上图中,采集和播放音频数据是使用声网 RTC SDK 的原始音频数据功能实现的。你也可以选用其他 RTC SDK 的相应功能实现。
前提条件
开始前,请确保满足如下要求:
- Git
- Java Development Kit
- Android Studio 4.1 以上版本
- Android API 级别 23 及以上
- Android 6.0 或以上版本的移动设备。声网推荐使用真机运行项目,部分模拟机可能存在功能缺失或者性能问题
- 参考开通服务创建项目、获取 App ID、App 证书、临时 Token 并开通 AIGCService 服务
集成 SDK
参考如下步骤获取 AIGCService SDK,并将 SDK 集成到你的项目中。
如果你本地尚无 Android 项目,可以参考 Android 官方文档 Create a project 创建。
联系 sales@shengwang.cn 获取最新的 AIGCService SDK 版本号,在 /app/build.gradle
的 dependencies
中添加对 AIGCService SDK 的依赖。
// x.y.z 替换为最新的 AIGCService SDK 版本号,例如 1.2.0
implementation("io.github.winskyan:Agora-AIGCService:x.y.z")
AIGCService SDK 仅支持 arm64-v8a 和 armeabi-v7a 架构。
项目设置
权限申请
在项目的 /app/src/main/AndroidManifest.xml
文件中,添加如下权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
环境配置
在项目的 /app/build.gradle
文件的 defaultConfig
字段中添加如下行:
minSdk 21
targetSdk 30
compileSdk 32
防止代码混淆
在 /app/proguard-rules.pro
文件中添加如下行:
-keep class io.agora.** { *; }
-keepnames class io.agora.* { *; }
-keepnames interface io.agora.* { *; }
-dontwarn io.agora.**
-keep class com.microsoft.cognitiveservices.speech.** { *; }
-keepnames class com.microsoft.cognitiveservices.speech.* { *; }
-keepnames interface com.microsoft.cognitiveservices.speech.* { *; }
-dontwarn com.microsoft.cognitiveservices.speech.**
-keep class io.netty.** { *; }
-keepnames class io.netty.* { *; }
-keepnames interface io.netty.* { *; }
-dontwarn io.netty.**
实现 AI 语音助手
下文展示如何在项目中集成 AIGCService 的核心 API,并实现发送音频数据、接收语言模型处理后的音频数据的逻辑。完整的 API 时序图可以参考 API 时序图。
1. 创建 AIGCService 实例
调用 create
和 initialize
方法创建并初始化 AIGC 服务示例。你需要在 initialize
方法中通过 AIGCServiceConfig
对服务实例进行配置。
if (null == mAIGCService) {
mAIGCService = AIGCService.create();
}
mAIGCService.initialize(new AIGCServiceConfig() {{
this.context = onContext;
this.callback = serviceCallback;
this.enableLog = true;
this.enableSaveLogToFile = true;
this.userName = "AI";
this.appId = KeyCenter.APP_ID;
this.rtmToken = KeyCenter.getRtmToken(KeyCenter.getUserUid());
this.userId = String.valueOf(KeyCenter.getUserUid());
this.enableMultiTurnShortTermMemory = false;
this.speechRecognitionFiltersLength = 0;
this.input = new SceneMode() {{
this.language = Language.ZH_CN;
this.speechFrameSampleRates = 16000;
this.speechFrameChannels = 1;
this.speechFrameBits = 16;
}};
this.output = new SceneMode() {{
this.language = Language.ZH_CN;
this.speechFrameSampleRates = 16000;
this.speechFrameChannels = 1;
this.speechFrameBits = 16;
}};
this.noiseEnvironment = NoiseEnvironment.NOISE;
this.speechRecognitionCompletenessLevel = SpeechRecognitionCompletenessLevel.NORMAL;
}});
初始化为耗时操作。完成初始化后,SDK 会触发 onEventResult(INITIALIZE, SUCCESS)
回调。
2. 监听回调事件
除了 onEventResult
回调外,你还需要注册如下回调,用来接收各语言模型返回的数据结果:
onSpeech2TextResult
:回调语音转文字 (STT) 处理后的结果。onLLMResult
:回调大语言模型 (LLM) 处理后的结果。onText2SpeechResult
:回调文字转语音 (TTS) 处理后的结果。onSpeechStateChange
:识别语音说话的状态。
@Override
public void onEventResult(@NonNull ServiceEvent event, @NonNull ServiceCode code, @Nullable String msg) {
Log.i(TAG, "onEventResult event:" + event + " code:" + code + " msg:" + msg);
if (event == ServiceEvent.INITIALIZE && code == ServiceCode.SUCCESS) {
} else if (event == ServiceEvent.START && code == ServiceCode.SUCCESS) {
} else if (event == ServiceEvent.STOP && code == ServiceCode.SUCCESS) {
} else if (event == ServiceEvent.DESTROY && code == ServiceCode.SUCCESS) {
}
}
@Override
public HandleResult onSpeech2TextResult(String roundId, Data<String> result, boolean isRecognizedSpeech, ServiceCode code) {
Log.i(TAG, "onSpeech2TextResult roundId:" + roundId + " result:" + result + " isRecognizedSpeech:" + isRecognizedSpeech + " code:" + code);
return HandleResult.CONTINUE;
}
@Override
public HandleResult onLLMResult(String roundId, Data<String> answer, boolean isRoundEnd, int estimatedResponseTokens, ServiceCode code) {
Log.i(TAG, "onLLMResult roundId:" + roundId + " answer:" + answer + " isRoundEnd:" + isRoundEnd + " estimatedResponseTokens:" + estimatedResponseTokens + " code:" + code);
return HandleResult.CONTINUE;
}
@Override
public HandleResult onText2SpeechResult(String roundId, Data<byte[]> voice, int sampleRates, int channels, int bits, ServiceCode code) {
Log.i(TAG, "onText2SpeechResult roundId:" + roundId + " voice:" + voice + " sampleRates:" + sampleRates + " channels:" + channels + " bits:" + bits + " code:" + code);
return HandleResult.CONTINUE;
}
@Override
public void onSpeechStateChange(SpeechState state) {
Log.i(TAG, "onSpeechStateChange state:" + state);
}
3. 获取和设置参数
该步骤允许用户根据场景需要,自定义 AIGC 服务的属性。具体步骤如下:
- 调用
getRoles
获取 SDK 支持的所有AIRole
,包括 AI 服务的名称、职业、性别等。 - 调用
setRole
设置 SDK 使用的AIRole
。 - 调用
getServiceVendors
获取所有 AI 服务商的信息,包含三种类型: - 调用
setServiceVendor
设置所使用的 AI 服务商。
@Override
public void onEventResult(@NonNull ServiceEvent event, @NonNull ServiceCode code, @Nullable String msg) {
Log.i(TAG, "onEventResult event:" + event + " code:" + code + " msg:" + msg);
if (event == ServiceEvent.INITIALIZE && code == ServiceCode.SUCCESS) {
Log.i(TAG, "getRoles:" + Arrays.toString(mAIGCService.getRoles()));
Log.i(TAG, "getCurrentRole:" + mAIGCService.getCurrentRole());
mAIGCService.setRole(mAIGCService.getRoles()[0].getRoleId());
Log.i(TAG, "getServiceVendors:" + mAIGCService.getServiceVendors());
ServiceVendor serviceVendor = new ServiceVendor();
serviceVendor.setTtsVendor(mAIGCService.getServiceVendors().getTtsList().get(0));
mAIGCService.setServiceVendor(serviceVendor);
mAIGCService.setSTTMode(STTMode.FREESTYLE);
}
}
4. 开启 AIGCService
完成初始化相关设置后,调用 start
方法开启服务。
mAIGCService.start()
该步骤也是耗时操作。完成开启服务后,会收到 onEventResult(START, SUCCESS)
回调,告知 AIGCService 开启成功。
5. 发送接收到的数据
获取到用户发送的消息数据后,你需要将该数据发送给 AIGCService
。AIGCService SDK 接收到数据后,会将数据发送给接入的各语言模型。
该步骤需要在调用 start
开启服务后实现。
- 语音消息
- 文字消息
如果用户发送的是语音消息,进行如下步骤:
-
采集本地用户发送的音频数据。如果你选用的是声网 RTC SDK,可以参考音频原始数据,从
onRecordAudioFrame
回调中获取。 -
获取到音频数据后,调用
pushSpeechDialogue
方法将数据发送给AIGCService
。JavamAIGCService.pushSpeechDialogue(data, Vad.UNKNOWN, false);
如果用户发送的是文字消息,你可以直接调用 pushTxtDialogue
或者 pushMessagesToLLM
将数据发送给 AIGCService
。
// 仅支持推送单条文字消息
mAIGCService.pushTxtDialogue("你叫什么名字?", "");
// 支持推送多条消息(消息及其上下文语境)
mAIGCService.pushMessagesToLLM("[{\n" +
"\t\t\"role\": \"user\",\n" +
"\t\t\"content\": \"你想去郊游吗?\"\n" +
"\t}, {\n" +
"\t\t\"role\": \"assistant\",\n" +
"\t\t\"content\": \"好啊!我们什么时候去?\"\n" +
"\t},\n" +
"\t{\n" +
"\t\t\"role\": \"user\",\n" +
"\t\t\"content\": \"周日?\"\n" +
"\t}\n" +
"]", "{\n" +
"\t\"temperature\": 0.5,\n" +
"\t\"presence_penalty\": 0.9,\n" +
"\t\"frequency_penalty\": 0.9\n" +
"}", "");
6. 接收处理结果
各语言模型处理结束后,会将处理结果返回给 SDK。SDK 会触发如下回调,将处理结果发送给 App。
- 语音消息
- 文字消息
如果用户发送的是语音消息,需要监听如下回调:
// 接收语音转文字的处理结果
@Override
public HandleResult onSpeech2TextResult(String roundId, Data<String> result, boolean isRecognizedSpeech, ServiceCode code) {
Log.i(TAG, "onSpeech2TextResult roundId:" + roundId + " result:" + result + " isRecognizedSpeech:" + isRecognizedSpeech + " code:" + code);
return HandleResult.CONTINUE;
}
// 接收大语言模型的处理结果
@Override
public HandleResult onLLMResult(String roundId, Data<String> answer, boolean isRoundEnd, int estimatedResponseTokens, ServiceCode code) {
Log.i(TAG, "onLLMResult roundId:" + roundId + " answer:" + answer + " isRoundEnd:" + isRoundEnd + " estimatedResponseTokens:" + estimatedResponseTokens + " code:" + code);
return HandleResult.CONTINUE;
}
// 接收语音识别的处理结果
@Override
public HandleResult onText2SpeechResult(String roundId, Data<byte[]> voice, int sampleRates, int channels, int bits, ServiceCode code) {
Log.i(TAG, "onText2SpeechResult roundId:" + roundId + " voice:" + voice + " sampleRates:" + sampleRates + " channels:" + channels + " bits:" + bits + " code:" + code);
return HandleResult.CONTINUE;
}
如果用户发送的是文字消息,需要监听如下回调:
// 接收大语言模型的处理结果
@Override
public HandleResult onLLMResult(String roundId, Data<String> answer, boolean isRoundEnd, int estimatedResponseTokens, ServiceCode code) {
Log.i(TAG, "onLLMResult roundId:" + roundId + " answer:" + answer + " isRoundEnd:" + isRoundEnd + " estimatedResponseTokens:" + estimatedResponseTokens + " code:" + code);
return HandleResult.CONTINUE;
}
// 接收语音识别的处理结果
@Override
public HandleResult onText2SpeechResult(String roundId, Data<byte[]> voice, int sampleRates, int channels, int bits, ServiceCode code) {
Log.i(TAG, "onText2SpeechResult roundId:" + roundId + " voice:" + voice + " sampleRates:" + sampleRates + " channels:" + channels + " bits:" + bits + " code:" + code);
return HandleResult.CONTINUE;
}
接收到处理后的数据后,如果是文字消息,你可以直接返回;如果是语音消息,还需要播放获取到的音频数据。
如果你选用的是声网 RTC SDK,可以参考音频原始数据,使用 onPlaybackAudioFrame
播放。
7. 停止 AIGCService
无需使用 AI 语音助手后,你可以调用 stop
停止 AIGCService
实例。
该步骤需要在调用 start
开启服务后实现。
停止服务为耗时操作。成功停止后,会收到 onEventResult(STOP, SUCCESS)
回调。
mAIGCService.stop();
8. 销毁 AIGCService
成功停止服务后调用 destroy
销毁服务实例。
AIGCService.destroy();
成功销毁后,会收到 onEventResult(DESTROY, SUCEESS)
回调。
API 时序图
实现 AI 语音助手的 API 调用时序图如下。其中,实现步骤需要用到如下关键类:
AIGCService
类:提供 AIGC 服务的核心类。AIGCServiceCallback
类:AIGCService
异步方法的事件回调类。
相关文档
开发过程中,你还可以参考如下文档:
如果你选择使用声网的 RTC SDK 来实现原始音频数据采集和播放功能,也可以参考: