You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
开发指南
传入自定义文本让 AI 播报
复制全文
下载 pdf
传入自定义文本让 AI 播报
在与 AI 通话过程中,您可随时传入一段自定义文本,让 AI 直接合成语音并播报,并支持控制播报优先级(立即插播或等待后播报)、传入自定义业务信息。该功能可用于播放业务话术、提醒用户注意事项、延迟安抚、流程引导等场景。​
应用场景​
​
​
场景​
描述​
示例​
AI 陪练​
用户长时间不说话时, AI 播放内容吸引用户继续互动。​
"我注意到你有一会儿没说话了,需要我解释一下刚才的内容吗?"​
桌游主持​
AI 扮演主持人角色,引导用户完成游戏流程。​
“现在轮到玩家 A 发言,请开始。”​
安全监管​
用户问题或大模型生成内容不合规时,引导用户调整会话内容。​
“抱歉,对话内容不符合社区规范。”​
延迟安抚​
触发 Function Calling 或联网造成 AI 回答延迟偏大时,引导用户耐心等待。​
"正在处理,请稍等。”​
持续文本播报​
业务服务端将持续生成的外部文本(如外部 LLM 的流式回复、分段下发的业务话术等)通过多次请求依次传入 TTS,并通过自定义信息通知客户端当前这批流式文本的状态(如是否已全部分发完毕)。​
中间请求携带 {"finish": false},最后一次请求携带 {"finish": true}。​
​
实现方式​
注意​
建议传入完整分句或整句进行播放,高频送入碎片化文本可能会导致播放顺序错乱。​
通过服务端实现​
调用 UpdateVoiceChat 接口,设置 Command 为 ExternalTextToSpeech,传入要播报的内容。同时支持以下能力:​
  • 控制 AI 的播报优先级:通过 InterruptMode 字段设置播报优先级。1 表示立即打断当前交互并播放;2 表示等待当前交互结束后播放;3 表示低优先级,AI 正在交互时会丢弃该文本。​
  • 传入自定义业务信息:通过 AdvanceConfig.CustomInfo 字段传入业务自定义信息(key-value 格式)。系统会将该自定义信息随同一轮次的字幕通过 customInfo 原样透传给客户端或服务端。该字段支持 SubtitleMode 为 0、1 或 3 的字幕模式,字幕格式参见实时字幕(对话记录)。建议仅传入业务关联所需的信息,不要传入敏感数据,并控制对象大小,避免字幕消息过大。​
参数详细说明参见接口文档:UpdateVoiceChat(2025-06-01)。​
​
{​
"AppId": "<YOUR_AppId>", // 与 StartVoiceChat 的一致​
"RoomId": "<YOUR_RoomId>", // 与 StartVoiceChat 的一致​
"TaskId": "<YOUR_TaskId>", // 与 StartVoiceChat 的一致 ​
"Command": "ExternalTextToSpeech", // 必须为该值​
"Message": "你刚才的故事讲得真棒,能再讲一个吗。", // 要播报的文本内容,末尾必须包含标点符号,长度不超过 200 个字符。​
"InterruptMode": 1, // 可选:按需选择播放优先级,可为 1、2、3​
// 可选:传入业务自定义信息​
"AdvanceConfig": {​
"RoundID": 11, // 本次播报关联的对话轮次 ID​
"CustomInfo": { // 随该轮字幕原样透传的业务自定义信息​
"finish": true​
}​
}​
}​
​
通过客户端实现​
在客户端调用 RTC SDK 接口 sendUserBinaryMessage(以 Android 端为例)发送需要播报的自定义文本给指定 AI。其中,​
  • userId:填入Bot 的 ID(即 StartVoiceChat 配置的 AgentConfig.UserId)。​
  • buffer:需遵循如下格式:​
