构建群聊
创建一个 Live API 房间,让多个角色回答同一条消息,收集带归属的回复,并在对话结束时关闭房间。
通过 Live API 运行群聊:一个文本房间、多个角色实例,以及一条每个被提及的角色都可以回答的主持人消息。房间中的任何人类参与者都可以发送该消息;本页将该参与者称为主持人。每条回复都会携带 membership_id 的实例,因此你的客户端可以不必猜测就将其归属。若多个角色必须回答同一条消息而不是轮流回答,请使用本页。
前提条件
一个可访问 Live API 和多角色会话的 Convai API 密钥
两个或更多该 API 密钥可访问的角色 ID,每个都是不带
-draft,-latest或版本后缀的裸 UUID一个稳定的、非空的
end_user_id用于每个真人参与者一个可以发送可靠数据消息并接收数据消息的 LiveKit 客户端
这里描述的群聊仅支持文本,因此客户端不需要发布或接收音轨。运行示例之前,请将 LIVE_API_URL 设置为 https://live.convai.com 。
运行群聊
创建房间
发送一个有序的 characters 数组到 POST /connect ,并将 group_chat 设为 true。每一项都会成为一个可独立寻址的角色实例:
curl --request POST "${LIVE_API_URL}/connect" \
--header "X-API-Key: ${CONVAI_API_KEY}" \
--header "Content-Type: application/json" \
--data '{
"characters": [
{ "character_id": "11111111-1111-4111-8111-111111111111" },
{ "character_id": "22222222-2222-4222-8222-222222222222" },
{ "character_id": "33333333-3333-4333-8333-333333333333" }
],
"connection_type": "text",
"group_chat": true,
"room_brief": "对一个新的安全培训模块进行设计评审。每个回答都控制在两句话以内。",
"end_user_id": "moderator-42"
}'room_brief 是房间中的每个角色都会收到的上下文,包括后续添加的角色,最多 4,000 个字符。它在房间创建时固定,因此如果需要不同的简报,就需要新建房间。 Connect API 文档 characters, group_chat,以及 room_brief 的完整内容。
此响应是缩短版:它展示了三条 roster 项中的两条,省略了其余标准 /connect 字段,并省略了每个条目的 session_id, character_session_id,以及 说明:
{
"room_url": "wss://example.livekit.cloud",
"room_name": "convai-room-example",
"token": "LIVEKIT_PARTICIPANT_TOKEN",
"session_id": "INITIAL_CHARACTER_SESSION_TOKEN",
"room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"route_epoch": 0,
"roster_epoch": 0,
"partial_dispatch": false,
"characters": [
{
"membership_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"character_id": "11111111-1111-4111-8111-111111111111",
"participant_identity": "CHARACTER_PARTICIPANT_1",
"display_name": "Ada",
"is_initial": true,
"provisioning_status": "dispatch_accepted",
"failure_code": null
},
{
"membership_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"character_id": "22222222-2222-4222-8222-222222222222",
"participant_identity": "CHARACTER_PARTICIPANT_2",
"display_name": "Rao",
"is_initial": false,
"provisioning_status": "dispatch_accepted",
"failure_code": null
}
]
}保留 room_session_id, route_epoch,以及 session_id。每条命令都携带 room_session_id,第一个 group-address 携带 route_epoch,以及 session_id 关闭房间。
加入并等待每个实例
使用 room_url, room_name,以及 token加入 LiveKit 房间。等待每个可用实例发出一条 bot-ready 消息后再发送一轮。如果 partial_dispatch 为 true,先使用其 provisioning_status 和 failure_code 处理每个失败条目——见 验证与排障. 映射每个角色实例 包含完整的映射规则,包括如何将一个实例绑定到其 LiveKit 参与者。
在群聊房间中, data.about 对象通常还会携带 bot-ready display_name :即要显示给该实例的名称。该字段尽力提供;若不存在,则回退到该 membership 的 上的 :即要显示给该实例的名称。该字段尽力提供;若不存在,则回退到该 membership 的 roster 条目中的名称。对于克隆,这两个来源不同: /connect 携带一个房间唯一的标签,因此名为 bot-ready Ada Ada 的第二个实例会显示为 Ada (2),而两个实例在各自的 Ada roster 条目上共享同一个普通名称。请按 /connect 对转录内容进行索引,而不是按名称。 membership_idmembership_id
向房间发出指令
发送 group-address ,通过 LiveKit 数据通道作为可靠数据消息。它使用 client-to-server message 信封,并附加两项内容:顶层的 label 为 "rtvi-ai" 以及顶层的 id 来命名该命令:
{
"label": "rtvi-ai",
"type": "group-address",
"id": "turn-0001",
"data": {
"room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"mode": "all",
"expected_route_epoch": 0,
"text": "请每个角色用一句话介绍自己。"
}
}房间中的任何人类参与者都可以发送它。
expected_route_epoch 是你持有的最新 epoch。在第一条命令中,它是 route_epoch 来自 /connect;之后它就是 extras.route_epoch 来自上一条 group-address, interaction-target,或者 character-roster-update 响应。每条命令都要提供一个 id ,长度为 1 到 128 个字符,并且在 group-address, interaction-target,以及 character-roster-update中保持唯一,因为这三者共用一个 id 空间。用相同内容重发一个 id 会被静默接受,不会产生第二个响应,所以不要等待它。以不同内容重发一个 id 则会被拒绝,并返回 command_id_conflict。参见 group-address.
mode 决定谁必须回答:
mode
被指向的人
target_membership_ids
"all"
房间中的每个实例;拒绝会被报告为失败
省略它;以此模式发送的列表会被忽略
"tagged"
仅限列出的实例;拒绝会被报告为失败
必需:至少一个在线 membership_id,且无重复
"open"
房间中的每个实例,而且每个都可以拒绝
省略它;以此模式发送的列表会被忽略
一个 tagged turn 按 membership_id:
{
"label": "rtvi-ai",
"type": "group-address",
"id": "turn-0002",
"data": {
"room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"mode": "tagged",
"target_membership_ids": ["dddddddd-dddd-4ddd-8ddd-dddddddddddd"],
"expected_route_epoch": 0,
"text": "Rao,你会先添加哪个控制项?"
}
}一次只能在房间中运行一个 turn。请在当前 turn 的 group-address 之后发送下一条 server-response.
读取回复
每个被指向的实例都会将回复作为普通的 bot-llm-text 消息流式输出。请按 data.membership_id为每个增量建立索引,并将 bot-llm-stopped 视为该实例回复的结束。不要同时消费 bot-transcription,它会重复相同文本并让每条回复翻倍。群聊本身不会添加任何带文本的消息;回复会出现在标准 bot 输出消息中。
一个拒绝 open turn 的实例通常会用 llm-no-response 和 reason: "abstain"来宣布这一点。该消息是扁平的,因此它的标识字段位于更深一层,即 data.data.membership_id。把这条消息当作提示,并从 turn-complete 列表中确定最终结果:一个实例可以出现在 passed_membership_ids 中而从未发送过 llm-no-response,而且在拒绝到达前已经开始流式输出文本的实例仍会被记录在 answered_membership_ids.
turn-complete 中。它会结束该 turn。它每个 turn 最多发送一次,包装在第 server-message 信封中,如 Turn 生命周期和消息顺序:
{
"type": "server-message",
"data": {
"type": "turn-complete",
"data": {
"room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"command_id": "turn-0001",
"mode": "all",
"addressed_membership_ids": [
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"ffffffff-ffff-4fff-8fff-ffffffffffff"
],
"answered_membership_ids": [
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
"dddddddd-dddd-4ddd-8ddd-dddddddddddd",
"ffffffff-ffff-4fff-8fff-ffffffffffff"
],
"passed_membership_ids": [],
"failed_membership_ids": [],
"turn_id": "turn-0001",
"addressed": 3,
"answered": 3,
"passed": 0,
"failed": 0,
"cancelled": false,
"route_epoch": 0
}
}
}addressed, answered, passed,以及 failed 始终等于对应列表的长度。
turn-complete 会经过某个实例的连接,因此其 data 也可能携带该实例的身份字段;忽略它们,读取这些列表即可。它不会重试,所以在极少数情况下会丢失;请将下面的 server-response 视为该 turn 的可靠结束。它也可能先于某个实例的最后一个 bot-llm-text delta 到达,因此在它之后再继续接受一小段宽限期内的回复文本。实际上,一到两秒就足够了。
一个 server-response ,并将 event_type: "group-address" 会结算该命令。它的 extras 携带 target_membership_ids (命名为 addressed_membership_ids 于 turn-complete), answered_membership_ids, passed_membership_ids, failed_membership_ids,以及一个 摘要 ,包含相同的计数。 status 为 "success" 表示该 turn 已结算,而不是表示有人回答了,因此在展示结果前要读取 failed_membership_ids 。超时的实例会被列为失败,而 turn 默认在 120 秒后超时。在 all 或 tagged 模式下的拒绝也会被报告为失败;只有 open 会产生 passed_membership_ids.
存储 extras.route_epoch 并将其作为 expected_route_epoch 发送到下一条命令上。不要发送 route_epoch 来自 turn-complete,它是 turn 自身的快照。
更改房间中的成员
使用 character-roster-update添加和移除实例,正如 更新 roster.
群聊房间新增两条规则。当 turn 正在运行时,该命令会被拒绝并返回 turn_in_progress ,因此要在该 turn 的 server-response之后再发送。移除活动实例会提升 route_epoch,所以要从 roster-update 响应中保存 route_epoch 并在你的下一次 expected_route_epoch 中将其发送为 group-address。新实例会继承房间的设置和简报,并且在它第一次被指向的 turn 中会收到自加入以来发生的内容。它不会获得在添加之前的对话,因此请重新说明新来者需要知道的任何内容。
房间的行为方式
被指向的实例会并行回答,并且在同一 turn 内无法看到彼此的回复。
每个 turn 之前,每个被指向的实例都会收到自其加入以来在房间中说过但尚未传递给它的内容。这包括其他实例的回复以及你发给其他实例的消息。
未被指向的实例在被指向之前不会收到任何内容。
同一角色的两个实例是彼此独立的参与者。
:即要显示给该实例的名称。该字段尽力提供;若不存在,则回退到该 membership 的于bot-ready被做成了房间唯一,以便区分它们;它们的/connectroster 条目共享同一个名称,因此请按membership_id.
验证与排障
在发送 turn 之前,验证:
每个预期实例都有唯一的
membership_id.每个可用实例都已经发出
bot-ready.你即将作为
expected_route_epoch发送的 epoch 是你收到的最新一个。
group-address 返回 not_group_chat_room
该房间是在没有 group_chat: true.
新建一个带有 group_chat: true的房间。该设置在创建时就固定。
group-address 返回 stale_route_epoch
expected_route_epoch 与房间当前的 epoch 不匹配。
从 extras.route_epoch 错误响应中读取,然后用新的 id.
group-address 返回 turn_in_progress
房间中仍有一个 turn 在运行。
等待该 turn 的 server-response,然后再次发送该命令。
重发的 group-address 从未收到回复
该 id 已用相同内容重复使用,并已去重。
依赖首次回复;发送一条带有新的 id.
实例从不发出 bot-ready
其配置失败或仍在进行中。
检查 partial_dispatch, provisioning_status,以及 failure_code 在创建响应上;不要寻址该实例。
被寻址的实例从不回复
它失败了,或者未在回合超时内完成。
从 failed_membership_ids 在该 server-response;即使如此,该实例也被列在那里 status 为 "success".
每个回复都出现两次
客户端消费 bot-transcription 以及 bot-llm-text.
仅消费 bot-llm-text 增量。
当回合结束时,回复会被截断
客户端在以下位置停止接受文本 turn-complete.
继续接受 bot-llm-text 在……之后短暂宽限期内 turn-complete.
后续步骤
群聊消息使用多角色会话服务器到客户端消息连接 API最后更新于
这有帮助吗?