基于ARTC SDK集成方案

更新时间: 2025-02-12 14:37:26

本文将介绍如何基于ARTC SDK实现AI实时互动的解决方案。

方案介绍

方案基于ARTC SDK搭建RTC网络,通过调用AI实时互动接口实现实时互动能力,该方案为您提供了高效且灵活的集成路径。您能够根据自身需求选择合适的API接口进行功能定制,从而实现智能对话、情感分析、撮合助手、数字人直播等多样化的人工智能互动体验。此外,该方案还支持对现有功能的二次开发和扩展,帮助您更好地适应不断变化的应用场景,提高用户体验。

方案架构

用户与AI智能体基于ARTC通话系统如下图所示:

image

集成智能体

前提条件

  1. 完成智能体创建,详细请参见:AI实时互动快速入门

  2. App需要集成ARTC SDK,具体集成方式参考快速开始实时音视频

开启与关闭通话

开启与AI智能体的通话流程如下:

  1. 用户通过应用程序向AppServer发起请求开始通话并且获取RTC Token。

  2. AppServer接收到请求后,调用StartAIAgentInstance - 启动智能体实例接口启动智能体,并且按照Token鉴权规则生成RTC Token。

  3. AppServer将调用结果以及RTC Token返回给用户。

  4. 用户使用ARTC SDK通过RTC Token加入对应RTC频道,开启与智能体的通话。

关闭与AI智能体的通话流程如下:

  1. 用户请求AppServer关闭通话流程,AppServer调用StopAIAgentInstance - 停止智能体实例接口停止AI智能体。

  2. 用户使用ARTC SDK退出频道结束会话。

image

开发参考

推荐您参考AICallKit集成方案中的功能进行实现。客户端示例代码,请参考Android端源码iOS端源码,服务端示例代码,请参考Server源码

功能实现

说明

以下功能均在音视频通话智能体集成中实现,并且该方案也支持您正常使用ARTC SDK的相关接口。

智能体状态

前提条件

解析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自定义消息通道中的消息体进行解析,来获取实时字幕内容。系统内置消息字段如下:

字段名称

描述

type

消息类型:

  • type=1002,表示AI智能体实时字幕

  • type=1003,表示终端用户实时字幕

  • type=1011,表示向智能体发送的自定义消息

senderId

发送消息的智能体Id

receiverId

接收人的UserId,在这里为空字符串

data

具体的消息内容,无内容该字段可以为空

text

  • type=1002时,表示AI智能体生成的具体文本

  • type=1003时,表示终端用户说话识别出的具体文本

end

  • type=1002时,智能体回答较长时,字幕会拆分为多次发送,当end=true时表示是这次回答的最后一句

  • type=1003时:

    • end=true,表示文本为这句话的最终识别结果

    • end=false,表示文本在不断识别中的中间结果

sentenceId

  • type=1002时,表示回应对应sentenceId语音输入的LLM内容

  • type=1003时,表示当前文本属于的句子ID

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();
    }
}

对讲机模式

前提条件

交互流程

image

消息协议说明

开启/关闭对讲机模式

消息类型: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}
})
上一篇: 百炼✖AI实时互动最佳实践 下一篇: RTC纯通道接入方案