基于ARTC SDK集成方案
本文将介绍如何基于ARTC SDK实现AI实时互动的解决方案。
方案介绍
方案基于ARTC SDK搭建RTC网络,通过调用AI实时互动接口实现实时互动能力,该方案为您提供了高效且灵活的集成路径。您能够根据自身需求选择合适的API接口进行功能定制,从而实现智能对话、情感分析、撮合助手、数字人直播等多样化的人工智能互动体验。此外,该方案还支持对现有功能的二次开发和扩展,帮助您更好地适应不断变化的应用场景,提高用户体验。
方案架构
用户与AI智能体基于ARTC通话系统如下图所示:
集成智能体
前提条件
完成智能体创建,详细请参见:AI实时互动快速入门。
App需要集成ARTC SDK,具体集成方式参考快速开始实时音视频。
开启与关闭通话
开启与AI智能体的通话流程如下:
用户通过应用程序向AppServer发起请求开始通话并且获取RTC Token。
AppServer接收到请求后,调用StartAIAgentInstance - 启动智能体实例接口启动智能体,并且按照Token鉴权规则生成RTC Token。
AppServer将调用结果以及RTC Token返回给用户。
用户使用ARTC SDK通过RTC Token加入对应RTC频道,开启与智能体的通话。
关闭与AI智能体的通话流程如下:
用户请求AppServer关闭通话流程,AppServer调用StopAIAgentInstance - 停止智能体实例接口停止AI智能体。
用户使用ARTC SDK退出频道结束会话。
开发参考
推荐您参考AICallKit集成方案中的功能进行实现。客户端示例代码,请参考Android端源码、iOS端源码,服务端示例代码,请参考Server源码。
功能实现
以下功能均在音视频通话智能体集成中实现,并且该方案也支持您正常使用ARTC SDK的相关接口。
智能体状态
前提条件
已开通实时音视频应用。更多信息,请参见快速开始实时音视频。
已打开RTC自定义消息通道。更多信息,请参见自定义消息发送和接收。
解析RTC自定义消息
智能体有聆听中、思考中、讲话中三种状态码。您可以对RTC自定义消息通道中的消息体进行解析,来获取智能体的状态码。系统内置消息字段如下:
字段名称 | 描述 |
type | 消息类型 |
senderId | 发送消息的UserId |
receiverId | 接收人的UserId,如果指定消息是群发,则为空字符串 |
data | 具体的消息内容,无内容该字段可以为空 |
state | 智能体状态码 |
消息体如下:
{
"type": 1001,
"data": {
"state": 1, // 1:聆听中 2:思考中 3:讲话中
}
"senderId": "robot_1", // 发送人id
"receiverId": "" // 无需指定接收人Id,一般情况下为空
}
iOS
从RTC自定义消息通道中获取智能体状态的示例代码如下:
public func onDataChannelMessage(_ uid: String, controlMsg: AliRtcDataChannelMsg) {
if controlMsg.type != .custom {
return
}
let dataDict = (try? JSONSerialization.jsonObject(with: controlMsg.data, options: .allowFragments)) as? [String : Any]
guard let dataDict = dataDict else {
return
}
debugPrint("onDataChannelMessage:\(dataDict)")
if dataDict["type"] as? Int32 == 1001 {
let senderId = dataDict["senderId"] as? String
let receiverId = dataDict["receiverId"] as? String
let data = dataDict["data"] as? [String: Any]
if let state = data?["state"] as? Int32 {
DispatchQueue.main.async {
// 更新你的UI状态
debugPrint("Received Robot State Changed: \(state)")
}
}
}
}
Android
从RTC自定义消息通道中获取智能体状态的示例代码如下:
aliRtcEngine.setRtcEngineNotify( new AliRtcEngineNotify() {
@Override
public void onDataChannelMessage(String uid, AliRtcEngine.AliRtcDataChannelMsg msg) {
super.onDataChannelMessage(uid, msg);
if (msg.type == AliEngineDataMsgCustom) {
try {
String dataStr = new String(msg.data);
JSONObject jsonObject = new JSONObject(dataStr);
int msgType = jsonObject.optInt("type");
String senderId = jsonObject.optString("senderId");
String receiverId = jsonObject.optString("receiverId");
JSONObject dataJson = jsonObject.optJSONObject("data");
if (null != dataJson) {
if (msgType == 1001) {
int robotState = dataJson.optInt("state");
ARTCAICallRobotState artcaiCallRobotState = null;
if (robotState == IMsgTypeDef.ROBOT_STATE.ROBOT_STATE_LISTENING) {
// TODO 机器人聆听状态处理
} else if (robotState == IMsgTypeDef.ROBOT_STATE.ROBOT_STATE_THINKING) {
// TODO 机器人思考状态处理
} else if (robotState == IMsgTypeDef.ROBOT_STATE.ROBOT_STATE_SPEAKING) {
// TODO 机器人说话状态处理
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});
实时字幕
前提条件
已开通实时音视频应用。更多信息,请参见快速开始实时音视频。
已打开RTC自定义消息通道。更多信息,请参见自定义消息发送和接收。
解析RTC自定义消息
您需要对RTC自定义消息通道中的消息体进行解析,来获取实时字幕内容。系统内置消息字段如下:
字段名称 | 描述 |
type | 消息类型:
|
senderId | 发送消息的智能体Id |
receiverId | 接收人的UserId,在这里为空字符串 |
data | 具体的消息内容,无内容该字段可以为空 |
text |
|
end |
|
sentenceId |
|
AI机器人实时字幕消息体如下:
{
"type": 1002,
"senderId": "robot_1", // 发送消息的智能体id
"receiverId": "", // 无需指定接收人Id,一般情况下为空
"data": {
"text": "这是AI机器人产生的文本内容", // AI智能体生成的具体文本
"end": false, // 这次返回的文本是否是最后一句
"sentenceId": 1 // 表示回应对应sentenceId语音输入的llm内容
}
}
终端用户实时字幕消息体如下:
{
"type": 1003,
"senderId": "robot_1", // 发送消息的智能体id
"receiverId": "", // 无需指定接收人Id,一般情况下为空
"data": {
"text": "这是终端用户说话识别到的目前文本内容", // 终端用户说话识别出的具体文本
"end": false, // 当前文本是否为这句话的最终结果
"sentenceId": 1 // 当前文本属于的句子ID
}
}
iOS
从RTC自定义消息通道中获取实时字幕的示例代码如下:
private var lastRobotSentenceId: Int? = nil
private var robotSpeakingText: String? = nil
public func onDataChannelMessage(_ uid: String, controlMsg: AliRtcDataChannelMsg) {
if controlMsg.type != .custom {
return
}
let dataDict = (try? JSONSerialization.jsonObject(with: controlMsg.data, options: .allowFragments)) as? [String : Any]
guard let dataDict = dataDict else {
return
}
debugPrint("onDataChannelMessage:\(dataDict)")
if dataDict["type"] as? Int32 == 1002 {
let senderId = dataDict["senderId"] as? String
let receiverId = dataDict["receiverId"] as? String
let data = dataDict["data"] as? [String: Any]
if let data = data {
let text = data["text"] as? String
let end = data["end"] as? Bool
let sentenceId = data["sentenceId"] as? Int
if sentenceId == lastRobotSentenceId {
self.robotSpeakingText?.append(text ?? "")
}
else {
self.lastRobotSentenceId = sentenceId
self.robotSpeakingText = text ?? ""
}
if end == true {
DispatchQueue.main.async {
debugPrint("Received Robot Speaking Text: \(self.robotSpeakingText!)")
// 更新你的UI状态
}
}
}
if let text = data?["text"] as? String {
DispatchQueue.main.async {
// 更新你的UI状态
debugPrint("Received Robot Speaking Text: \(text)")
}
}
}
else if dataDict["type"] as? Int32 == 1003 {
let senderId = dataDict["senderId"] as? String
let receiverId = dataDict["receiverId"] as? String
let data = dataDict["data"] as? [String: Any]
if let text = data?["text"] as? String {
if data?["end"] as? Bool == true {
DispatchQueue.main.async {
debugPrint("Received ASR Text: \(text)")
// 更新你的UI状态
}
}
}
}
}
Android
从RTC自定义消息通道中获取实时字幕的示例代码如下:
private int mSentenceId = -1;
private String mRobotSpeakingText = "";
aliRtcEngine.setRtcEngineNotify( new AliRtcEngineNotify() {
@Override
public void onDataChannelMessage(String uid, AliRtcEngine.AliRtcDataChannelMsg msg) {
super.onDataChannelMessage(uid, msg);
if (msg.type == AliEngineDataMsgCustom) {
try {
String dataStr = new String(msg.data);
JSONObject jsonObject = new JSONObject(dataStr);
int msgType = jsonObject.optInt("type");
String senderId = jsonObject.optString("senderId");
String receiverId = jsonObject.optString("receiverId");
JSONObject dataJson = jsonObject.optJSONObject("data");
if (null != dataJson) {
if (msgType == 1002) {
// 机器人的话
String text = dataJson.optString("text");
// 当前文本是否为这句话的最终结果
boolean end = dataJson.optBoolean("end");
// 表示回应对应sentenceId语音输入的llm内容
int sentenceId = dataJson.optInt("sentenceId");
if (sentenceId == mSentenceId) {
mRobotSpeakingText += text;
}
else {
mRobotSpeakingText = text;
mSentenceId = sentenceId;
}
if (end) {
System.out.println(mRobotSpeakingText);
// TODO 机器人字幕显示
}
} else if (msgType == 1003) {
// ASR识别出的具体文本
String text = dataJson.optString("text");
// 当前文本是否为这句话的最终结果
boolean end = dataJson.optBoolean("end");
// 当前文本属于的句子ID
int sentenceId = dataJson.optInt("sentenceId");
if (end) {
System.out.println(text);
// TODO ASR字幕显示
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});
打断智能体讲话
发送打断信息
打断信息通过RTC自定义消息通道进行发送,您需要先开通实时音视频应用,以及打开RTC自定义消息通道设置。更多信息,请参见快速开始实时音视频、自定义消息发送和接收。
打断消息中需要含有以下字段:
字段名称 | 描述 |
type | 消息类型 |
senderId | 发送消息的UserId |
receiverId | 接收的智能体Id |
打断信息如下:
{
"type": 1101,
"senderId": "user_1", // 发送人id
"receiverId": "robot_1" // 智能体Id
}
iOS
终端RTC SDK通过自定义消息通道发送打断智能体讲话指令的示例代码如下:
var sendDict: [String: Any] = [
"type": 1101,
]
sendDict.updateValue(myUid, forKey: "senderId")
sendDict.updateValue(robotUid, forKey: "receiverId")
if let sendData = sendDict.aicall_jsonString.data(using: .utf8) {
let rtcMsg = AliRtcDataChannelMsg()
rtcMsg.type = .custom
rtcMsg.data = sendData
self.rtcEngine.sendDataChannelMessage(rtcMsg)
}
Android
终端RTC SDK通过自定义消息通道发送打断智能体讲话指令的示例代码如下:
int msgType = 1101;
// 用户入会之后的userId
String senderId = "myRtcUserId";
// 机器人入会之后的userId
String receiverId = "robotRtcUserId";
if (null != mAliRtcEngine) {
try {
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", msgType);
jsonObject.put("senderId", senderId);
jsonObject.put("receiverId", receiverId);
AliRtcEngine.AliRtcDataChannelMsg rtcDataChannelMsg = new AliRtcEngine.AliRtcDataChannelMsg();
rtcDataChannelMsg.type = AliEngineDataMsgCustom;
rtcDataChannelMsg.data = jsonObject.toString().getBytes(StandardCharsets.UTF_8);
mAliRtcEngine.sendDataChannelMsg(rtcDataChannelMsg);
} catch (JSONException ex) {
ex.printStackTrace();
}
}
对讲机模式
前提条件
已开通实时音视频应用。更多信息,请参见快速开始实时音视频。
已打开RTC自定义消息通道。更多信息,请参见自定义消息发送和接收。
交互流程
消息协议说明
开启/关闭对讲机模式
消息类型:1105
消息体如下:
{
"type": 1105,
"senderId": "user_1", // 发送人id
"receiverId": "robot_1", // 机器人Id
"data": {
"enable": true // true表示开启对讲机模式,false表示关闭对讲机模式
}
}
对讲机开启/关闭状态传递
消息类型:1007
消息体如下:
{
"type": 1007,
"seqId": 5,
"senderId": "robot_1", // AI智能体uid
"receiverId": "", // 无需指定接收人Id,一般情况下为空
"data": {
"enable": true // true表示当前智能体进入对讲机模式,false当前智能体退出了对讲机模式
}
}
无论是OpenAPI,还是通过DataChannel开启/关闭对讲机模式,智能体服务都会发送该消息告诉结果。
按下开始讲话
消息类型:1106
消息体如下:
{
"type": 1106,
"senderId": "user_1", // 发送人id
"receiverId": "robot_1" // 机器人Id
}
松开发送讲话
消息类型:1107
消息体如下:
{
"type": 1107,
"senderId": "user_1", // 发送人id
"receiverId": "robot_1" // 机器人Id
}
松开取消讲话
消息类型:1108
消息体如下:
{
"type": 1108,
"senderId": "user_1", // 发送人id
"receiverId": "robot_1" // 机器人Id
}
代码示例
iOS
// 启动或关闭对讲机模式
public func enablePushToTalk(enable: Bool) {
var sendDict: [String: Any] = [
"type": 1105,
]
sendDict.updateValue(myUid, forKey: "senderId")
sendDict.updateValue(robotUid, forKey: "receiverId")
sendDict.updateValue(["enable": enable], forKey: "data")
if let sendData = sendDict.aicall_jsonString.data(using: .utf8) {
let rtcMsg = AliRtcDataChannelMsg()
rtcMsg.type = .custom
rtcMsg.data = sendData
self.rtcEngine.sendDataChannelMessage(rtcMsg)
self.rtcEngine.muteLocalMic(enable, mode: .allAudioMode)
}
}
// 按住讲话,请确保已经启动对讲机模式
public func startPushToTalk() {
var sendDict: [String: Any] = [
"type": 1106,
]
sendDict.updateValue(myUid, forKey: "senderId")
sendDict.updateValue(robotUid, forKey: "receiverId")
if let sendData = sendDict.aicall_jsonString.data(using: .utf8) {
let rtcMsg = AliRtcDataChannelMsg()
rtcMsg.type = .custom
rtcMsg.data = sendData
self.rtcEngine.sendDataChannelMessage(rtcMsg)
// 取消静音麦克风
self.rtcEngine.muteLocalMic(false, mode: .allAudioMode)
}
}
// 完成讲话,请确保已经启动对讲机模式
public func finishPushToTalk() {
var sendDict: [String: Any] = [
"type": 1107,
]
sendDict.updateValue(myUid, forKey: "senderId")
sendDict.updateValue(robotUid, forKey: "receiverId")
if let sendData = sendDict.aicall_jsonString.data(using: .utf8) {
let rtcMsg = AliRtcDataChannelMsg()
rtcMsg.type = .custom
rtcMsg.data = sendData
self.rtcEngine.sendDataChannelMessage(rtcMsg)
// 取消静音麦克风
self.rtcEngine.muteLocalMic(true, mode: .allAudioMode)
}
}
// 取消这次发送,请确保已经启动对讲机模式
public func cancelPushToTalk() {
var sendDict: [String: Any] = [
"type": 1108,
]
sendDict.updateValue(myUid, forKey: "senderId")
sendDict.updateValue(robotUid, forKey: "receiverId")
if let sendData = sendDict.aicall_jsonString.data(using: .utf8) {
let rtcMsg = AliRtcDataChannelMsg()
rtcMsg.type = .custom
rtcMsg.data = sendData
self.rtcEngine.sendDataChannelMessage(rtcMsg)
// 取消静音麦克风
self.rtcEngine.muteLocalMic(true, mode: .allAudioMode)
}
}
// 处理接收到的DataChannel消息
public func onDataChannelMessage(_ uid: String, controlMsg: AliRtcDataChannelMsg) {
if controlMsg.type != .custom {
return
}
let dataDict = (try? JSONSerialization.jsonObject(with: controlMsg.data, options: .allowFragments)) as? [String : Any]
guard let dataDict = dataDict else {
return
}
debugPrint("onDataChannelMessage:\(dataDict)")
if dataDict["type"] as? Int32 == 1007 {
let data = dataDict["data"] as? [String: Any]
if let data = data {
if let enable = data["enable"] as? Bool {
// 服务端已经开启/关闭对讲机模式,参考流程
DispatchQueue.main.async {
self.enablePushToTalk = enable
self.rtcEngine.muteLocalMic(enable, mode: .allAudioMode)
}
}
}
}
// 处理其他消息
...
}
Android
public void sendCustomMessage(int msgType, JSONObject data) {
if (null != mAliRtcEngine) {
try {
String senderId; // 自己的用户id
String receiverId; // 机器人用户id
JSONObject jsonObject = new JSONObject();
jsonObject.put("type", msgType);
jsonObject.put("senderId", senderId);
jsonObject.put("receiverId", receiverId);
if (null != data) {
jsonObject.put("data", data);
}
AliRtcEngine.AliRtcDataChannelMsg rtcDataChannelMsg = new AliRtcEngine.AliRtcDataChannelMsg();
rtcDataChannelMsg.type = AliEngineDataMsgCustom;
rtcDataChannelMsg.data = jsonObject.toString().getBytes(StandardCharsets.UTF_8);
mAliRtcEngine.sendDataChannelMsg(rtcDataChannelMsg);
} catch (JSONException ex) {
ex.printStackTrace();
}
}
}
// 启动或关闭对讲机模式
public boolean enablePushToTalk(boolean enable) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("enable", enable);
// 向机器人发送消息
sendCustomMessage(1105, jsonObject);
// 静音麦克风
mAliRtcEngine.muteLocalMic(enable, AliRtcEngine.AliRtcMuteLocalAudioMode.AliRtcMuteAllAudioMode);
}
// 按住讲话,请确保已经启动对讲机模式
public func startPushToTalk() {
// 向机器人发送消息
sendCustomMessage(1106, null);
// 取消静音麦克风
mAliRtcEngine.muteLocalMic(false, AliRtcEngine.AliRtcMuteLocalAudioMode.AliRtcMuteAllAudioMode);
}
// 完成讲话,请确保已经启动对讲机模式
public func finishPushToTalk() {
// 向机器人发送消息
sendCustomMessage(1107, null);
// 静音麦克风
mAliRtcEngine.muteLocalMic(true, AliRtcEngine.AliRtcMuteLocalAudioMode.AliRtcMuteAllAudioMode);
}
// 取消这次发送,请确保已经启动对讲机模式
public func cancelPushToTalk() {
// 向机器人发送消息
sendCustomMessage(1108, null);
// 静音麦克风
mAliRtcEngine.muteLocalMic(true, AliRtcEngine.AliRtcMuteLocalAudioMode.AliRtcMuteAllAudioMode);
}
// 处理接收到的DataChannel消息
private AliRtcEngineNotify mRtcEngineRemoteNotify = new AliRtcEngineNotify() {
@Override
public void onDataChannelMessage(String uid, AliRtcEngine.AliRtcDataChannelMsg msg) {
super.onDataChannelMessage(uid, msg);
if (msg.type == AliEngineDataMsgCustom) {
String dataStr = new String(msg.data);
JSONObject jsonObject = new JSONObject(dataStr);
JSONObject dataJson = jsonObject.optJSONObject("data");
int msgType = jsonObject.optInt("type");
if (msgType == 1007) {
boolean enable = dataJson.optBoolean("enable");
// 机器人通知对讲机模式状态,业务处理...
}
}
}
}
Web
// 启动或关闭对讲机模式
public enablePushToTalk(enable: boolean) { }
// 按住讲话,请确保已经启动对讲机模式
public startPushToTalk(): boolean { }
// 完成讲话,请确保已经启动对讲机模式
public finishPushToTalk(): boolean { }
// 取消这次发送,请确保已经启动对讲机模式
public cancelPushToTalk(): boolean { }
controller.on('AICallPushToTalkChanged', (enable: boolean) => {
// 当前对讲机模式变化为 {enable}
})