> 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/group-chat-messages.md).

# 群聊消息

用于通过 Live APIs 运行群聊房间的管理员客户端收发消息的参考，包括字段、错误和轮次结果。

这些消息只存在于一个使用以下方式创建的房间中 `group_chat: true`。它们通过该房间的 LiveKit 数据通道传输，并与文档中记录的消息一起传输于 [客户端到服务器的消息](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/client-to-server-messages.md) 和 [服务器到客户端的消息](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md) 页面上。只有房间中的人类参与者可以发送 `group-address`；本页称该参与者为 **主持人**。当使用以下方式创建时，一个房间可以容纳不止一名人类 `max_num_participants` ，超过其默认的 `1`；额外的人类按中所述加入 [使用多角色会话](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md#create-and-join-a-room)。封装约定遵循 [消息术语表](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/message-glossary.md)，但有两个不同：群聊命令还携带一个顶层的 `label` 和 `id`，并且 `turn-complete` 到达时没有顶层的 `label` 字段开始的类型。

{% hint style="info" %}
群聊房间仅对已启用该功能的账户可用。
{% endhint %}

### 你发送的命令

#### group-address

将房间定向给一个回合。只有主持人可以发送它，而且只能通过 LiveKit 数据通道发送。

```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": "请每个角色用一句话介绍自己。"
  }
}
```

| 字段                           | 输入      | 必填            | 描述                                                                                                                                                        |
| ---------------------------- | ------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `label`                      | 字符串     | 否             | 常规 RTVI 标签， `"rtvi-ai"`。服务器会忽略它；发送它只是为了与其他客户端消息保持一致。                                                                                                      |
| `id`                         | 字符串     | 是             | 命令 id，1–128 个字符。在该房间内你发送的每个命令中必须唯一； `group-address`, `interaction-target`，并且 `character-roster-update` 共享同一个 id 空间。                                       |
| `data.room_session_id`       | UUID    | 是             | 房间，来自 `/connect` 响应。                                                                                                                                      |
| `data.mode`                  | 字符串     | 是             | 定向模式： `"all"`, `"tagged"`，或者 `"open"`。请参见下方表格。                                                                                                            |
| `data.target_membership_ids` | UUID\[] | `"tagged"` 仅限 | 至少一个在线 `membership_id`，且不含重复。若为 `"all"` 和 `"open"` 则省略它——使用任一模式发送的列表都会被忽略，而该回合仍会定向到房间中的每个实例。                                                              |
| `data.expected_route_epoch`  | 整数      | 是             | 你持有的最新 `route_epoch` ，来自 `/connect` 或来自 `extras.route_epoch` 上一次 `group-address`, `interaction-target`，或者 `character-roster-update` 响应的结果。移除当前活动实例会提升该纪元。 |
| `data.text`                  | 字符串     | 是             | 主持人的消息。                                                                                                                                                   |

**定向模式**

| `mode`     | 被指向的人                             | 必须回复                 |
| ---------- | --------------------------------- | -------------------- |
| `"all"`    | 房间中的每个角色实例。                       | 是。拒绝会被报告为失败。         |
| `"tagged"` | 仅限列在中的实例 `target_membership_ids`. | 是。拒绝会被报告为失败。         |
| `"open"`   | 房间中的每个角色实例。                       | 否。实例可以拒绝，此时会被报告为已通过。 |

**服务器响应额外字段**

服务器对每个 `group-address` 都只返回一个 `server-response` 携带 `event_type: "group-address"`。对于它接受的命令，该响应会在所有被定向的实例完成后到达；被拒绝的命令会立即以 `status: "error"`进行回复。一个回合还会产生 `server-response` 你未发送的消息，其中带有 `event_type` `"context-update"` 或 `"user_text_message"`，每个被定向实例可能各一条；忽略任何 `server-response` 其 `event_type` 不是你自己的命令之一的消息。对于 `status: "success"`, `extras` 携带：

| 字段                        | 输入      | 描述                                              |
| ------------------------- | ------- | ----------------------------------------------- |
| `command_id`              | 字符串     | 该 `id` 从请求中回显。                                  |
| `room_session_id`         | UUID    | 该房间。                                            |
| `mode`                    | 字符串     | 本次回合使用的定向模式。                                    |
| `route_epoch`             | 整数      | 房间当前的纪元。将此值作为 `expected_route_epoch` 发送到下一条命令中。 |
| `target_membership_ids`   | UUID\[] | 本次回合定向到的实例。                                     |
| `answered_membership_ids` | UUID\[] | 产生回复的实例。                                        |
| `passed_membership_ids`   | UUID\[] | 拒绝的实例。只有一个 `"open"` 回合会在这里产生条目。                 |
| `failed_membership_ids`   | UUID\[] | 未产生回复的实例。超时会在这里报告。                              |
| `summary.turn_id`         | 字符串     | 回合标识符，等于 `command_id`.                          |
| `summary.addressed`       | 整数      | 被定向的实例数量。                                       |
| `summary.answered`        | 整数      | 回复的实例数量。                                        |
| `summary.passed`          | 整数      | 拒绝的实例数量。                                        |
| `summary.failed`          | 整数      | 未回复的实例数量。                                       |
| `summary.cancelled`       | 布尔值     | 存在于每个摘要中。取消无法触达，因此该值始终为 `false`.                |
| `summary.route_epoch`     | 整数      | 回合开始时捕获的路由纪元。                                   |

`status: "success"` 表示回合已结束，而不是表示有人回复了。即使回合中每个实例都失败或超时，仍会返回 `成功`。请阅读 `failed_membership_ids` 以了解发生了什么。

在 `status: "error"`, `extras` 携带 `代码`。房间本身抛出的错误—— `stale_route_epoch`, `invalid_target_membership`, `duplicate_target_membership`, `empty_target_set` 在一个 `"all"` 或 `"open"` 回合中， `turn_in_progress`, `turn_wait_timeout`，并且 `command_id_conflict` ——也携带 `command_id`, `room_session_id`, `active_membership_id`，并且 `route_epoch`。在命令到达房间之前就被拒绝的错误—— `not_roster_room`, `not_group_chat_room`, `unauthorized_sender`, `invalid_command`, `wrong_room`, `invalid_route_epoch`, `empty_message`, `empty_target_set` 在一个 `"tagged"` 回合中，以及 `coordinator_unavailable` ——只携带 `代码` ，因此要把它们和你尚未完成的命令关联，而不是和 `extras.command_id`。失败的 `group-address-part` 帧会以 `代码` 和 `command_id`回复。意外的服务器端故障会以 `status: "error"` 和一个 `消息` 但没有 `extras`，因此请读取 `extras.code` 带有可选访问并回退到 `消息`.

**错误代码**

| `代码`                          | 当                                                                                                         | 该怎么做                                                          |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `not_roster_room`             | 该房间是使用以下方式创建的 `character_id`来表示移除，而不是 `characters`.                                                       | 使用以下方式创建该房间 `characters`.                                     |
| `not_group_chat_room`         | 该房间是在没有 `group_chat: true`.                                                                               | 新建一个带有 `group_chat: true`.                                    |
| `unauthorized_sender`         | 发送者不是该房间中的人类参与者。                                                                                          | 从人类参与者的连接发送该命令。                                               |
| `invalid_command`             | 在一个 `group-address`, `id` 缺失或超过 128 个字符时， `data` 不是对象，或者 `mode` 不是这三个值之一；或者一个 `group-address-part` 帧格式错误。 | 修正消息并用新的 `id`.                                                |
| `wrong_room`                  | `data.room_session_id` 不是该房间。                                                                             | 使用 `room_session_id` 来自 `/connect`.                           |
| `invalid_route_epoch`         | `expected_route_epoch` 不是整数。                                                                              | 发送整数。                                                         |
| `stale_route_epoch`           | `expected_route_epoch` 与房间的纪元不匹配。                                                                         | 从 `extras.route_epoch` 从此响应中移除并用新的 `id`.                      |
| `empty_message`               | `文本` 为空。                                                                                                  | 发送文本。                                                         |
| `empty_target_set`            | `"tagged"` 是在没有目标的情况下发送的，或者 `"all"` / `"open"` 发送到了一个没有在线实例的房间。                                           | 至少列出一个 `membership_id`，或者等待某个实例变为就绪。                          |
| `invalid_target_membership`   | 目标不是该房间中的在线 `membership_id` ，或者不是 UUID。                                                                   | 使用 `membership_id` 中的值， `/connect`, `bot-ready`，或者 roster 响应。 |
| `duplicate_target_membership` | 相同的 id 在 `target_membership_ids`.                                                                         | 移除重复项。                                                        |
| `turn_in_progress`            | 由于有一个回合正在运行，你的命令被立即拒绝。                                                                                    | 等待该 turn 的 `server-response`，然后再重新发送。                         |
| `turn_wait_timeout`           | 你的命令等待了正在运行的回合，但该回合在回合超时加上一个很短的余量内仍未结束，默认大约两分钟。                                                           | 重新发送。                                                         |
| `command_id_conflict`         | 该 `id` 被以不同内容重复使用。                                                                                        | 使用新的 `id`.                                                    |
| `coordinator_unavailable`     | 该房间已不再可服务。                                                                                                | 创建一个新房间。                                                      |

**幂等性**

重新发送相同的 `id` 且内容相同会被静默接受，不会产生第二个响应。不要等待第二个响应；请依赖第一个响应。重新发送相同的 `id` 则会被拒绝，并返回 `command_id_conflict`.

**一次只进行一个回合**

一个房间一次只运行一个回合。在回合运行时发送第二个 `group-address` 会被以 `turn_in_progress` 拒绝，并且不会被缓存，因此请在该回合的之后重试 `server-response`. `interaction-target` 和 `character-roster-update` 在回合期间也会以相同代码被拒绝。

***

#### group-address-part

携带一个 `group-address` 的帧，而该帧太大，无法一次发送完。LiveKit 数据通道拒绝超过 15,360 字节的消息。

```json
{
  "label": "rtvi-ai",
  "type": "group-address-part",
  "id": "turn-0002",
  "data": {
    "command_id": "turn-0002",
    "index": 0,
    "count": 3,
    "payload": "eyJsYWJlbCI6InJ0dmktYWkiLCJ0eXBl..."
  }
}
```

| 字段                | 输入  | 必填 | 描述                                              |
| ----------------- | --- | -- | ----------------------------------------------- |
| `label`           | 字符串 | 否  | 常规 RTVI 标签， `"rtvi-ai"`。服务器会忽略它。                |
| `id`              | 字符串 | 是  | 该 `group-address` 正在发送中的命令 id，1–128 个字符。每一帧都相同。 |
| `data.command_id` | 字符串 | 否  | 当存在时，它必须等于 `id`.                                |
| `data.index`      | 整数  | 是  | 此帧的从零开始的位置，来自 `0` 设置为 `count - 1`.              |
| `data.count`      | 整数  | 是  | 帧的总数， `1` 设置为 `64`。每一帧都相同。                      |
| `data.payload`    | 字符串 | 是  | 完整 JSON 的一个 base64 切片。 `group-address` JSON。    |

**分块规则**

* 先把完整的 JSON 消息进行一次 base64 编码，然后将该字符串拆分到各个帧中；每个 `group-address` payload `都是其中的一段。` 服务器按
* 顺序拼接这些切片，并只解码一次。 `index` 。
* 只有最后一段可以携带 `=` 填充。服务器在解码前会先拼接切片，因此除结尾之外任何地方出现填充都会导致整个命令失败。可以在任意位置拆分编码字符串；服务器会原样重建它。切勿分别对每一帧单独进行 base64 编码，因为那会把填充放在中间并导致整个命令失败。
* 单个命令的 base64 载荷总计最多 1 MiB，约合 768 KiB 的 `group-address` JSON。
* 格式错误或超大帧会使该命令失败，错误为 `event_type: "group-address"`, `status: "error"`，并且 `code: "invalid_command"`。不过有两种失败是静默的：如果某帧的 `id` 缺失或超过 128 个字符，则该帧会被丢弃，因为没有可用于关联回复的内容；如果一个命令在首帧后的 1 分钟内仍未完成，则这条部分送达的命令会被放弃。在这两种情况下，回合都不会结束，因此请在客户端侧超时，并及时发送一个命令的所有帧。
* 重新组装后的消息会被按完全相同的方式处理，就像一个 `group-address` 随该 `id`.

***

### 你接收的消息

#### turn-complete

结束一个群聊回合并报告其结果。 `turn-complete` 报告整个群聊回合； `bot-turn-completed` 是按角色区分的，并且在群聊房间中保持不变。

```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"
      ],
      "answered_membership_ids": [
        "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
        "dddddddd-dddd-4ddd-8ddd-dddddddddddd"
      ],
      "passed_membership_ids": [],
      "failed_membership_ids": [],
      "turn_id": "turn-0001",
      "addressed": 2,
      "answered": 2,
      "passed": 0,
      "failed": 0,
      "cancelled": false,
      "route_epoch": 0
    }
  }
}
```

| 字段                         | 输入      | 描述                                                                 |
| -------------------------- | ------- | ------------------------------------------------------------------ |
| `room_session_id`          | UUID    | 该房间。                                                               |
| `command_id`               | 字符串     | 该 `id` 的 `group-address` 开启该回合的。                                   |
| `mode`                     | 字符串     | 本次回合使用的定向模式。                                                       |
| `addressed_membership_ids` | UUID\[] | 本次回合定向到的实例。命名为 `target_membership_ids` 在 `server-response` extras。 |
| `answered_membership_ids`  | UUID\[] | 产生回复的实例。                                                           |
| `passed_membership_ids`    | UUID\[] | 拒绝的实例。只有一个 `"open"` 回合会在这里产生条目。                                    |
| `failed_membership_ids`    | UUID\[] | 未产生回复的实例。未在回合超时内完成的实例会列在这里。                                        |
| `turn_id`                  | 字符串     | 回合标识符，等于 `command_id`.                                             |
| `addressed`                | 整数      | 被定向的实例数量。                                                          |
| `answered`                 | 整数      | 回复的实例数量。                                                           |
| `passed`                   | 整数      | 拒绝的实例数量。                                                           |
| `failed`                   | 整数      | 未回复的实例数量。                                                          |
| `cancelled`                | 布尔值     | 出现在每个 `turn-complete`中。取消无法触达，因此该值始终为 `false`.                     |
| `route_epoch`              | 整数      | 回合开始时捕获的路由纪元。                                                      |

此消息的四个属性会影响客户端如何消费它：

* `turn-complete` 没有顶层的 `label`，不同于大多数服务器消息。不要根据 `label`.
* `turn-complete` 对传入消息进行过滤；每个回合最多发送一次，不论房间中有多少个角色实例。它通过某个实例的连接发布，且不会重试，因此请把 `server-response` 视为回合可靠结束。其 `data` 还可能携带发布该消息的实例的身份字段；忽略它们并读取列表。
* `turn-complete` 可能会在实例最后一个 [`bot-llm-text`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#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)之前到达。之后继续接受一小段宽限期内的回复文本——实际上 1 到 2 秒就足够。
* `route_epoch` 这里指的是回合的快照，而不是下一次要发送的值。发送 `extras.route_epoch` 来自 `server-response` 作为下一个 `expected_route_epoch`.

***

#### 按角色消息上的身份字段

在使用以下方式创建的房间中的服务器消息 `characters` 数组（每个群聊房间都是如此）携带六个身份字段，用来将它们与某个角色实例关联： `room_session_id`, `membership_id`, `character_id`, `character_session_id`, `participant_identity`，并且 `is_initial`。这些字段的位置取决于消息的形状。已经携带 `data` 对象的消息会在该对象内部接收它们。扁平消息会在为它们创建的 `data` 对象中接收它们，从而产生一个嵌套的 `data.data` 路径。

| 消息                                                                                                                                           | 身份字段的位置                         |
| -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| [`bot-llm-text`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#bot-llm-text)                       | `data`                          |
| [`bot-llm-started`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#bot-llm-started-bot-llm-stopped) | `data`                          |
| [`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) | `data`                          |
| `bot-ready`                                                                                                                                  | `data.about`，映射在 `data`         |
| `character-status`                                                                                                                           | `data`                          |
| `character-removed`                                                                                                                          | `data`                          |
| [`llm-no-response`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#llm-no-response)                 | 嵌套的 `data` 对象                   |
| [`interaction-created`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#interaction-created)         | 嵌套的 `data` 对象                   |
| [`server-response`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#server-response)                 | 嵌套的 `data` 对象；有用的字段保留在 `extras` |

每个实例都会将其回复作为普通 `bot-llm-text` 消息流式发送。按 `data.membership_id`. `bot-llm-stopped` 来标识该回复。

```json
{
  "label": "rtvi-ai",
  "type": "bot-llm-text",
  "data": {
    "text": "当然，马上到。",
    "room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
    "membership_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
    "character_id": "11111111-1111-4111-8111-111111111111",
    "character_session_id": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
    "participant_identity": "CHARACTER_PARTICIPANT_1",
    "is_initial": true
  }
}
```

不要也消费 `bot-transcription`；它重复了相同的回复文本。

一个拒绝 `"open"` 回合会用 `llm-no-response` 和 `reason: "abstain"`来宣布它。由于该消息是扁平的，其身份字段位于一个嵌套的 `data` 对象下：

```json
{
  "label": "rtvi-ai",
  "type": "server-message",
  "data": {
    "type": "llm-no-response",
    "reason": "abstain",
    "data": {
      "room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
      "membership_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",
      "character_id": "22222222-2222-4222-8222-222222222222",
      "character_session_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
      "participant_identity": "CHARACTER_PARTICIPANT_2",
      "is_initial": false
    }
  }
}
```

`turn-complete` 才是权威，而不是这条消息。如果实例在拒绝之前流式发送了任何可见文本，那么以回复为准，该实例会列在 `answered_membership_ids`中。实例也可能列在 `passed_membership_ids` 中而从未发送过 `llm-no-response`中。将 `llm-no-response` 视为提示，并根据 `turn-complete` 列表来确定结果。

***

#### 群聊房间中的 bot-ready

`bot-ready` 是中所描述的消息 [映射每个角色实例](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/multi-character-sessions.md#map-each-character-instance)。在群聊房间中，它的 `data.about` 对象通常除了那里记录的字段外还会携带一个字段： `：即要显示给该实例的名称。该字段尽力提供；若不存在，则回退到该 membership 的`，用于显示该实例的名称。该字段只是尽力而为。若它缺失，则回退到该实例的 `：即要显示给该实例的名称。该字段尽力提供；若不存在，则回退到该 membership 的` 上的 `/connect` roster 条目，该条目携带的是角色的纯名称，不含下一步所述的房间唯一后缀，因此请自行消歧回退值。

显示名称在房间内会被设为唯一：名为 `Ada` 的第二个实例会被赋予显示名称 `Ada (2)`。一个 `character-status` 消息带有 `status: "ready"` 在其 `data` 对象中以平面形式携带相同字段。

### 相关页面

{% content-ref url="/pages/15180836560fd90ff696290b278b06e6bf755122" %}
[构建群聊](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/build-a-group-chat.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/138cedadc568671d93b18cf90042de5055cbffd5" %}
[客户端到服务器消息](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/client-to-server-messages.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 %}


---

# 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/group-chat-messages.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.
