> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/build-a-group-chat.md).

# 构建群聊

创建一个 Live API 房间，让多个角色回答同一条消息，收集带归属的回复，并在对话结束时关闭房间。

通过 Live API 运行群聊：一个文本房间、多个角色实例，以及一条每个被提及的角色都可以回答的主持人消息。房间中的任何人类参与者都可以发送该消息；本页将该参与者称为主持人。每条回复都会携带 `membership_id` 的实例，因此你的客户端可以不必猜测就将其归属。若多个角色必须回答同一条消息而不是轮流回答，请使用本页。

{% hint style="info" %}
群聊仅对已启用该功能的账户可用。你的账户的角色和参与者限制仍然适用。
{% endhint %}

### 前提条件

* 一个可访问 Live API 和多角色会话的 Convai API 密钥
* 两个或更多该 API 密钥可访问的角色 ID，每个都是不带 `-draft`, `-latest`或版本后缀的裸 UUID
* 一个稳定的、非空的 `end_user_id` 用于每个真人参与者
* 一个可以发送可靠数据消息并接收数据消息的 LiveKit 客户端

这里描述的群聊仅支持文本，因此客户端不需要发布或接收音轨。运行示例之前，请将 `LIVE_API_URL` 设置为 <code class="expression">space.vars.live\_server\_url</code> 。

### 运行群聊

{% stepper %}
{% step %}

#### 创建房间

发送一个有序的 `characters` 数组到 `POST /connect` ，并将 `group_chat` 设为 `true`。每一项都会成为一个可独立寻址的角色实例：

```bash
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](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/connect-api.md) 文档 `characters`, `group_chat`，以及 `room_brief` 的完整内容。

此响应是缩短版：它展示了三条 roster 项中的两条，省略了其余标准 `/connect` 字段，并省略了每个条目的 `session_id`, `character_session_id`，以及 `说明`:

```json
{
  "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` 关闭房间。
{% endstep %}

{% step %}

#### 加入并等待每个实例

使用 `room_url`, `room_name`，以及 `token`加入 LiveKit 房间。等待每个可用实例发出一条 `bot-ready` 消息后再发送一轮。如果 `partial_dispatch` 为 `true`，先使用其 `provisioning_status` 和 `failure_code` 处理每个失败条目——见 [验证与排障](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md#verify-and-troubleshoot). [映射每个角色实例](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md#map-each-character-instance) 包含完整的映射规则，包括如何将一个实例绑定到其 LiveKit 参与者。

在群聊房间中， `data.about` 对象通常还会携带 `bot-ready` display\_name `：即要显示给该实例的名称。该字段尽力提供；若不存在，则回退到该 membership 的` 上的 `：即要显示给该实例的名称。该字段尽力提供；若不存在，则回退到该 membership 的` roster 条目中的名称。对于克隆，这两个来源不同： `/connect` 携带一个房间唯一的标签，因此名为 `bot-ready` Ada `Ada` 的第二个实例会显示为 `Ada (2)`，而两个实例在各自的 `Ada` roster 条目上共享同一个普通名称。请按 `/connect` 对转录内容进行索引，而不是按名称。 `membership_id`membership\_id
{% endstep %}

{% step %}

#### 向房间发出指令

发送 `group-address` ，通过 LiveKit 数据通道作为可靠数据消息。它使用 [client-to-server message](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/client-to-server-messages.md) 信封，并附加两项内容：顶层的 `label` 为 `"rtvi-ai"` 以及顶层的 `id` 来命名该命令：

```json
{
  "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`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/group-chat-messages.md#group-address).

`mode` 决定谁必须回答：

| `mode`     | 被指向的人              | `target_membership_ids`        |
| ---------- | ------------------ | ------------------------------ |
| `"all"`    | 房间中的每个实例；拒绝会被报告为失败 | 省略它；以此模式发送的列表会被忽略              |
| `"tagged"` | 仅限列出的实例；拒绝会被报告为失败  | 必需：至少一个在线 `membership_id`，且无重复 |
| `"open"`   | 房间中的每个实例，而且每个都可以拒绝 | 省略它；以此模式发送的列表会被忽略              |

一个 `tagged` turn 按 `membership_id`:

```json
{
  "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`.
{% endstep %}

{% step %}

#### 读取回复

每个被指向的实例都会将回复作为普通的 [`bot-llm-text`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#bot-llm-text) 消息流式输出。请按 `data.membership_id`为每个增量建立索引，并将 [`bot-llm-stopped`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#bot-llm-started-bot-llm-stopped) 视为该实例回复的结束。不要同时消费 `bot-transcription`，它会重复相同文本并让每条回复翻倍。群聊本身不会添加任何带文本的消息；回复会出现在标准 bot 输出消息中。

一个拒绝 `open` turn 的实例通常会用 [`llm-no-response`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#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 生命周期和消息顺序](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/turn-lifecycle-and-message-ordering.md):

```json
{
  "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`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#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 自身的快照。
{% endstep %}

{% step %}

#### 更改房间中的成员

使用 `character-roster-update`添加和移除实例，正如 [更新 roster](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md#update-the-roster).

群聊房间新增两条规则。当 turn 正在运行时，该命令会被拒绝并返回 `turn_in_progress` ，因此要在该 turn 的 `server-response`之后再发送。移除活动实例会提升 `route_epoch`，所以要从 roster-update 响应中保存 `route_epoch` 并在你的下一次 `expected_route_epoch` 中将其发送为 `group-address`。新实例会继承房间的设置和简报，并且在它第一次被指向的 turn 中会收到自加入以来发生的内容。它不会获得在添加之前的对话，因此请重新说明新来者需要知道的任何内容。
{% endstep %}

{% step %}

#### 结束会话

使用 `session_id` 从创建响应中关闭房间。它属于初始角色实例，将其传给 `/disconnect` 会结束整个房间：

```bash
curl --request POST "${LIVE_API_URL}/disconnect?session_id=INITIAL_CHARACTER_SESSION_TOKEN"
```

一个 `session_id` 从 join 响应中只会移除该真人参与者。

{% hint style="warning" %}
`/disconnect` 不需要 API 密钥。持有 `session_id` 的人都可以结束会话，因此请将每个 `session_id` 都视为机密。
{% endhint %}
{% endstep %}
{% endstepper %}

### 房间的行为方式

* 被指向的实例会并行回答，并且在同一 turn 内无法看到彼此的回复。
* 每个 turn 之前，每个被指向的实例都会收到自其加入以来在房间中说过但尚未传递给它的内容。这包括其他实例的回复以及你发给其他实例的消息。
* 未被指向的实例在被指向之前不会收到任何内容。
* 同一角色的两个实例是彼此独立的参与者。 `：即要显示给该实例的名称。该字段尽力提供；若不存在，则回退到该 membership 的` 于 `bot-ready` 被做成了房间唯一，以便区分它们；它们的 `/connect` roster 条目共享同一个名称，因此请按 `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`.                                      |

### 后续步骤

{% content-ref url="/pages/bb45ccf71bb5a77477510cd31ad07e93ae57d73e" %}
[群聊消息](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/group-chat-messages.md)
{% endcontent-ref %}

{% content-ref url="/pages/83bfde40c2f58faadfb576aa1fa7ea38a47d20fe" %}
[使用多角色会话](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md)
{% endcontent-ref %}

{% content-ref url="/pages/59c52679f01490a61606cfcd7dd110c8f8fec77f" %}
[服务器到客户端消息](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md)
{% endcontent-ref %}

{% content-ref url="/pages/70d752d330612b0fbf1290588e718236d6a18f0e" %}
[连接 API](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/connect-api.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/build-a-group-chat.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
