接入 AI 实时对话引擎
本文说明如何通过 TiRTC 接入探鸽平台 AI 实时对话引擎。业务服务端负责获取角色、申请连接凭证并下发给设备;设备端负责使用凭证建连、启动会话、收发音频和处理事件。
1. 总体流程
sequenceDiagram
autonumber
participant D as 设备端
participant B as 业务服务端
participant P as 探鸽平台
B->>P: 创建或查询角色(控制台 / OpenAPI),获取 role_id
D->>B: 请求发起 AI 实时对话
B->>B: 校验用户、设备、套餐与业务权限
B->>P: POST /v1/token/aichat
P-->>B: peer_id, token
B-->>D: 下发 peer_id, token
D->>P: TiRtcWhipConnect(peer_id, token)
P-->>D: TiRTC 连接建立成功
D->>P: start_session(device_id, role_id)
P-->>D: session_id 与音频格式
D->>P: 上行麦克风音频
P-->>D: 下行回复音频、字幕、轮次事件role_id 是 AI 实时对话引擎的必传参数。设备端不要自行拼接 peer_id,也不要持有服务端签名密钥。peer_id 与 token 必须由业务服务端向探鸽平台申请后下发。
2. AI 实时对话引擎控制台
接入前,请先进入 AI 实时对话引擎控制台 完成以下配置:
- 已在探鸽平台创建应用,并获取
AppId、AccessKeyId、SecretKeyId。 - 已完成设备注册,以及设备与应用或产品的绑定关系配置。
- 已开通 AI 实时对话引擎相关套餐或服务能力。
- 已在探鸽开放平台创建并配置智能体。
- 已获取本次会话使用的
role_id,或已通过 OpenAPI 建立设备与角色的绑定关系。
角色获取、设备绑定角色、设备角色查询等 OpenAPI 以后续 Apifox 文档 为准。
3. 服务端集成
服务端接入的目标是确定本次会话使用哪个设备和哪个角色,向探鸽平台申请短期连接凭证,并把凭证安全地下发给设备。
3.1 接口鉴权
服务端 API 签名与探鸽云端 OpenAPI 服务端签名一致,详见 服务端API接口签名算法Demo。
3.2 申请连接凭证
只有申请 AI 实时对话引擎连接凭证时需要调用 https://api-tirtc.tange365.com 域名:
POST https://api-tirtc.tange365.com/v1/token/aichat
Content-Type: application/json
Authorization: TGV1-HMAC-SHA256 ...
X-Tg-App-Id: {AppId}
{
"device_id": "DEMO_DEVICE_01",
"role_id": "role_xxx"
}响应示例:
{
"code": 0,
"msg": "ok",
"data": {
"peer_id": "whips://aichat?device_id=DEMO_DEVICE_01&role_id=role_xxx",
"token": "v1.{payload}.{signature}"
}
}完整说明见 服务端接口。
3.3 下发凭证给设备
业务服务端从平台申请到 peer_id 与 token 响应后,应通过安全通道下发给设备。设备只需要消费这两个值,不需要知道服务端签名密钥,也不需要自行拼接 peer_id。
下发载荷示例:
{
"type": "aichat_join",
"device_id": "DEMO_DEVICE_01",
"role_id": "role_xxx",
"peer_id": "whips://aichat?device_id=DEMO_DEVICE_01&role_id=role_xxx",
"token": "v1.{payload}.{signature}"
}安全建议:
- 不要在日志中打印完整
token。 peer_id和token应尽快使用,避免长时间缓存。- 同一设备已有进行中的会话时,服务端应按业务规则拒绝、排队或先结束旧会话。
4. 设备端集成
设备端接入的目标是使用业务服务端下发的 peer_id 和 token 建立 TiRTC 连接,发送 start_session 启动会话,然后完成音频上下行、事件处理和会话结束。
4.1 建立 TiRTC 连接
设备收到 peer_id 与 token 后,调用 TiRTC SDK 建立连接。
#include "tiRTC.h"
#include <stdbool.h>
#include <string.h>
static tirtc_conn_t g_ai_conn = NULL;
static bool g_tirtc_ready = false;
static int ai_tirtc_runtime_init(const char *device_id)
{
if (g_tirtc_ready) {
return 0;
}
int ret = TiRtcInit();
if (ret != 0) {
return ret;
}
TIRTCCALLBACKS cbs;
memset(&cbs, 0, sizeof(cbs));
ret = TiRtcStart(device_id, &cbs);
if (ret != 0) {
TiRtcUninit();
return ret;
}
g_tirtc_ready = true;
return 0;
}
static void on_ai_whip(int err, tirtc_conn_t hconn, void *user_data)
{
(void)user_data;
if (err != 0) {
/* 建连失败:记录错误码,释放本地会话状态 */
return;
}
g_ai_conn = hconn;
/* 建连成功后,下一步发送 start_session 命令 */
}
int start_ai_tirtc_connect(const char *device_id, const char *peer_id, const char *token)
{
int ret = 0;
if (device_id == NULL || device_id[0] == '\0' ||
peer_id == NULL || peer_id[0] == '\0' ||
token == NULL || token[0] == '\0') {
return -1;
}
ret = ai_tirtc_runtime_init(device_id);
if (ret != 0) {
return ret;
}
/* 返回 0 表示请求已提交,最终是否成功以 on_ai_whip 回调为准 */
return TiRtcWhipConnect(peer_id, token, on_ai_whip, NULL);
}
void stop_ai_tirtc_connect(void)
{
if (g_ai_conn != NULL) {
TiRtcClose(g_ai_conn);
g_ai_conn = NULL;
}
if (g_tirtc_ready) {
TiRtcStop();
TiRtcUninit();
g_tirtc_ready = false;
}
}TiRtcWhipConnect 返回 0 只表示建连请求已提交;真正成功以回调中的 err == 0 为准。
4.2 发送 start_session
TiRTC 连接建立后,设备端需要通过命令通道发送 start_session。关于详细字段格式和默认值说明,请参考 事件协议。
请求示例:
{
"jsonrpc": "2.0",
"id": "start-session-001",
"method": "start_session",
"params": {
"device_id": "DEMO_DEVICE_01",
"role_id": "role_xxx",
"input_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
},
"output_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
}
}
}收到成功响应后,设备应以响应中的 input_audio 和 output_audio 作为本次会话实际音频格式,然后开始持续上行音频。若收到 JSON-RPC error,应停止采集并释放本地会话状态。
4.3 上行设备语音
设备端在采集线程中按固定周期发送音频帧,建议单帧时长控制在 100 ms 以内。
默认约定:
| 项目 | 推荐值 |
|---|---|
音频 stream_id | 1,如探鸽平台或接入包另有约定,以实际约定为准。 |
| 采样率 | 16000 |
| 声道 | 1 |
| 编码 | opus,也支持 pcm / g711a |
| 帧长 | 100 ms 以内 |
发送代码示例:
#include <stdint.h>
#include <string.h>
#include "tiRTC.h"
static const uint8_t kAiAudioStreamId = 1;
int ai_send_audio_opus(tirtc_conn_t hconn,
const void *opus_data,
uint32_t len,
uint32_t ts_ms)
{
TIRTCFRAMEINFO fi;
memset(&fi, 0, sizeof(fi));
fi.stream_id = kAiAudioStreamId;
fi.media = TIRTC_AUDIO_OPUS;
// 重要:必需设置与 input_audio 一致的音频格式 flags
fi.flags = TIRTC_AUDIOSAMPLE_16K16B1C;
fi.ts = ts_ms;
fi.length = len;
return TiRtcSendAudioStream(hconn, &fi, opus_data);
}音频参数映射关系: 必须确保以下三个配置完全对齐,否则将导致平台侧识别失败或解码异常:
start_session.input_audio中的意图声明。- C 语言层设置的
fi.media。 - C 语言层设置的采样规格
fi.flags。
探鸽平台会做云端 VAD 和语音轮次判断。设备端可以不做本地 VAD;如果为节省带宽自行过滤静音,需要避免截断用户说话的开头和结尾。
需要让智能体理解设备摄像头画面时,在 start_session 成功后继续发送 stream_id = 0 的 H.264 或 H.265 视频帧。视频流第一帧必须是关键帧;完整约定和代码见设备端集成。
4.4 播放下行语音
回复音频会通过 TiRTC 音频流下发。设备端应在音频回调中按 start_session 响应的 output_audio 解码并送入播放链路。
处理建议:
- 按
stream_id过滤 AI 实时对话引擎音频流。 - 按
media和协商的codec解码。 - 播放链路应支持连续流式播放,不要等待整句 TTS 结束后再播放。
- 如果收到打断或会话结束事件,应及时停止当前播放缓冲。
4.5 处理事件
AI 实时对话引擎的控制消息、字幕和设备能力调用都通过命令通道发送。详见 事件协议。
设备端至少需要实现以下事件处理:
| method | 方向 | 说明 |
|---|---|---|
start_session | 设备 -> 平台 | 建连后启动 AI 实时对话会话。 |
caption | 平台 -> 设备 | ASR / TTS 字幕事件。 |
round_start | 平台 -> 设备 | 一轮回复音频开始。 |
round_end | 平台 -> 设备 | 一轮回复音频结束。 |
interrupt | 设备 -> 平台 | 主动打断当前回复。 |
submit_speech | 设备 -> 平台 | 手动提交当前上行语音。 |
end_session | 双向 | 结束当前会话。 |
4.6 结束会话
无论触发方是设备端、用户意图还是平台策略,设备端都应保证结束流程可重复调用且不会泄漏资源。建议在用户按键结束、平台通知结束、连接错误、网络断开、业务超时和设备关机时,都调用同一套本地清理逻辑:
- 停止采集。
- 停止播放。
- 释放本地会话上下文,清理音频采集和播放资源。
- 断开 TiRTC 连接。
4.7 最小跑通路径
为了快速排查环境与配置问题,建议开发者首先通过以下的端到端最小跑通路径验证:
- 服务端请求测试:使用配置好的 AppId/AK/SK,通过
curl发送TGV1-HMAC-SHA256签名的请求到/v1/token/aichat,确保能稳定返回 HTTP200状态码和有效的peer_id及token。 - 设备端连接测试:将拿到的
device_id、peer_id和token贴入设备端测试代码的start_ai_tirtc_connect方法,观察on_ai_whip回调的err是否为0。 - 会话握手测试:在连接成功的回调中触发发送
start_session消息。 - 验证成功判据:设备端命令回调应当能够收到探鸽平台返回的 JSON-RPC 成功响应(包含
session_id)。如果收到错误响应则需要核对参数拼写。 - 音频回路验证:传入一小段事先准备好的 PCM 测试音频(不断循环发送),并验证设备端是否能收到下行的
caption事件和TIRTC_AUDIO_OPUS的回音数据。