通过 TiRTC 接入 Coze 智能体
本文带你从零完成一条可工作的链路:设备把麦克风音频通过 TiRTC 发给探鸽平台,探鸽平台调用你的 Coze 智能体,再把 Coze 的语音回复通过 TiRTC 发回设备。
第一次接入时,请严格按本文顺序操作,不要跳步。
先看懂整条链路

你只需要记住 4 个角色:
| 角色 | 负责什么 |
|---|---|
| Coze 控制台 | 创建智能体,生成 API Key。 |
| 探鸽角色配置 | 保存 Coze 凭证和 bot_id,生成设备会话使用的 role_id。 |
| 业务服务端 | 使用 device_id + role_id 申请短期 peer_id + token,再下发给设备。 |
| 设备端 | 用 TiRTC 建连,发送 start_session,上传麦克风音频并播放回复音频。 |
不要混淆 3 个 ID
bot_id:Coze 智能体 ID,只在创建探鸽角色时使用。role_id:探鸽角色 ID,申请凭证和启动会话时使用。device_id:你在探鸽平台注册的设备 ID。
开始前准备
确认下面每一项都已经完成:
- 你可以登录 Coze 控制台。
- 你已经创建一个可以正常对话的 Coze 智能体,并记录它的
bot_id。 - 你已经创建探鸽应用和设备,并拿到探鸽
AppId。 - 你的业务服务端可以调用探鸽服务端接口。
- 设备端已经集成 TiRTC SDK,可以建立连接、收发音频和收发命令。
如果 TiRTC 还没有接通,请先完成设备端集成,再回到本文。
第 1 步:在 Coze 创建智能体和 API Key
1.1 创建并测试智能体
如果你还没有创建过 Coze 智能体,请先阅读 Coze 智能体快速入门。
- 登录 Coze 控制台。
- 创建智能体,并在 Coze 调试页面确认它能正确回答问题。
- 确保该智能体允许通过 API 调用;如果控制台要求发布,请先完成发布。
- 记录智能体的
bot_id。不要把智能体名称当成bot_id。
1.2 创建凭证
打开 Coze Playground 授权页,从 API & SDK → 授权 → 服务身份及凭证进入并创建 API Key。
在 Coze 控制台依次进入:
- API & SDK。
- 授权。
- 服务身份及凭证。
- 单击添加,创建凭证。
- 在权限列表中授予该身份调用目标智能体和实时语音接口所需的权限。
- 生成并立即复制 API Key。API Key 只应保存在服务端,不要写入设备程序或提交到代码仓库。

