# 获取智能体状态 (/zh-CN/ai/build/handle-runtime-events/get-agent-state)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

如果你的 App 希望感知智能体状态变化，可以选择通过 RTM Message 或 RTM Presence 获取状态。本文介绍如何通过 RTM Message 获取智能体状态，并说明该方案与 RTM Presence 方案的差异。

:::info[信息]
- 本文重点介绍如何通过 RTM Message 获取智能体状态。
- 如果你希望使用客户端组件监听状态变化，可以参考[监听智能体事件](/zh-CN/ai/build/handle-runtime-events/event-notifications)。
- 如果你希望通过 RTM Presence 获取状态，可以继续沿用现有 Presence 方案。
:::

## 实现原理 [#实现原理]

ConvoAI 支持通过 RTM Message 向客户端推送智能体状态变化事件。与 RTM Presence 的键值同步方式不同，RTM Message 会将每一次状态变化包装成一条独立的事件消息，便于你记录状态变更时间、进行顺序处理或与其他 RTM Message 事件统一消费。

ConvoAI 推送的状态消息通过 RTM 的消息回调（`onMessageEvent`）下发。消息的内层 `message` 字段是一个 JSON 字符串，需要再次解析才能得到具体的状态事件。对于状态消息，建议你直接根据内层 `message` 中的 `event_type` 字段识别消息类型。

当前通过 RTM Message 推送的智能体状态包括：

- `state.listening`
- `state.thinking`
- `state.speaking`

其工作方式如下：

```text
ConvoAI 状态变化
  ├─ listening -> true / false
  ├─ thinking  -> true / false
  └─ speaking  -> true / false
            ↓
        RTM Message
            ↓
  客户端解析内层 payload 并根据 event_type 识别状态事件
```

### 状态消息类型 [#状态消息类型]

ConvoAI 会在智能体状态变化时发送 RTM Message。每一条消息只对应一个状态变化事件。

当前支持以下事件类型：

| `event_type`      | 说明                           |
| ----------------- | ------------------------------ |
| `state.listening` | 智能体开始或结束聆听用户输入。 |
| `state.thinking`  | 智能体开始或结束处理用户输入。 |
| `state.speaking`  | 智能体开始或结束播报语音。     |

### 消息结构 [#消息结构]

智能体状态消息的内层 `message` 为 JSON 字符串，反序列化后的结构如下：

```json
{
  "event_id": "xxxx",
  "event_type": "state.listening",
  "event_ms": 1611566412672,
  "payload": {
    "value": true,
    "timestamp": 1611566412600
  }
}
```

字段说明如下：

| 字段                | 类型    | 说明                                                                           |
| ------------------- | ------- | ------------------------------------------------------------------------------ |
| `event_id`          | string  | 事件唯一 ID。                                                                  |
| `event_type`        | string  | 状态事件类型，取值为 `state.listening`、`state.thinking` 或 `state.speaking`。 |
| `event_ms`          | integer | 事件发送时间戳，单位为毫秒。                                                   |
| `payload.value`     | boolean | 当前状态值。`true` 表示进入该状态，`false` 表示退出该状态。                    |
| `payload.timestamp` | integer | 状态实际发生时间戳，单位为毫秒。                                               |

### 示例 [#示例]

智能体进入聆听状态时，内层 `message` 反序列化后的示例如下：

```json
{
  "event_id": "a1b2c3d4",
  "event_type": "state.listening",
  "event_ms": 1611566412672,
  "payload": {
    "value": true,
    "timestamp": 1611566412600
  }
}
```

智能体退出说话状态时，内层 `message` 反序列化后的示例如下：

```json
{
  "event_id": "e5f6g7h8",
  "event_type": "state.speaking",
  "event_ms": 1611566420000,
  "payload": {
    "value": false,
    "timestamp": 1611566419900
  }
}
```

## 前提条件 [#前提条件]

开始前，请确保你已经：

