> 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/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/session-lifecycle.md).

# 会话生命周期

每个 `ConvaiCharacter` 场景中的都与 Convai 保持一个独立会话。该会话会跟踪角色是否已连接、当前状态是什么，以及——在启用持久化时——你上次连接时处于哪段对话。理解会话如何创建、持久化和恢复，可以让你在训练模拟、交互式体验和游戏中构建可靠、可恢复的角色交互。

***

### 会话状态机

每个角色会话会经历以下状态。

```mermaid
stateDiagram-v2
    [*] --> 未连接

    未连接 --> 连接中：已发起连接
    连接中 --> 已连接：连接已建立
    连接中 --> 错误：配置或身份验证失败

    已连接 --> 正在断开连接：已发起断开连接
    正在断开连接 --> 未连接：正常关闭

    已连接 --> 重新连接中：连接丢失
    重新连接中 --> 已连接：重连成功
    重新连接中 --> 错误：超过最大尝试次数
```

| 状态          | 值 | 含义                        |
| ----------- | - | ------------------------- |
| `未连接`       | 0 | 没有活动会话。初始状态，以及正常断开后的最终状态。 |
| `连接中`       | 1 | 连接尝试进行中。从未连接过渡到已连接。       |
| `Connected` | 2 | 会话处于活动状态。音频流和对话都在运行。      |
| `重新连接中`     | 3 | 连接已丢失。SDK 正在尝试自动重新建立连接。   |
| `正在断开连接`    | 4 | 正在进行优雅关闭。从已连接过渡到未连接。      |
| `错误`        | 5 | 不可恢复的错误。需要人工干预才能重新连接。     |

你会通过以下方式接收状态转换： `SessionStateChangedRelayData` 事件，通过 `ConvaiSessionEventRelay`。参见 [事件系统](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/event-system.md) 来了解如何订阅。

***

### 按角色的会话

每个 `ConvaiCharacter` 都有自己独立的会话。会话不会在角色之间共享。在多角色场景中，每个角色都会独立连接和断开——会话 ID 绑定到 Inspector 中设置的角色 ID 字符串（而不是场景名或对象名），重连策略按角色生效，并且某个角色上的会话错误不会影响其他角色。

`ConvaiSessionData` 是持久化会话存储，它将每个角色映射到其当前会话标识。它会在启动时自动从磁盘加载，并在每次更改时写入到 `{Application.persistentDataPath}/Convai/sessions.json` ——会话 ID 无需任何额外设置即可在应用重启后保留。

| 方法                                       | 描述                               |
| ---------------------------------------- | -------------------------------- |
| `GetSessionId(characterId)`              | 返回该角色当前的会话 ID，或者 `null` （如果不存在）。 |
| `StoreSessionId(characterId, sessionId)` | 为该角色存储一个会话 ID，并立即保存到磁盘。          |
| `ClearSessionId(characterId)`            | 移除某个角色的会话 ID 并保存。                |
| `ClearAllSessionIds()`                   | 移除所有已存储的会话 ID 并保存。               |
| `GetAllSessionIds()`                     | 返回所有当前角色→sessionId 映射的只读快照。      |

{% hint style="info" %}
`ConvaiSessionData` 是一个单例。数据存储在 `{Application.persistentDataPath}/Convai/sessions.json` ，并在应用重启后持续保留。如果你需要从头开始，请显式调用 `ClearAllSessionIds()` 。
{% endhint %}

***

### 会话持久化

当会话 ID 被持久化后，SDK 可以在下一次连接时恢复之前的对话——角色会记住先前交互的上下文。

#### 哪些会持久化，哪些会重置

| 重连时        | 行为                                    |
| ---------- | ------------------------------------- |
| 会话 ID      | 通过以下方式持久化： `ConvaiSessionData` ——支持恢复 |
| 对话历史       | 由 Convai 管理；当会话 ID 有效时会恢复             |
| 进行中的音频     | 重置——任何流中的音频都会被丢弃                      |
| 活动轮次状态     | 重置——该轮次会重新干净开始                        |
| 模块状态（例如情绪） | 重置——模块会在重连时重新初始化                      |

#### 默认持久化栈

SDK 通过以下方式提供可插拔的持久化层： `ISessionPersistence` ，适用于需要自定义后端存储的项目（加密存储、云存档、数据库）。默认栈如下：

```
ISessionPersistence
  └─ KeyValueStoreSessionPersistence        ← 使用前缀 "convai.session." 将 characterId 映射到 sessionId
       └─ PlayerPrefsKeyValueStore           ← 默认的 IKeyValueStore 实现；封装了 Unity PlayerPrefs
            └─ UnityEngine.PlayerPrefs       ← 持久化到磁盘
```

