> 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，每个都只是一个裸 UUID，没有 `-draft`, `-latest`或版本后缀
* 一个稳定、非空的 `end_user_id` 用于每个人类参与者
* 一个能够发送可靠数据消息并接收数据消息的 LiveKit 客户端

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

### 运行群聊

{% stepper %}
{% step %}

#### 创建房间

发送一个有序的 `角色` 数组到 `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) 文档 `角色`, `group_chat`，以及 `room_brief` 完整地。

此响应是缩短版：它展示了三条名册条目中的两条，省略了其余标准 `/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`，第一个 `群组点名` 携带 `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` ：该实例要显示的名称。该字段尽力而为提供。当它缺失时，回退到 `display_name` 该成员资格的 `/connect` 名册条目。对于克隆，两种来源不同： `bot-ready` 包含一个房间唯一的标签，因此名为 `Ada` 的第二个实例会显示为 `Ada (2)`，而两个实例在 `Ada` 的 `/connect` 名册条目上共享同一个普通名称。请按 `membership_id`来为转写建立索引，而不是按名称。
{% endstep %}

{% step %}

#### 对房间点名

发送 `群组点名` 通过 LiveKit 数据通道作为可靠数据消息发送。它使用 [客户端到服务器消息](/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` 是你持有的最新轮次。第一次命令时它是 `route_epoch` 来自 `/connect`；之后它是 `extras.route_epoch` 来自上一条 `群组点名`, `interaction-target`，或 `character-roster-update` 响应。为每条命令指定一个 `id` 长度为 1 到 128 个字符且在 `群组点名`, `interaction-target`，以及 `character-roster-update`之间唯一，因为这三者共享同一个 id 空间。使用相同内容重新发送一个 `id` ，如果内容相同，会被静默接受且不会产生第二个响应，因此不要等待第二个响应。重新发送一个 `id` ，如果内容不同，则会被拒绝并返回 `command_id_conflict`。请参阅 [`群组点名`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/group-chat-messages.md#group-address).

`模式` 决定谁必须回答：

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

一个 `tagged` 回合通过以下方式命名这些实例： `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，你会先添加哪个控件？"
  }
}
```

一个房间中一次只运行一个回合。请在当前回合的 `群组点名` 之后发送下一条 `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`，它会重复相同的文本并让每个回复翻倍。群聊本身不会添加任何带文本的消息；回复会出现在标准的机器人输出消息中。

拒绝一个 `open` 回合的实例通常会通过 [`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` 结束回合。它每个回合最多发送一次，包裹在 `server-message` 信封中，如 [回合生命周期与消息顺序](/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
    }
  }
}
```

`已点名`, `已回答`, `已放弃`，以及 `失败` 始终等于对应列表的长度。

`turn-complete` 会经过某个实例的连接，因此其 `data` 也可能携带该实例的身份字段；忽略它们并读取这些列表。它不会重试，因此在少数情况下可能丢失；请将 `server-response` 下方视为回合可靠结束信号。它也可能在某个实例最后一条 `bot-llm-text` 增量之前到达，因此在其后再保留一小段宽限时间继续接受回复文本。实践中一到两秒就足够了。

每个场景 [`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"` 表示回合已敲定，而不是有人回答了，所以在展示结果之前先读取 `failed_membership_ids` 。实例超时会被列为失败，且回合默认在 120 秒后超时。 `所有` 或 `tagged` 下的拒绝也会被报告为失败；只有 `open` 会产生 `passed_membership_ids`.

存储 `extras.route_epoch` 并将其作为 `expected_route_epoch` 发送到下一条命令中。不要发送 `route_epoch` 来自 `turn-complete`，它是回合自身的快照。
{% endstep %}

{% step %}

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

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

群聊房间增加了两条规则。该命令会被拒绝，并返回 `turn_in_progress` ，因此请在回合的 `server-response`。移除当前活动实例会提升 `route_epoch`route\_epoch `route_epoch` 从 roster-update 响应中获取，并将其作为 `expected_route_epoch` 下一次 `群组点名`发送。新实例会继承房间的设置及其简报，并在其第一次被点名的回合中接收自它加入以来发生的内容。它不会获得加入之前的对话，因此新来者需要知道的内容都要重新说明。
{% endstep %}

{% step %}

#### 结束会话

使用以下方式关闭房间： `session_id` ，来自 create 响应。它属于初始角色实例，将其传给 `/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 %}

### 房间如何运作

* 被点名的实例会并行回答，且在同一回合中彼此看不到对方的回复。
* 在每个回合之前，每个被点名的实例都会收到自它加入以来在房间中说过但尚未发给它的内容。这包括其他实例的回复以及你发给其他实例的消息。
* 未被点名的实例在点到它的那个回合之前什么也收不到。
* 同一角色的两个实例是不同的参与者。 `display_name` 在 `bot-ready` 会被设为房间唯一，因此能将它们区分开；它们的 `/connect` 名册条目共享同一个名称，因此请按 `membership_id`.

### 验证和排障

在发送回合之前，请确认：

* 每个预期实例都有一个唯一的 `membership_id`.
* 每个可用实例都已发出 `bot-ready`.
* 你即将作为 `expected_route_epoch` 发送的轮次是你收到的最新一个。

| 症状                              | 可能原因                                          | 修复方法                                                                                   |
| ------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------- |
| `群组点名` 返回 `not_group_chat_room` | 房间创建时未启用 `group_chat: true`.                  | 使用以下设置创建新房间： `group_chat: true`。该设置在创建时即固定。                                            |
| `群组点名` 返回 `stale_route_epoch`   | `expected_route_epoch` 与房间当前轮次不匹配。            | 读取 `extras.route_epoch` 从错误响应中获取，然后使用新的 `id`.                                          |
| `群组点名` 返回 `turn_in_progress`    | 房间中仍有一个回合在运行。                                 | 等待该回合的 `server-response`，然后再次发送该命令。                                                    |
| 重新发送的 `群组点名` 不会收到响应             | 该 `id` 被以相同内容重复使用并已去重。                        | 请依赖第一次响应；使用新的 `id`.                                                                    |
| 某个实例从不发出 `bot-ready`            | 它的配置失败或仍在进行中。                                 | 检查 `partial_dispatch`, `provisioning_status`，以及 `failure_code` 出现在 create 响应中；不要点名该实例。 |
| 被点名的实例从不回复                      | 它失败了，或者未能在回合超时前完成。                            | 读取 `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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
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=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
