> 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` 响应。移除当前活动实例会使该轮次的 epoch 增加。 |
| `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`             | 整数      | 房间当前的 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`       | 布尔值     | 每个 summary 中都会出现。取消不可达，因此其值始终为 `false`.             |
| `summary.route_epoch`     | 整数      | 轮次开始时捕获的 route epoch。                               |

`status: "success"` 表示轮次已结束，而不是有人回答了。在所有实例都失败或超时的轮次中，仍会返回 `success`。请阅读 `failed_membership_ids` 以了解发生了什么。

当 `status: "error"`, `extras` 携带 `code`。由房间本身抛出的错误—— `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` ——携带 `code` 仅此而已，因此应将它们与当前未完成的命令关联，而不是与 `extras.command_id`。失败的 `group-address-part` 帧会以 `code` 和 `command_id`。意外的服务器端故障会以 `status: "error"` 和一个 `消息` 但没有 `extras`， `extras.code` 进行可选访问，并回退到 `消息`.

**错误代码**

| `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` 与房间的 epoch 不匹配。                                                               | 读取 `extras.route_epoch` 自此响应并使用新的 `id`.                            |
| `empty_message`               | `text` 为空。                                                                                           | 请发送文本。                                                             |
| `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`            | 由于有轮次正在运行，你的命令被立即拒绝。                                                                                 | 等待该轮次的 `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`              | 字符串 | 是  | 该命令的 command id `group-address` 正在发送，1–128 个字符。每个帧都相同。 |
| `data.command_id` | 字符串 | 否  | 存在时，它必须等于 `id`.                                        |
| `data.index`      | 整数  | 是  | 此帧从 0 开始的序号，范围从 `0` 到 `count - 1`.                     |
| `data.count`      | 整数  | 是  | 帧总数， `1` 到 `64`。每个帧都相同。                                |
| `data.payload`    | 字符串 | 是  | 完整内容的一个 base64 片段 `group-address` JSON。                |

**分块规则**

* 先将完整的 `group-address` JSON 消息进行一次 Base64 编码，然后将该字符串分割到各个帧中；每个 `payload` 都是其中一个切片。
* 服务器会按 `index` 顺序拼接这些切片，并只解码一次。
* 只有最后一个切片可以携带 `=` 填充。服务器会先拼接这些切片再解码，因此只要填充出现在末尾以外的位置，整个命令就会失败。编码字符串可以在任意位置切分；服务器会精确重建它。切勿分别对每个帧进行 base64 编码，因为那会把填充放在中间并导致整个命令失败。
* 单个命令的 base64 载荷总计最多 1 MiB，大约相当于 768 KiB 的 `group-address` JSON。
* 格式错误或超大帧会使该命令失败，并返回 `event_type: "group-address"`, `status: "error"`，并且 `code: "invalid_command"`。另外有两种失败是静默的：某个帧的 `id` 缺失或超过 128 个字符时会被丢弃，因为没有可用于关联回复的内容；如果一个命令在其首帧后一分钟内未能完成，则会被放弃。在这两种情况下，轮次都不会完成，因此应在客户端侧超时，并及时发送一个命令的每个帧。
* 重组后的消息会被处理得与一个 `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` 附加项。 |
| `answered_membership_ids`  | UUID\[] | 产生回复的实例。                                                       |
| `passed_membership_ids`    | UUID\[] | 拒绝的实例。只有 `"open"` 轮次会在这里产生条目。                                  |
| `failed_membership_ids`    | UUID\[] | 未产生回复的实例。在回合超时内尚未完成的实例会列在这里。                                   |
| `turn_id`                  | 字符串     | 轮次标识符，等于 `command_id`.                                         |
| `已回应`                      | 整数      | 被寻址的实例数量。                                                      |
| `已回答`                      | 整数      | 回复的实例数量。                                                       |
| `通过`                       | 整数      | 拒绝的实例数量。                                                       |
| `失败`                       | 整数      | 未回复的实例数量。                                                      |
| `已取消`                      | 布尔值     | 存在于每个 `turn-complete`。取消状态不可达，因此该值始终为 `false`.                 |
| `route_epoch`              | 整数      | 轮次开始时捕获的 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) 增量之前以及在 [`server-response`](/api-docs/zh/api-can-kao/core-api-reference/live-apis-beta/server-to-client-messages.md#server-response)之前到达。请在它之后继续接受一小段宽限期内的回复文本——实际上一到两秒就足够。
* `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": "Sure, on my way.",
    "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` 对象通常除了那里记录的字段外，还会携带一个字段： `display_name`，即要显示给该实例的名称。该字段尽力提供。若不存在，则回退到该实例的 `display_name` 在该实例的 `/connect` 名册项，它携带角色的纯名称，不包含下文所述的房间唯一后缀，因此请自行消歧该回退值。

显示名称在房间内保持唯一：名为 `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 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/group-chat-messages.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.