- 参考[实现对话式智能体](/zh-CN/ai/get-started/quick-start)完成与智能体交互的基本流程。
- 完成 RTM SDK 的基础集成，并参考[实现收发消息](/zh-CN/realtime-media/rtm/get-started/quick-start)实现消息监听逻辑。
- 在创建智能体时启用 RTM 服务，并将 `data_channel` 设置为 `rtm`。

在开始监听智能体状态消息前，你需要先在创建智能体时启用 RTM 服务，并将数据通道设置为 RTM。

调用 [POST 创建对话式智能体](/zh-CN/api-reference/api-ref/conversational-ai/join)接口时，至少需要包含以下配置：

```json
{
  "properties": {
    "advanced_features": {
      "enable_rtm": true
    },
    "parameters": {
      "data_channel": "rtm"
    }
  }
}
```

## 实现通过 RTM Message 获取智能体状态 [#实现通过-rtm-message-获取智能体状态]

完成 RTM 登录并订阅与智能体同名的频道后，你可以在 RTM 消息回调中解析状态消息。处理逻辑分为三步：

1. 将收到的 RTM `message` 字段反序列化为状态事件对象。
2. 检查反序列化结果中是否包含 `event_type` 和 `payload` 等字段。
3. 根据 `event_type` 分发处理，并使用 `payload.value` 更新业务状态。

### 监听并解析状态消息 [#监听并解析状态消息]

<Tabs defaultValue="android" groupId="language" persist>
<TabsList>
  <TabsTrigger value="android">Android</TabsTrigger>
  <TabsTrigger value="ios">iOS</TabsTrigger>
  <TabsTrigger value="web">Web</TabsTrigger>
</TabsList>

<TabsContent value="android">

```kotlin
val rtmConfig = RtmConfig()
rtmConfig.eventListener = object : RtmEventListener {
    override fun onMessageEvent(event: MessageEvent) {
        // 1. 反序列化内层 message（JSON 字符串）
        val payload = JSONObject(event.message.data)
        if (!payload.has("event_type") || !payload.has("payload")) {
            return
        }
        val eventType = payload.getString("event_type")
        val statePayload = payload.getJSONObject("payload")
        val value = statePayload.getBoolean("value")
        val timestamp = statePayload.getLong("timestamp")

        // 2. 根据 event_type 分发处理
        when (eventType) {
            "state.listening" -> handleListeningChanged(value, timestamp)
            "state.thinking" -> handleThinkingChanged(value, timestamp)
            "state.speaking" -> handleSpeakingChanged(value, timestamp)
        }
    }

}
mRtmClient = RtmClient.create(rtmConfig)

````

</TabsContent>

<TabsContent value="ios">

```swift
func rtmKit(_ rtmKit: AgoraRtmClientKit, didReceiveMessageEvent event: AgoraRtmMessageEvent) {
    // 1. 反序列化内层 message（JSON 字符串）
    guard let jsonString = event.message.stringData,
          let data = jsonString.data(using: .utf8),
          let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
          let eventType = payload["event_type"] as? String,
          let statePayload = payload["payload"] as? [String: Any] else {
        return
    }
    let value = statePayload["value"] as? Bool ?? false
    let timestamp = statePayload["timestamp"] as? Int ?? 0

    // 2. 根据 event_type 分发处理
    switch eventType {
    case "state.listening":
        handleListeningChanged(value, timestamp)
    case "state.thinking":
        handleThinkingChanged(value, timestamp)
    case "state.speaking":
        handleSpeakingChanged(value, timestamp)
    default:
        break
    }
}
````

</TabsContent>

<TabsContent value="web">

