快速开始
本文档用于说明如何快速集成客户端 SDK (ZEGO Express SDK)并在您的业务后台调用 AI Agent 相关后台接口实现与 AI Agent 的语音互动。
前提条件
- 已在 ZEGO 控制台 创建项目,并申请有效的 AppID 和 AppSign,详情请参考 控制台 - 项目信息。
- 联系 ZEGO 技术支持开通 AI Agent 相关服务并获取 LLM 和 TTS 相关配置信息。
- 已经集成了支持 AI 降噪和 AI 回声消除的 ZEGO Express SDK。
- 已部署业务后台并实现调用 AI Agent 相关服务端 API(可参考服务端示例代码)。
文本转语音(TTS)服务供应商。可选:Bytedance:火山引擎。
详情请参考 TTS 参数说明文档。
示例代码
以下是实现核心能力所需要的示例代码,您可以参考示例代码来实现自己的业务逻辑。
iOS 客户端示例代码。包含最基本的登录、推流、拉流、退出房间等能力。
服务端示例代码。包含最基本的获取 ZEGO Token、注册智能体、创建智能体实例、删除智能体实例等能力。
整体业务流程图
您需要在客户端使用 ZEGO Express SDK 实现真实用户进入房间并推流。然后调用 AI Agent 提供的服务端 API 接口,实现将智能体加入房间并与真实用户进行实时互动。
核心能力实现
以下是初始化 ZEGO Express SDK 的关键步骤:
1 在 Info.plist 文件声明必要的权限
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
...
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风以进行语音聊天</string>
</dict>
</plist>
2 运行时申请录音权限
- (void)requestAudioPermission:(void(^)(BOOL granted))completion {
/// 需要在项目的 Info.plist 文件中添加麦克风权限的使用说明
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
[audioSession requestRecordPermission:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(granted);
});
}];
}
3 创建并初始化 ZegoExpressEngine
-(void)initZegoExpressEngine{
ZegoEngineProfile* profile = [[ZegoEngineProfile alloc]init];
profile.appID = kZegoPassAppId;
profile.scenario = ZegoScenarioHighQualityChatroom; //设置该场景可以避免申请相机权限,接入方应按自己的业务场景设置具体值
[ZegoExpressEngine createEngineWithProfile:profile eventHandler:self];
}
注册智能体用于设定智能体基础配置,包括智能体名称、LLM、TTS、ASR等相关配置。注册后可以该智能体作为模板创建多个实例与多个真实用户进行互动。
通常智能体是相对比较固定的,一旦设定好智能体的相关参数(人设形象)就不会经常改动。所以建议按照业务流程需要在初始化应用时或者其他其他步骤注册智能体即可。
以下是调用业务后台接口实现注册智能体的示例:
+ (void)registerAgentWithId:(NSString *)agentId name:(NSString *)agentName {
NSURL *url = [NSURL URLWithString:[kBaseUrl stringByAppendingString:@"/api/agent/register"]]; // kBaseUrl 为您的业务后台地址
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *body = @{
@"agent_id": agentId,
@"agent_name": agentName
};
NSError *error;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body
options:0
error:&error];
if (error) {
NSLog(@"JSON序列化错误: %@", error);
return;
}
[request setHTTPBody:bodyData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError) {
NSLog(@"JSON解析错误: %@", jsonError);
return;
}
NSLog(@"注册Agent成功: %@", json);
} else {
NSLog(@"请求失败,状态码: %ld", (long)httpResponse.statusCode);
}
}];
[task resume];
}
// 注意:process.env. 为从环境变量读取的值。请参考服务端示例代码的 .env.example 文件。
async registerAgent(agentId: string, agentName: string) {
if (!process.env.LLM_BASE_URL || !process.env.LLM_API_KEY || !process.env.LLM_MODEL) {
throw new Error('LLM_BASE_URL, LLM_API_KEY and LLM_MODEL environment variables must be set');
}
// 请求接口:https://aigc-aiagent-api.zegotech.cn?Action=RegisterAgent
// 文档说明:https://doc-zh.zego.im/aiagent-server/agent-configuration-management/register-agent
const action = 'RegisterAgent';
const body = {
AgentId: agentId,
Name: agentName,
LLM: {
Url: process.env.LLM_BASE_URL || "",
ApiKey: process.env.LLM_API_KEY || "",
Model: process.env.LLM_MODEL || "",
// SystemPrompt: SYSTEM_PROMPT
},
TTS: {
Vendor: "Bytedance",
Params: {
"app": {
"appid": process.env.TTS_BYTEDANCE_APP_ID || "",
"token": process.env.TTS_BYTEDANCE_TOKEN || "",
"cluster": process.env.TTS_BYTEDANCE_CLUSTER || ""
},
"speed_ratio": 1,
"volume_ratio": 1,
"pitch_ratio": 1,
"emotion": "happy",
"audio": {
"rate": 24000,
"voice_type": process.env.TTS_BYTEDANCE_VOICE_TYPE || ""
}
},
FilterText: [{ BeginCharacters: "(", EndCharacters: ")" }, { BeginCharacters: "(", EndCharacters: ")" }, { BeginCharacters: "{", EndCharacters: "}" }],
}
};
// sendRequest 方法封装了请求的 URL 和公共参数。详情参考:https://doc-zh.zego.im/aiagent-server/accessing-server-apis
return this.sendRequest<any>(action, body);
}
真实用户登录房间后推流。注意在此场景下需要开启 AI 降噪和 AI 回声消除以获得更好的效果。 登录用的 token 需要从业务后台获取,请参考完整示例代码。
请确保 roomID、userID、streamID 在一个 ZEGO APPID 下是唯一的。
- roomID: 由用户自己定义生成规则,会用来登录 Express SDK 的房间。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', ''。如果需要与 Web SDK 互通,请不要使用 '%'。
- userID: 长度不超过32字节。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', ''。如果需要与 Web SDK 互通,请不要使用 '%'。
- streamID: 长度不超过256字节。仅支持数字,英文字符 和 '-', '_'。
// 记录智能体
self.streamToPlay = [self getAgentStreamID];
ZegoEngineConfig* engineConfig = [[ZegoEngineConfig alloc] init];
engineConfig.advancedConfig = @{
@"set_audio_dump_mode":@1,//取消录制文件大小限制
@"notify_remote_device_unknown_status": @"true",
@"notify_remote_device_init_status":@"true",
@"enforce_audio_loopback_in_sync": @"true", /**该配置用来做应答延迟优化的,需要集成对应版本的ZegoExpressEngine sdk,请联系即构同学**/
@"set_audio_volume_ducking_mode":@1,/**该配置是用来做音量闪避的**/
@"enable_rnd_volume_adaptive":@"true",/**该配置是用来做播放音量自适用**/
};
[ZegoExpressEngine setEngineConfig:engineConfig];
//这个设置只影响AEC(回声消除),我们这里设置为ModeGeneral,是会走我们自研的回声消除,这比较可控,
//如果其他选项,可能会走系统的回声消除,这在iphone手机上效果可能会更好,但如果在一些android机上效果可能不好
[[ZegoExpressEngine sharedEngine] setAudioDeviceMode:ZegoAudioDeviceModeGeneral];
//请注意:开启 AI 降噪和 AI 回声消除需要联系 ZEGO 技术支持获取对应的ZegoExpressionEngine.xcframework,具备该能力的版本还未发布
[[ZegoExpressEngine sharedEngine] enableAGC:TRUE];
[[ZegoExpressEngine sharedEngine] enableAEC:TRUE];
[[ZegoExpressEngine sharedEngine] setAECMode:ZegoAECModeAIAggressive];
[[ZegoExpressEngine sharedEngine] enableANS:TRUE];
[[ZegoExpressEngine sharedEngine] setANSMode:ZegoANSModeAIBalanced];
// 登录房间
[self loginRoom:^(int errorCode, NSDictionary *extendedData) {
if (errorCode!=0) {
NSString* errorMsg =[NSString stringWithFormat:@"进入语音房间失败:%d", errorCode];
completion(NO, errorMsg);
return;
}
//进房后开始推流
[self startPushlishStream];
}];
可以用已注册的智能体为模板创建多个智能体实例加入不同房间与不同用户进行实时互动。创建智能体实例后,智能体实例会自动登录房间并推流,同时也会拉真实用户的流。
创建智能体实例成功后,真实用户监听流变化事件并拉流就可以与智能体进行实时互动了。
以下是调用业务后台接口实现创建智能体实例的示例:
+ (void)createAgentWithId:(NSString *)agentId
roomId:(NSString *)roomId
userId:(NSString *)userId
userStreamId:(NSString *)userStreamId
agentStreamId:(NSString *)agentStreamId
agentUserId:(NSString *)agentUserId {
NSURL *url = [NSURL URLWithString:[kBaseUrl stringByAppendingString:@"/api/agent/create"]]; // kBaseUrl 为您的业务后台地址
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *body = @{
@"agent_id": agentId,
@"room_id": roomId,
@"user_id": userId,
@"user_stream_id": userStreamId,
@"agent_stream_id": agentStreamId,
@"agent_user_id": agentUserId
};
NSError *error;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body
options:0
error:&error];
if (error) {
NSLog(@"JSON序列化错误: %@", error);
return;
}
[request setHTTPBody:bodyData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError) {
NSLog(@"JSON解析错误: %@", jsonError);
return;
}
NSLog(@"创建Agent实例成功: %@", json);
} else {
NSLog(@"请求失败,状态码: %ld", (long)httpResponse.statusCode);
}
}];
[task resume];
}
//监听房间流信息更新状态,拉取智能体流播放
- (void)onRoomStreamUpdate:(ZegoUpdateType)updateType
streamList:(NSArray<ZegoStream *> *)streamList
extendedData:(nullable NSDictionary *)extendedData
roomID:(NSString *)roomID{
if (updateType == ZegoUpdateTypeAdd) {
for (int i=0; i<streamList.count; i++) {
ZegoStream* item = [streamList objectAtIndex:i];
if ([item.streamID isEqualToString: self.streamToPlay]) {
NSLog(@"匹配到目标流,准备播放: streamID=%@", self.streamToPlay);
[self startPlayStream:self.streamToPlay];
break;
}
}
} else if(updateType == ZegoUpdateTypeDelete) {
for (int i=0; i<streamList.count; i++) {
ZegoStream* item = [streamList objectAtIndex:i];
[[ZegoExpressEngine sharedEngine] stopPlayingStream:item.streamID];
}
}
}
async createAgentInstance(agentId: string, userId: string, rtcInfo: RtcInfo, messages?: any[]) {
// 请求接口:https://aigc-aiagent-api.zegotech.cn?Action=CreateAgentInstance
// 文档说明:https://doc-zh.zego.im/aiagent-server/agent-instance-management/create-agent-instance
const action = 'CreateAgentInstance';
const body = {
AgentId: agentId,
UserId: userId,
RTC: rtcInfo,
MessageHistory: {
SyncMode: 1, // Change to 0 to use history messages from ZIM
Messages: messages && messages.length > 0 ? messages : [],
WindowSize: 10
}
};
// sendRequest 方法封装了请求的 URL 和公共参数。详情参考:https://doc-zh.zego.im/aiagent-server/accessing-server-apis
const result = await this.sendRequest<any>(action, body);
console.log("create agent instance result", result);
return result.AgentInstanceId;
}
恭喜你🎉!完成这一步骤后,您已经成功创建了一个智能体实例,并可以与真实用户进行实时互动了。您可以用语音问智能体任何问题,智能体都会用语音回答您的问题!
删除智能体实例后,智能体实例会自动退出房间并停止推流。用户再停止推流和退出房间后,一次完整的互动就结束了。
以下是调用业务后台接口实现删除智能体实例的示例:
// agentInstanceId 在创建智能体实例接口返回
// 3. 删除AI Agent实例
+ (void)deleteAgentWithInstanceId:(NSString *)agentInstanceId {
NSURL *url = [NSURL URLWithString:[kBaseUrl stringByAppendingString:@"/api/agent/delete"]]; // kBaseUrl 为您的业务后台地址
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
NSDictionary *body = @{
@"agent_instance_id": agentInstanceId
};
NSError *error;
NSData *bodyData = [NSJSONSerialization dataWithJSONObject:body
options:0
error:&error];
if (error) {
NSLog(@"JSON序列化错误: %@", error);
return;
}
[request setHTTPBody:bodyData];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200) {
NSError *jsonError;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:&jsonError];
if (jsonError) {
NSLog(@"JSON解析错误: %@", jsonError);
return;
}
NSLog(@"删除Agent实例成功: %@", json);
if ([ZegoExpressEngine sharedEngine] == nil) {
return;
}
[[ZegoExpressEngine sharedEngine] stopPlayingStream:self.streamToPlay];
[[ZegoExpressEngine sharedEngine] stopPublishingStream];
[[ZegoExpressEngine sharedEngine] logoutRoomWithCallback:nil];
[ZegoExpressEngine destroyEngine:nil];
} else {
NSLog(@"请求失败,状态码: %ld", (long)httpResponse.statusCode);
}
}];
[task resume];
}
async deleteAgentInstance(agentInstanceId: string) {
// 请求接口:https://aigc-aiagent-api.zegotech.cn?Action=DeleteAgentInstance
// 文档说明:https://doc-zh.zego.im/aiagent-server/agent-instance-management/delete-agent-instance
const action = 'DeleteAgentInstance';
const body = {
AgentInstanceId: agentInstanceId
};
// sendRequest 方法封装了请求的 URL 和公共参数。详情参考:https://doc-zh.zego.im/aiagent-server/accessing-server-apis
return this.sendRequest(action, body);
}
以上就是您实现与智能体进行实时语音互动的完整核心流程。