​Image​​
​
​
参数名​
类型​
描述​
magic number​
binary​
消息格式,此处填入 ctrl。​
length​
binary​
自定义文本消息长度,单位为 bytes。存放方式为大端序。​
control_message​
binary​
自定义文本配置信息。​
​
control_message​
​
​
参数名​
类型​
描述​
Command​
String​
控制命令,此处填入 ExternalTextToSpeech。​
Message​
String​
自定义文本内容,末尾必须包含标点符号,长度不超过 200 个字符。​
InterruptMode​
Int​
文本内容播报的优先级。​
  • 1:高优先级。系统会终止当前交互,直接播放传入的文本内容。​
  • 2:中优先级。系统会在当前交互结束后,播放传入的文本内容。​
  • 3:低优先级。如果此时正在交互,系统会直接丢弃传入的文本内容。如果未在交互,系统会播放传入的文本内容。​
AdvanceConfig​
Object​
外部文本播报的高级配置。用于指定外部文本所属的对话轮次,并将业务自定义信息随该轮字幕透传给客户端。​
​
AdvanceConfig​
​
​
参数名​
类型​
描述​
RoundID​
Int​
本次外部文本播报所属的对话轮次 ID。​
CustomInfo​
JSONMap​
业务自定义信息,需使用键值对。系统会将该自定义信息随同一轮次的字幕通过 customInfo 原样透传给客户端或服务端。​
​
你可参看以下示例传入自定义文本内容进行播放:​
​
C++
Java
TypeScript
​
​
// 发送文本转语音(TTS)指令,让智能体播报指定的文本内容,并透传业务自定义信息。​
void sendTTSMessage(​
const std::string& user_id,​
const std::string& content,​
int interrupt_mode,​
int round_id,​
const nlohmann::json& custom_info​
) {​
nlohmann::json json_data;​
json_data["Command"] = "ExternalTextToSpeech";​
json_data["Message"] = content;​
json_data["InterruptMode"] = interrupt_mode; // InterruptMode 可选值 1、2、3​
json_data["AdvanceConfig"] = {​
{"RoundID", round_id},​
{"CustomInfo", custom_info}​
};​
sendUserBinaryMessage(user_id, json_data.dump());​
}​
​
void buildBinaryMessage(const std::string& magic_number, const std::string& message, size_t& binary_message_length, std::shared_ptr<uint8_t[]>& binary_message) {​
auto magic_number_length = magic_number.size();​
auto message_length = message.size();​
​
binary_message_length = magic_number_length + 4 + message_length;​
binary_message = std::shared_ptr<uint8_t[]>(new uint8_t[binary_message_length]);​
std::memcpy(binary_message.get(), magic_number.data(), magic_number_length);​
binary_message[magic_number_length] = static_cast<uint8_t>((message_length >> 24) & 0xFF);​
binary_message[magic_number_length+1] = static_cast<uint8_t>((message_length >> 16) & 0xFF);​
binary_message[magic_number_length+2] = static_cast<uint8_t>((message_length >> 8) & 0xFF);​
binary_message[magic_number_length+3] = static_cast<uint8_t>(message_length & 0xFF);​
std::memcpy(binary_message.get()+magic_number_length+4, message.data(), message_length);​
}​
​
int sendUserBinaryMessage(const std::string &uid, const std::string& message) {​
if (rtcRoom_ != nullptr)​
{​
size_t length = 0;​
std::shared_ptr<uint8_t[]> binary_message = nullptr;​
buildBinaryMessage("ctrl", message, length, binary_message);​
return rtcRoom_->sendUserBinaryMessage(uid.c_str(), static_cast<int>(length), binary_message.get());​
}​
return -1;​
}​
​
// 示例调用(请替换为你的业务变量)​
sendTTSMessage(​
"<BOT_USER_ID>", // 替换为实际 Bot ID​
"这是业务自定义的播报文本。", // 替换为实际播报文本​
1, // 替换为实际优先级​
11, // 替换为实际 roundId​
{​
{"finish", true}, // 替换为实际业务状态​
{"biz", "demo"} // 替换为实际业务字段​
}​
);​
​
​
应用实践:传入自定义文本并透传业务信息​
当需要将大量自定义文本作为 AI 回复播报时(例如外部大模型的流式回复、长篇的业务话术等),若还需要知道这段播报属于哪条业务回复、当前回复是否结束、后续是否要继续等待等。此时可将业务协议写入 CustomInfo,由系统随字幕透传给客户端或服务端。​
以下以将外部大模型较长回复拆成多段播报为例说明。为了降低首包延迟,业务可以把自定义文本拆成多个完整分句,再按顺序多次发送。流程参考如下:​
  1. 将外部文本按完整分句拆分文本。​
  1. 对属于同一段完整内容(如同一条 LLM 回复)的各个分段,使用相同的 AdvanceConfig.RoundID 依次在服务端调用 UpdateVoiceChat 或通过客户端 sendUserBinaryMessage 发送 ExternalTextToSpeech。​
  1. 每次请求都携带 CustomInfo.finish,中间段设置为 false,最后一段设置为 true。​
  1. 客户端按 roundId 聚合字幕。即使收到 paragraph: true,也继续等待后续分段;只有在收到相同 roundId 下的 customInfo.finish: true 后,才将这段完整的外部内容标记为结束(例如用于在 UI 上呈现一条完整的对话气泡)。​
  1. 若播报被打断并开启新轮次,客户端应终止等待旧 roundId 的结束标记,并丢弃旧轮次的待完成状态。​