```typescript
rtm.addEventListener("message", (event) => {
  // 1. 反序列化内层 message（JSON 字符串）
  const payload = JSON.parse(event.message as string);
  if (!payload.event_type || !payload.payload) {
    return;
  }
  const { event_type, payload: statePayload } = payload;

  // 2. 根据 event_type 分发处理
  switch (event_type) {
    case "state.listening":
      handleListeningChanged(statePayload.value, statePayload.timestamp);
      break;
    case "state.thinking":
      handleThinkingChanged(statePayload.value, statePayload.timestamp);
      break;
    case "state.speaking":
      handleSpeakingChanged(statePayload.value, statePayload.timestamp);
      break;
    default:
      break;
  }
});
```

</TabsContent>
</Tabs>

在上述示例中：

- 内层 `message` 是 JSON 字符串，需要再次反序列化才能得到 `event_type` 和 `payload`。
- `event_type` 用于判断具体的状态类型。
- `payload.value` 表示进入（`true`）或退出（`false`）该状态。
- `payload.timestamp` 可用于精确排序或日志记录。

### 更新业务状态 [#更新业务状态]

你可以根据接收到的事件更新 UI 或内部状态机，例如：

- 收到 `state.listening` 且 `payload.value = true` 时，展示“正在听”。
- 收到 `state.thinking` 且 `payload.value = true` 时，展示“思考中”。
- 收到 `state.speaking` 且 `payload.value = true` 时，展示“说话中”。

## RTM Message 与 RTM Presence 方案差异 [#rtm-message-与-rtm-presence-方案差异]

ConvoAI 同时支持通过 RTM Message 和 RTM Presence 获取智能体状态。两种方案的核心差异如下：

| 对比项   | RTM Presence           | RTM Message                                |
| -------- | ---------------------- | ------------------------------------------ |
| 返回形式 | 状态键值对             | 状态事件消息                               |
| 粒度     | 更适合读取当前状态     | 更适合处理每次状态变化                     |
| 时间信息 | 一般只依赖消息到达时间 | 显式包含 `event_ms` 和 `payload.timestamp` |
| 适用场景 | 简单 UI 状态展示       | 事件驱动、日志记录、统一消息消费           |

可以按以下方式选择：

- 如果你只需要根据智能体当前是否处于 `listening`、`thinking` 或 `speaking` 状态来更新界面，优先使用 RTM Presence，接入更直接。
- 如果你希望将状态变化与字幕、打断、性能指标等其他 RTM Message 一起统一处理，适合使用 RTM Message。
- 如果你需要记录每次状态变化的事件时间，或要对状态切换进行顺序分析，适合使用 RTM Message。

## 开发注意事项 [#开发注意事项]

- RTM 状态消息的内层 `message` 字段是 JSON 字符串，需要再次反序列化后才能读取 `event_type` 和 `payload`。
- 如果你已经通过 RTM Presence 驱动界面状态，切换到 RTM Message 时需要避免两套链路同时更新同一份 UI 状态，以免产生重复更新或状态抖动。
- `state.speaking` 为 `false` 并不一定等于端侧已经停止说话。当前 `speaking` 状态的结束时间由服务端根据 TTS 音频长度估算得出，因此在端侧实际播放结束时间与服务端估算时间存在偏差时，可能出现状态已经切换为 `false`，但端侧仍在播放剩余音频的情况。
- 旧版状态链路中，智能体状态可能通过 RTC Datastream 或 RTM Presence 的聚合态对外暴露。当前文档中的 RTM Message 状态事件，针对的是新状态链路中的 `listening`、`thinking` 和 `speaking` 三类细粒度状态。

:::warning[注意]
本文中的状态消息仅覆盖智能体状态事件，不包含手动 `sos/eos` 指令或相关回调。
:::

## 相关文档 [#相关文档]

- [实现对话式智能体](/zh-CN/ai/get-started/quick-start)
- [监听智能体事件](/zh-CN/ai/build/handle-runtime-events/event-notifications)
- [实时字幕](/zh-CN/ai/build/transcripts)
- [RTM 实现收发消息](/zh-CN/realtime-media/rtm/get-started/quick-start)
- [RTM 添加事件监听](/zh-CN/realtime-media/rtm/build/messaging/add-event-listener)