上图红框依次标出了授权和服务身份及凭证。进入该页面后,单击右上角的添加。
你最终需要拿到下面 1 项:
| Coze 凭证内容 | 后面填写到探鸽角色的字段 |
|---|---|
| API Key | service_config.config.api_key |
第 2 步:在探鸽平台创建 Coze 类型角色
你可以在探鸽控制台创建角色,也可以调用角色接口。接口地址、请求字段和响应格式以创建角色接口文档为准。
2.1 字段对应关系
| 探鸽字段 | 从哪里获取 | 必填 | 说明 |
|---|---|---|---|
name | 自己命名 | 是 | 探鸽控制台中显示的角色名称。 |
service_config.type | 固定值 | 是 | 必须是 coze。 |
api_key | Coze API Key | 是 | 只保存在服务端角色配置中。 |
bot_id | Coze 智能体 | 是 | Coze 智能体 ID。 |
workflow_id | Coze 工作流 | 否 | 只有明确需要指定工作流时才填写;不用就传空字符串或省略。 |
2.2 创建角色
按照创建角色接口文档,将角色类型设置为 coze,填写 api_key 和 bot_id 后创建角色。
创建成功后保存返回的 role_id。后面的连接凭证申请和 start_session 都要使用它。
第 3 步:业务服务端申请 TiRTC 连接凭证
业务服务端使用同一个 device_id 和 role_id 申请 peer_id + token。接口地址、签名方式、请求字段和错误处理见服务端集成。
业务服务端只把 device_id、role_id、peer_id 和 token 下发给设备。设备端不要自己拼 peer_id,也不要保存探鸽 AK/SK。
第 4 步:设备通过 TiRTC 建连
设备按下面顺序执行:
- 初始化并启动 TiRTC SDK。
- 注册音频回调和
on_command命令回调。 - 调用
TiRtcWhipConnect(peer_id, token, ...)。 - 等待建连回调返回
err == 0。 - 发送
start_session。 - 等待
start_session成功响应。 - 按响应中的音频格式开始上传麦克风音频,并播放下行音频。
不要提前发音频
TiRtcWhipConnect 返回 0 只表示请求已提交。必须等待连接回调成功,并收到 start_session 成功响应后,才能开始发送真实麦克风音频。
4.1 发送 start_session
AI 实时对话引擎信令使用命令字 0x2100,payload 是 UTF-8 JSON-RPC 2.0:
{
"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
}
}
}device_id 和 role_id 必须与申请 peer_id + token 时使用的值完全一致,否则平台会返回:
{
"jsonrpc": "2.0",
"id": "start-session-001",
"error": {
"code": -32602,
"message": "start_session params do not match WHIP token"
}
}成功响应示例:
{
"jsonrpc": "2.0",
"id": "start-session-001",
"result": {
"session_id": "aivoice-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"input_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
},
"output_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
}
}
}设备必须以成功响应中的 input_audio 和 output_audio 为准,不要只相信请求值。
4.2 C 端发送命令
下面的辅助函数只负责把 JSON 发到 AI 实时对话引擎命令通道:
#include <stdint.h>
#include <string.h>
#include "tiRTC.h"
#define TIRTC_AI_SIGNALING 0x2100
/* 发送一条 UTF-8 JSON-RPC 信令。 */
static int send_ai_json(tirtc_conn_t hconn, const char *json)
{
if (hconn == NULL || json == NULL || json[0] == '\0') {
return -1;
}
return TiRtcSendCommand(
hconn,
TIRTC_AI_SIGNALING,
json,
(uint32_t)strlen(json));
}完整初始化、建连和音频发送代码见设备端集成。
第 5 步:收发音频
5.1 上行音频
设备发送的格式必须和 start_session.result.input_audio 一致:
codec=opus:使用TIRTC_AUDIO_OPUS,16 kHz、单声道。codec=pcm:发送对应采样规格的 PCM。- 不要把 PCM 字节标成 Opus,也不要把 8 kHz 音频标成 16 kHz。
- 单帧建议不超过 100 ms。
5.2 下行音频
设备端需要:
- 在 TiRTC 音频回调中接收数据。
- 按返回的
output_audio.codec解码。 - 立即送入连续播放缓冲,不要等待整句完成。
- 收到取消事件或结束会话时,清空还没有播放的旧音频。
Coze 事件协议
先区分两个通道
| 通道 | 承载内容 | 设备怎么接收 |
|---|---|---|
| TiRTC 音频通道 | conversation.audio.delta 中的 Coze 回复音频 | TiRTC 音频回调。 |
TiRTC 命令通道 0x2100 | start_session 响应,以及除 conversation.audio.delta 外的 Coze 服务端事件 | TIRTCCALLBACKS.on_command。 |
当前会透传给设备的 Coze 事件
Coze 服务端事件按下面的规则下发:
conversation.audio.delta:平台解码其中的音频数据,通过 TiRTC 音频通道发送,不再生成forward事件。- 其他所有 Coze 服务端事件:无论平台是否还需要执行内部处理,解析后的事件结构都会放入
method=forward的params中,并且每个事件只透传一次。后续 Coze 版本新增的未知事件类型也遵循这条规则。
forward 消息示例:
{
"jsonrpc": "2.0",
"id": "<coze_event_id>",
"method": "forward",
"params": {
"id": "<coze_event_id>",
"event_type": "conversation.audio.sentence_start",
"data": {},
"detail": {
"logid": "<coze_log_id>"
}
}
}设备不应再把可接收的事件范围限定为固定的 3 类。下面列出常用事件及建议动作;实际事件类型和 data 字段以当前 Coze 实时语音协议为准。
params.event_type | 含义 | 设备建议动作 |
|---|---|---|
conversation.audio.sentence_start | Coze 开始生成一段回复语音。 | UI 进入“智能体正在说话”;准备播放缓冲。 |
conversation.audio.completed | Coze 本轮回复音频已经生成完成。 | 等待本地播放缓冲排空后,UI 回到聆听状态。 |
conversation.chat.canceled | Coze 当前对话被取消。 | 立即停止并清空旧回复音频。 |
conversation.audio_transcript.update / conversation.audio_transcript.completed | Coze 返回语音识别文本增量或完整文本。 | 需要字幕时读取原生事件;不要等待通用 caption。 |
conversation.message.delta / conversation.message.completed | Coze 返回消息增量或完整消息。 | 按产品需要显示或记录。 |
conversation.chat.completed / conversation.chat.failed | 本轮对话完成或失败。 | 更新会话状态,并记录失败信息。 |
chat.created、chat.updated、conversation.chat.created、conversation.chat.in_progress | 会话创建、配置更新或进入处理状态。 | 通常记录状态;需要时更新 UI。 |
input_audio_buffer.speech_started / input_audio_buffer.speech_stopped | Coze 服务端 VAD 检测到用户开始或停止说话。 | 按产品需要更新聆听状态。 |
| 其他事件 | 其他当前或未来的 Coze 服务端事件。 | 保留并记录收到的 params,按所用 Coze 协议版本处理。 |
forward 包里的顶层 id 用于关联 Coze 事件。设备收到这类平台下行事件后不需要发送 JSON-RPC response。
与通用 AI 实时对话引擎事件的差异
Coze 适配器不是通用 Pipeline
Coze 原生字幕等事件会通过 forward 下发,但不会自动转换成通用事件页里的 caption、round_start、round_end 或 device_action。如果产品需要这些能力,请读取 forward.params.event_type 并按所用 Coze 协议版本处理,不能只按通用事件页开发。
下行事件已统一透传,不代表同名或相近的设备上行通用控制会自动转换成 Coze 原生客户端事件。设备上行方法的当前支持情况如下:
method | Coze 链路当前行为 |
|---|---|
start_session | 支持,必须发送。 |
end_session | 支持,关闭当前探鸽与 Coze 会话。 |
forward | 支持,把 params 作为原始 Coze 客户端事件转发;属于高级能力。 |
interrupt / interupt | 可以进入第三方引擎,但不会直接映射成 Coze 取消事件,不能依赖它完成云端打断。 |
submit_speech | 不会直接映射成 Coze 客户端事件;默认依赖 Coze 服务端 VAD 判断用户说完。 |
update_config | 不会直接映射成 Coze chat.update,也不会动态修改当前 Coze 配置。 |
如果确实需要发送 Coze 原生客户端事件,可以使用 forward:
{
"jsonrpc": "2.0",
"method": "forward",
"params": {
"id": "coze-client-event-001",
"event_type": "<Coze 原生客户端事件类型>",
"data": {}
}
}params 会直接写入 Coze WebSocket。事件类型和 data 必须严格符合你正在使用的 Coze 实时语音协议版本;不熟悉 Coze 原生协议时不要使用该入口。
通用 JSON-RPC 格式和其他 AI 实时对话引擎服务类型的事件见事件协议。
最小验收步骤
第一次联调只做下面 8 项:
- 在 Coze 调试页直接与智能体对话,确认 Coze 本身正常。
- 创建 Coze API Key,确认
api_key和bot_id不为空。 - 创建探鸽
coze类型角色,保存返回的role_id。 - 使用
device_id + role_id申请peer_id + token。 - 设备调用
TiRtcWhipConnect,确认建连回调err == 0。 - 设备发送
start_session,确认收到相同id的成功响应。 - 设备连续发送一段清晰的 16 kHz 单声道语音。
- 确认设备收到下行音频并能连续播放;同时记录
forward事件用于排错。
只要第 8 步成功,最小链路就已经跑通。字幕、打断、动态配置和设备插件应在此之后单独验收。
常见问题
创建角色时报 Coze 字段为空
检查请求体中的 service_config.type 是否为 coze,以及 api_key、bot_id 是否都填写。
start_session 返回参数与 Token 不匹配
你申请 peer_id + token 时使用的 device_id 或 role_id,和设备发送 start_session 时的值不一致。不要复用其他设备、其他角色或旧会话的凭证。
TiRTC 建连成功,但设备说话后没有回复
按顺序检查:
start_session是否已经成功。- 上行音频的
codec、采样率、声道和 TiRTC frame flags 是否一致。 - Coze
bot_id是否正确,智能体是否允许 API 调用。 - Coze API Key 是否拥有目标智能体和实时语音权限。
- 平台日志中是否出现 Coze 鉴权或 WebSocket 错误。
能收到事件,但听不到声音
- 确认使用 TiRTC 音频回调接收音频,不要从
forward事件里找音频。 - 确认解码器使用
start_session.result.output_audio.codec。 - 确认扬声器播放参数与解码后的采样率、声道一致。
- 不要等待
conversation.audio.completed后再一次性播放。
听到声音,但没有 caption
这是当前 Coze 适配链路的协议差异。Coze 原生的 conversation.audio_transcript.update 和 conversation.audio_transcript.completed 会通过 forward 下发,但不会映射成通用 caption。需要字幕时请读取原生事件,不能把 caption 作为 Coze 音频是否成功的判断条件。
发送 interrupt 后 Coze 仍继续生成
当前 Coze 适配器不会把通用 interrupt 自动转换成 Coze 原生取消事件。设备可以立即停止本地播放,但云端取消需要使用受支持的 Coze 原生事件透传或等待平台后续增加正式映射。
上线前检查清单
- [ ] Coze 智能体已测试,并允许 API 调用。
- [ ] Coze API Key 权限遵循最小权限原则,并且只保存在服务端。
- [ ] 探鸽角色类型是
coze,并保存了正确的role_id。 - [ ]
peer_id + token由业务服务端申请,设备没有自行拼接。 - [ ]
start_session的device_id + role_id与凭证完全一致。 - [ ] 设备严格按协商结果收发音频。
- [ ] 设备从音频回调播放
conversation.audio.delta,没有等待同名forward事件。 - [ ] 设备能接收并记录全部
forward事件,并按产品需要处理对应的params.event_type。 - [ ] 产品读取 Coze 原生事件时,没有错误依赖通用
caption等事件映射。 - [ ] 断网、失败、取消和主动退出都能幂等清理资源。