会话 ID 以如下格式的键存储： `convai.session.<characterId>`.

#### 替换持久化存储

实现 `IKeyValueStore` 即可使用任何后端存储——数据库、加密存储、云存档系统。 `PlayerPrefsKeyValueStore` 会在内部将所有读写调度到 Unity 主线程；如果你的后端存储有线程限制，也请采用同样的线程安全模式。

```csharp
public sealed class SecureKeyValueStore : IKeyValueStore
{
    public string GetString(string key, string defaultValue = null)
    {
        return SecureStorage.GetValue(key) ?? defaultValue;
    }

    public void SetString(string key, string value)
    {
        SecureStorage.SetValue(key, value);
    }

    public bool HasKey(string key) => SecureStorage.HasKey(key);

    public void DeleteKey(string key) => SecureStorage.DeleteKey(key);

    public void Save() => SecureStorage.Flush();
}
```

通过以下方式注册： `ConvaiRuntimeBuilder`:

```csharp
var runtime = new ConvaiRuntimeBuilder()
    .UsePersistence(new MyPersistenceProvider(new SecureKeyValueStore()))
    .Build();
```

***

### 重连策略

`ReconnectPolicy` 控制当连接意外断开时 SDK 的行为。

| 字段                         | 类型             | 默认值                | 描述                                                |
| -------------------------- | -------------- | ------------------ | ------------------------------------------------- |
| `RoomRejoinTtlSeconds`     | `double`       | `60`               | SDK 在断开后可以重新加入现有房间的时间窗口（秒）。超过该窗口后，会创建一个新房间。       |
| `ResumePolicy`             | `ResumePolicy` | `ResumeIfPossible` | 控制 SDK 是否尝试通过以下方式恢复之前的对话： `character_session_id`. |
| `MaxReconnectAttempts`     | `int`          | `3`                | 在会话进入以下状态之前允许的自动重连最大次数： `错误` 状态的聊天机器人。            |
| `SpawnAgentOnRejoin`       | `bool`         | `true`             | 重新加入现有房间时是否重新生成 AI 代理。                            |
| `StartWaitTimeoutMs`       | `int`          | `5000`             | 连接 `Start()` 阶段在被视为失败之前的超时时间（毫秒）。                 |
| `AutoMicStartDelaySeconds` | `float`        | `0.5`              | 连接后等待多长时间再启动麦克风（秒）。可防止在会话完全就绪前捕获音频。               |

#### `ResumePolicy` 选项

| 值                  | 行为                               |
| ------------------ | -------------------------------- |
| `AlwaysFresh`      | 始终开启全新对话。角色不会记住上一个会话。            |
| `ResumeIfPossible` | 尝试恢复之前的对话。如果会话已过期或恢复失败，则回退到全新会话。 |
| `AlwaysResume`     | 始终恢复。如果恢复失败，连接就会失败——不会回退到全新会话。   |

#### 预设策略

| 预设                                | 描述                                             |
| --------------------------------- | ---------------------------------------------- |
| `ReconnectPolicy.Default`         | 60 秒 TTL， `ResumeIfPossible`，3 次尝试，麦克风延迟 0.5 秒 |
| `ReconnectPolicy.AlwaysCreateNew` | 不尝试重新加入。始终创建新房间和全新会话。                          |

```csharp
var policy = new ReconnectPolicy(
    roomRejoinTtlSeconds: 120,
    resumePolicy: ResumePolicy.AlwaysFresh,
    maxReconnectAttempts: 5,
    autoMicStartDelaySeconds: 1.0f
);
```

{% hint style="warning" %}
`AlwaysResume` 会将会话置于 `错误` 状态，如果 Convai 无法恢复会话（例如会话已在后端过期）。除非你的训练模拟要求严格连续性，并且你已经明确处理了错误状态，否则请使用 `ResumeIfPossible` 。
{% endhint %}

***

### 使用示例

#### 示例 1：医疗训练模拟——网络中断后恢复

学习者正在评估中途时网络中断。连接恢复后，患者角色会恢复同一段对话——不会丢失上下文。

```csharp
var policy = new ReconnectPolicy(
    roomRejoinTtlSeconds: 120,          // 重新加入现有房间的 2 分钟窗口
    resumePolicy: ResumePolicy.ResumeIfPossible,
    maxReconnectAttempts: 5,
    autoMicStartDelaySeconds: 1.0f      // 为较慢的移动网络增加额外延迟
);
```