例如,一段长文本拆成两段发送时,若使用服务端实现,业务服务端可依次发送以下请求。​
第一个请求传入中间段:​
​
{​
"AppId": "YOUR_AppId",​
"RoomId": "YOUR_RoomId",​
"TaskId": "YOUR_TaskId",​
"Command": "ExternalTextToSpeech",​
"Message": "北京三日游可以先游览故宫和景山。",​
"InterruptMode": 2,​
"AdvanceConfig": {​
"RoundID": 11,​
"CustomInfo": {​
"finish": false​
}​
}​
}​
​
第二个请求传入最后一段,并设置结束标记:​
​
{​
"AppId": "YOUR_AppId",​
"RoomId": "YOUR_RoomId",​
"TaskId": "YOUR_TaskId",​
"Command": "ExternalTextToSpeech",​
"Message": "最后一天可以游览颐和园,祝您旅途愉快。",​
"InterruptMode": 2,​
"AdvanceConfig": {​
"RoundID": 11,​
"CustomInfo": {​
"finish": true​
}​
}​
}​
​
客户端收到的两段字幕均包含 paragraph: true,但仅第二段字幕携带 customInfo.finish: true。因此,客户端应使用 customInfo.finish 判断这段长文本播报结束,而不是使用 paragraph 判断。​
客户端处理逻辑示例(伪代码):​
​
type SubtitleItem = {​
text: string;​
roundId?: number;​
sequence?: number;​
customInfo?: Record<string, unknown>;​
};​
​
function handleSubtitleMessage(message: SubtitleMessage) {​
for (const item of message.data as SubtitleItem[]) {​
if (typeof item.roundId !== 'number') {​
continue;​
}​
​
// 使用 roundId 聚合属于同一段完整内容(如同一条 LLM 回复)的多个文本分段。​
appendSubtitle(item.roundId, item.text);​
​
// paragraph 只表示本次 ExternalTextToSpeech 输入结束。​
// 业务约定的 finish=true 才表示整段长文本(完整回复)全部分发完毕。​
if ((item.customInfo as { finish?: boolean } | undefined)?.finish === true) {​
markResponseFinished(item.roundId);​
}​
}​
}​
​
function handleInterruptOrNewRound(activeRoundId: number) {​
// 这里表示业务已经收到“打断事件”或“更新到新的活跃 roundId”。​
// 旧轮次可能收不到 finish=true,需清理旧轮次的待完成状态。​
discardPendingResponsesBefore(activeRoundId);​
}​
​
FAQ​
  • Q1:如果设置 AI 支持语音打断,自定义文本内容不打断 AI 输出。本次交互被语音打断时,自定义文本内容何时播放?​
  • A1:此时自定义文本内容会被丢弃。​
  • Q2:播放的自定义文本内容是否会放入历史对话?​
  • A2:会。​
  • Q3:如果设置 AI 支持语音打断,自定义文本内容不打断 AI 输出。自定义文本内容播放时被打断,该文本内容是否会放在历史对话中?​
  • A3:已输出的内容会放入历史对话中。​
​
最近更新时间:2026.09.21 16:24:31
这个页面对您有帮助吗?
有用
有用
无用
无用