手动控制说话开始和结束
当你的业务场景不适合依赖服务端自动判定用户何时开始说话或结束说话时,可以使用手动 SoS/EoS 能力,由客户端显式声明用户回合边界。该能力适用于 AI 面试、互动答题、对讲机模式等需要“按下开始、点击提交”的交互。本文介绍如何通过创建智能体配置和客户端组件接口,实现手动控制用户说话边界。
集成流程
手动轮次控制需要同时配置智能体启动参数和客户端组件:
启动 Agent 时配置 manual 模式
→ 客户端初始化 toolkit 并订阅 RTM 消息
→ 注册手动 SoS/EoS 结果回调
→ 用户点击开始/提交时调用 manualSOS/manualEOS
选择控制模式
start_of_speech.mode 和 end_of_speech.mode 是两个独立配置。先根据交互选择需要手动控制的边界:
| 业务场景 | start_of_speech.mode | end_of_speech.mode | 客户端调用 |
|---|---|---|---|
| 用户可自由开始说话,但需要点击“提交答案” | vad | manual | 只调用 manualEOS |
| 用户点击或按下后开始说话,结束由服务端自动判停 | manual | vad | 只调用 manualSOS |
| 按住说话、松开发送、对讲机模式 | manual | manual | 先调用 manualSOS,再调用 manualEOS |
如果只把 end_of_speech.mode 设为 manual,则服务端仍会通过 VAD 自动识别用户开始说话;业务只需要在用户点击提交时调用 manualEOS。
如果只把 start_of_speech.mode 设为 manual,则服务端只会在客户端调用 manualSOS 后开始接收本轮用户音频;结束说话仍由 VAD 自动识别,业务不需要调用 manualEOS。
如果同时把 start_of_speech.mode 和 end_of_speech.mode 设为 manual,则服务端只会在客户端调用 manualSOS 后开始接收本轮用户音频,并在客户端调用 manualEOS 后提交本轮发言。
前提条件
开始前,请确保完成以下准备工作:
- 已集成 RTC SDK v4.5.1 及以上版本,且已在 App 中实现基本的实时音视频功能并获取相关设备权限。请参考实现音视频互动。
- 已在控制台为项目启用 RTM 服务,并在 App 中实现基本的实时消息功能。请参考实现收发消息。
- 已参考实现对话式智能体实现与智能体对话的基本逻辑。
- 确保 RTC 可用、RTM 已登录,且 RTC 和 RTM 实例的生命周期大于组件的生命周期。组件内部不负责维护 RTC,RTM 的初始化、生命周期以及鉴权/登录状态的逻辑。
实现手动控制用户说话开始和结束
创建智能体时开启手动轮次控制
调用 POST 创建对话式智能体 接口时,开启 RTM,并根据业务场景将需要手动控制的一侧设为 manual。
- 仅手动控制 EoS
- 仅手动控制 SoS
- 手动控制 SoS 和 EoS
如果你只需要手动控制结束说话,可以保留自动 SoS,仅将 end_of_speech.mode 设为 manual。
{
"name": "manual-eos-agent",
"properties": {
"channel": "channel_name",
"token": "token",
"agent_rtc_uid": "0",
"remote_rtc_uids": ["123"],
"advanced_features": {
"enable_rtm": true
},
"turn_detection": {
"mode": "default",
"config": {
"start_of_speech": {
"mode": "vad",
"vad_config": {
"interrupt_duration_ms": 160,
"speaking_interrupt_duration_ms": 320,
"prefix_padding_ms": 800
}
},
"end_of_speech": {
"mode": "manual"
}
}
},
"parameters": {
"data_channel": "rtm"
}
}
}
如果你只需要手动控制开始说话,可以将 start_of_speech.mode 设为 manual,并保留自动 EoS。
{
"name": "manual-sos-agent",
"properties": {
"channel": "channel_name",
"token": "token",
"agent_rtc_uid": "0",
"remote_rtc_uids": ["123"],
"advanced_features": {
"enable_rtm": true
},
"turn_detection": {
"mode": "default",
"config": {
"start_of_speech": {
"mode": "manual"
},
"end_of_speech": {
"mode": "vad",
"vad_config": {
"silence_duration_ms": 480
}
}
}
},
"parameters": {
"data_channel": "rtm"
}
}
}
如果你需要完整的对讲机模式,则同时将 start_of_speech.mode 和 end_of_speech.mode 设为 manual。
{
"name": "manual-sos-eos-agent",
"properties": {
"channel": "channel_name",
"token": "token",
"agent_rtc_uid": "0",
"remote_rtc_uids": ["123"],
"advanced_features": {
"enable_rtm": true
},
"turn_detection": {
"mode": "default",
"config": {
"start_of_speech": {
"mode": "manual"
},
"end_of_speech": {
"mode": "manual"
}
}
},
"parameters": {
"data_channel": "rtm"
}
}
}
集成并初始化客户端组件
先完成组件集成:
- Android
- iOS
- Web
- Maven
- 源码集成
在 Android 项目的 build.gradle 文件中添加如下依赖:
implementation 'io.agora.agents:agora-agent-client-toolkit:2.9.0'
将 convoaiApi 文件夹拷贝到你的项目中,并在后续调用组件 API 前引入组件。
- CocoaPods
- Swift Package Manager
- 源码集成
在 Podfile 中添加如下依赖:
pod 'agent-client-toolkit-swift', '2.9.0'
在 Xcode 中添加如下 Package URL,并将版本指定为 tag 2.9.0:
https://github.com/AgoraIO/agent-client-toolkit-swift.git
将 ConversationalAIAPI 文件夹拷贝到你的项目中,并在后续调用组件 API 前引入组件。
- Package Manager
- 源码集成
根据你的项目类型安装组件依赖。
-
Vanilla JS / TypeScript:
Shellpnpm add agora-agent-client-toolkit@2.9.0 -
React:
Shellpnpm add agora-agent-client-toolkit@2.9.0 agora-agent-client-toolkit-react@2.9.0
将 conversational-ai-api 文件夹拷贝到你的项目中,并在后续调用组件 API 前引入组件。
如果在 Android 或 iOS 项目中同时集成多个声网 SDK 时出现库冲突,请参考多 SDK 库冲突处理。
然后使用已有的 RTC 和 RTM 实例初始化客户端组件。
- Android
- iOS
- Web
val config = ConversationalAIAPIConfig(
rtcEngine = rtcEngine,
rtmClient = rtmClient,
renderMode = TranscriptRenderMode.Word,
enableLog = true,
enableRenderModeFallback = true
)
val api = ConversationalAIAPIImpl(config)
let config = ConversationalAIAPIConfig(
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
renderMode: .words,
enableLog: true,
enableRenderModeFallback: true
)
convoAIAPI = ConversationalAIAPIImpl(config: config)
const config: IConversationalAIAPIConfig = {
rtcEngine: rtcEngine,
rtmEngine: rtmEngine,
renderMode: ETranscriptHelperMode.WORD,
enableLog: true,
enableRenderModeFallback: true,
};
const conversationalAIAPI = await ConversationalAIAPI.init(config);
注册消息回调并订阅频道消息
手动 SoS/EoS 的处理结果通过专用回调或事件返回,你需要先注册这些回调或事件,再订阅智能体所在频道的消息:
- Android
- iOS
- Web
api.addHandler(object : IConversationalAIAPIEventHandler {
override fun onUserManualSosEvent(agentUserId: String, event: UserManualSosEvent) {
// 处理手动 SOS 结果。event.payload.success 表示服务端是否接受。
}
override fun onUserManualEosEvent(agentUserId: String, event: UserManualEosEvent) {
// 处理手动 EOS 结果。
}
override fun onAgentManualEosEvent(agentUserId: String, event: AgentManualEosEvent) {
// 处理服务端因超限等原因触发的自动 EOS 通知。
}
})
api.subscribeMessage("channelName") { error ->
if (error != null) {
// 处理订阅失败
}
}
convoAIAPI.addHandler(handler: self)
func onUserManualSosEvent(agentUserId: String, event: UserManualSosEvent) {
// 处理手动 SOS 结果。event.payload.success 表示服务端是否接受。
}
func onUserManualEosEvent(agentUserId: String, event: UserManualEosEvent) {
// 处理手动 EOS 结果。
}
func onAgentManualEosEvent(agentUserId: String, event: AgentManualEosEvent) {
// 处理服务端因超限等原因触发的自动 EOS 通知。
}
convoAIAPI.subscribeMessage(channelName: channelName) { error in
if let error = error {
print("订阅失败: \(error.message)")
}
}
import {
AgoraVoiceAIEvents,
type UserManualSosEvent,
type UserManualEosEvent,
type AgentManualEosEvent,
} from 'agora-agent-client-toolkit'
conversationalAIAPI.on(
AgoraVoiceAIEvents.USER_MANUAL_SOS,
(agentUserId: string, event: UserManualSosEvent) => {
// 处理手动 SOS 结果。event.payload.success 表示服务端是否接受。
}
)
conversationalAIAPI.on(
AgoraVoiceAIEvents.USER_MANUAL_EOS,
(agentUserId: string, event: UserManualEosEvent) => {
// 处理手动 EOS 结果。
}
)
conversationalAIAPI.on(
AgoraVoiceAIEvents.AGENT_MANUAL_EOS,
(agentUserId: string, event: AgentManualEosEvent) => {
// 处理服务端因超限等原因触发的自动 EOS 通知。
}
)
conversationalAIAPI.subscribeMessage(channelName)
这些回调用于接收服务端对手动 SoS/EoS 请求的处理结果。你可以根据回调中的 event、requestId 和错误信息实现自己的业务逻辑。如需查看相关接口和事件说明,请参考客户端组件 API。
让智能体加入频道
创建智能体后,让智能体加入与你的用户相同的 RTC 频道:
调用 POST 创建对话式智能体接口,并完成以下参数设置:
advanced_features.enable_rtm: true—— (必选)启动 RTM 服务parameters.data_channel: "rtm"—— (必选)开启 RTM 数据传输通道parameters.enable_metrics: true—— (按需开启)接收智能体性能数据parameters.enable_error_message: true—— (按需开启)接收智能体错误事件
调用成功后,智能体会加入指定 RTC 频道,用户可以开始与智能体互动。
在用户开始说话时发送手动 SoS
仅当 start_of_speech.mode = "manual" 时需要调用 manualSOS。如果你使用 VAD 自动 SoS,可以跳过这一步。
- Android
- iOS
- Web
api.manualSOS(agentUserId = "agentUserId") { requestId, error ->
if (error != null) {
Log.e("ManualSOS", "发送失败: ${error.errorMessage}, requestId=$requestId")
} else {
Log.i("ManualSOS", "已发送 SOS, requestId=$requestId")
}
}
convoAIAPI.manualSOS(agentUserId: agentUid) { requestId, error in
if let error = error {
print("发送失败: \(error.message), requestId=\(requestId)")
} else {
print("已发送 SOS, requestId=\(requestId)")
}
}
const requestId = await conversationalAIAPI.manualSOS(
agentUserId,
'sos-req-20260612-001'
)
手动 SoS 生效后,服务端开始把后续音频计入本轮用户发言。在此之前到达的音频不会计入本轮。
在用户结束说话时发送手动 EoS
仅当 end_of_speech.mode = "manual" 时需要调用 manualEOS。如果你使用 VAD 自动 EoS,可以跳过这一步。
- Android
- iOS
- Web
api.manualEOS(agentUserId = "agentUserId") { requestId, error ->
if (error != null) {
Log.e("ManualEOS", "发送失败: ${error.errorMessage}, requestId=$requestId")
} else {
Log.i("ManualEOS", "已发送 EOS, requestId=$requestId")
}
}
convoAIAPI.manualEOS(agentUserId: agentUid) { requestId, error in
if let error = error {
print("发送失败: \(error.message), requestId=\(requestId)")
} else {
print("已发送 EOS, requestId=\(requestId)")
}
}
const requestId = await conversationalAIAPI.manualEOS(
agentUserId,
'eos-req-20260612-001'
)
手动 EoS 生效后,服务端会按现有链路提交 ASR、触发 LLM 推理,并在后续生成回复。该信号只表示“用户当前发言段结束”,不等同于整个 turn 立即结束。
销毁组件实例
结束 AI 对话场景后或关闭 App 前,你需要销毁组件实例,以释放组件的所有资源。
- Android
- iOS
- Web
api.destroy()
convoAIAPI.destroy()
conversationalAIAPI.destroy()
API 参考
客户端组件 API
- Android
- iOS
- Web