**预期结果：** SDK 会在 2 分钟窗口内自动重试最多 5 次。如果 Convai 端的会话仍然有效，对话会从中断处继续。如果会话已过期，角色会开启一段全新对话，而不是卡住。

***

#### 示例 2：企业入职自助终端——始终全新的对话

每位接近终端的新员工都应从头开始，并且不记得之前的用户。 `AlwaysFresh` 和 `AlwaysCreateNew` 确保每次都从干净状态开始。

```csharp
// 在 ConvaiRoomManager 的重连设置中应用策略
var policy = ReconnectPolicy.AlwaysCreateNew;
// 在 AlwaysCreateNew 中，ResumePolicy 默认是 AlwaysFresh——不会继承之前的会话
```

为了确保在下一次会话开始前移除前一位用户的数据：

```csharp
public class KioskSessionReset : MonoBehaviour
{
    [SerializeField] private string _characterId;

    public void OnUserLogOut()
    {
        ConvaiSessionData.Instance.ClearSessionId(_characterId);
    }
}
```

**预期结果：** 每位新用户都会开始一段完全全新的对话。角色不会记住之前的交互，这对于共享自助终端部署是正确的。

***

#### 示例 3：在训练模拟中处理错误状态

当所有重连尝试都用尽后，会话会进入 `错误` 状态。应将其呈现给培训主持人，并允许手动重试，而不是静默卡住。

```csharp
public class SessionErrorHandler : MonoBehaviour
{
    [SerializeField] private ConvaiSessionEventRelay _relay;
    [SerializeField] private GameObject _errorPanel;
    [SerializeField] private ConvaiManager _manager;

    private void OnEnable()  => _relay.OnSessionStateChanged.AddListener(HandleStateChange);
    private void OnDisable() => _relay.OnSessionStateChanged.RemoveListener(HandleStateChange);

    private void HandleStateChange(SessionStateChangedRelayData data)
    {
        _errorPanel.SetActive(data.IsError);
    }

    // 由培训主持人的“重试”按钮调用
    public async void RetryConnection()
    {
        _errorPanel.SetActive(false);
        await _manager.ConnectAsync();
    }
}
```

**预期结果：** 当会话进入 `错误` 状态时，错误面板会出现。培训主持人点击“重试”，即可在不重启模拟的情况下尝试新的连接。

***

### 故障排查

| 症状                    | 可能原因                                                   | 修复                                                                              |
| --------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------- |
| 会话在断开后一直停留于 `错误` 状态   | `AlwaysResume` 无法在后端恢复已过期的会话                           | 切换为 `ResumeIfPossible`；调用 `ClearSessionId(characterId)` 以移除陈旧的会话 ID，然后重新连接      |
| 尽管 `ResumeIfPossible` | 之前的 `ClearAllSessionIds()` 调用清空了会话文件，或者运行之间角色 ID 发生了变化 | 请验证 `characterId` 字符串在各次运行之间保持稳定；检查 `{persistentDataPath}/Convai/sessions.json` |
| 会话一直卡在 `连接中` 状态，永远不变  | `StartWaitTimeoutMs` 未针对慢速网络配置；或防火墙阻止了传输               | 增加 `StartWaitTimeoutMs` 在 `ReconnectPolicy`；验证对 Convai 端点的网络访问                  |
| 重连循环始终失败；会话最终进入 `错误`  | `MaxReconnectAttempts` 已耗尽                             | 订阅 `ConvaiSessionEventRelay.OnSessionStateChanged` 并将错误展示给用户；在用户确认后手动调用重连       |
| 两个角色共享了同一个会话 ID       | Inspector 中的角色 ID 字符串相同                                | 为每个角色分配唯一的角色 ID `ConvaiCharacter` 在场景中                                          |

***

### 下一步

现在你已经了解角色会话如何创建、状态转换如何工作、会话 ID 如何在重启间持久化，以及如何配置重连行为。接下来阅读轮次切换模式，了解 SDK 如何检测语音输入，然后阅读事件系统，学习如何在场景脚本中订阅会话和角色事件。

{% content-ref url="/pages/251e0bf7030a5f742a1182e15529c0604e3ee150" %}
[轮次切换模式](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/turn-taking-modes.md)
{% endcontent-ref %}

{% content-ref url="/pages/0bd691fc4d8a06b0dbafd0f28b11f39be6f57f9a" %}
[事件系统](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/event-system.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:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/session-lifecycle.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
