> 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/features/dynamic-context/how-dynamic-context-works.md).

# 动态上下文如何工作

动态上下文为角色提供了一个对场景中正在发生之事的实时、结构化视图。角色不再只依赖在 Convai 控制面板上配置的静态系统提示，而是可以引用学员当前所在位置、已收集的设备，或刚刚触发的警报——因为这些信息会在发生时直接注入到会话中。本页解释其底层模型：SDK 跟踪的两个原语、它们如何组装成规范化上下文字符串、更新如何批处理与刷新，以及 SDK 如何回传每次更新的结果。

### 状态与事件

动态上下文建立在两种原始类型之上。

**状态** 是持久的、命名的键值对。每个状态都有名称和值。设置某个状态时，该名称之前的任何值都会被替换。状态适合表示会随时间变化、但始终只有一个当前值的事实：操作员当前所在工位、某区域的危险等级，或某项检查清单是否已完成。

**事件** 是按时间顺序发生的一次性事件。不同于状态，事件会按顺序累积，且从不被替换或去重。每次调用 `AddEvent` 都会在角色上下文中追加一行。事件适合表示会话期间发生、且角色应当能够按顺序引用的内容："学员绕过了手动上锁程序"、"7 号工位触发了化学警报"。

这两个原语会同时进入角色的感知范围。状态提供当前条件的稳定、可查询快照；事件提供已发生事情的时间顺序记录。

### 规范化上下文格式

在更新到达 Convai 之前，SDK 会把所有已跟踪的状态和事件组装成一个规范化上下文字符串：

```
{StateName} 是 {Value}
{AnotherState} 是 {Value}
事件文本第一行
事件文本第二行
```

状态首先出现，按它们被 **首次设置** 的顺序——更新状态值不会改变其位置。所有状态之后，事件会按调用顺序排列。

状态在更新过程中保持插入顺序的原因，是为了给角色提供一个稳定、可预测的世界视图。如果 `Station` 是最先设置的内容，那么无论值变化多少次，它始终会首先出现在角色的上下文中。这使上下文能被模型更一致地解释。

**示例：**

```csharp
context.SetState("Station", "Bay 3");       // 位置 1
context.SetState("HazardLevel", "High");    // 位置 2
context.AddEvent("Operator bypassed interlock");
context.SetState("Station", "Bay 7");       // 更新值；位置仍保持为 1
```

完成所有四次调用后的规范化上下文：

```
Station 是 Bay 7
HazardLevel 是 High
Operator bypassed interlock
```

你只需提供名称、值和事件文本。SDK 会自动组装并发送规范化字符串。

### 同一跟踪器的两个入口

动态上下文有两个入口，它们写入同一个底层跟踪器，并产生相同的网络行为。

**检视器— `ConvaiDynamicContextRelay`**

`ConvaiDynamicContextRelay` 是检视器入口。它取代了已弃用的 `ConvaiDynamicContextCommand` 组件。可通过 **Convai → Dynamic Context → Convai Dynamic Context Relay**，可以将其放在与 `ConvaiCharacter` 相同的 GameObject 上，或放在任何具有显式 **角色** 引用的 GameObject 上。如果 **角色** 为空并且 **自动解析角色** 已启用（默认值），则该 relay 会在调用时查找其自身 GameObject 上的 `ConvaiCharacter` 组件。

不同于 `ConvaiDynamicContextCommand`，一个 relay 不会封装单个预配置操作，因此你不再需要为每个命令创建一个子 GameObject。该 relay 公开的公共方法会直接调用 `character.DynamicContext`: `SetState(name, value)`, `AddEvent(text)`, `SetCurrentAttentionObject(objectName)`, `ClearCurrentAttentionObject()`, `ResetContext()` / `ResetContext(removeStatic)`，以及 `Flush()`。可将其中任意一个绑定到一个 `UnityEvent` ——触发器碰撞体、时间线标记或 UI 按钮——，方式与绑定其他任何公共 `MonoBehaviour` 方法相同。一个 relay 组件可以服务于同一角色上的多个不同 `UnityEvent` 回调。

两个检视器字段会作为默认值应用于通过该 relay 实例进行的每次调用： **Reaction Mode** 设置 `ConvaiRespondMode` 每次调用传入的 `Silent`（默认 **Flush Immediately**，启用时，会在操作之后立即调用 `Flush()` ，从而使更新绕过批处理延迟。由于该 relay 始终会显式传递其配置的 **Reaction Mode** 显式值，因此当调用通过 relay 路由时，方法自身的脚本默认值不适用——例如， `AddEvent`的脚本默认值为 `Auto` 会被 **Reaction Mode** 该 relay 设置的值覆盖。

该 relay 的 **事件** 部分公开了 `OnQueued` 和 `OnSkipped`. `OnSkipped` 事件，当 relay 无法解析某个 `ConvaiCharacter`. `OnQueued` 角色并分发调用后触发一次——它确认的是分发，而不是值已被接受。空的状态名或 null 值仍会记录 Console 警告，即使在 `OnQueued` 触发。

**脚本— `IConvaiDynamicContext`**

访问 `character.DynamicContext` 以获取 `IConvaiDynamicContext` 接口，并可直接从 C# 调用方法。这能让你完全控制时机、批处理和响应模式。

```csharp
IConvaiDynamicContext context = _character.DynamicContext;
context.SetState("Station", "Bay 7");
context.AddEvent("Operator bypassed interlock");
```

在以下情况下使用此入口：

* 上下文更新依赖于运行时逻辑或无法通过静态检视器字段表达的数据
* 多个状态必须原子性地更改（使用 `SetStates`)
* 你需要读回状态值（`TryGetStateValue`)
* 更新来源是外部系统，例如状态机或分析流水线

### 批处理与发送时机

已跟踪调用—— `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`, `ClearCurrentAttentionObject`，以及 `重置` ——绝不会立即发送网络消息。每次调用都会立刻更新本地跟踪状态，然后暂存一个待发送批次。这样做是为了让在同一帧内触发的一连串相关更改合并为一次规范化更新，而不是每次调用都发送一条网络消息并可能触发一次 LLM 回合。

```mermaid
graph TD
    A["SetState / AddEvent / RemoveState 调用"] --> B["本地暂存；批处理窗口打开或延长"]
    B --> C{"已调用 Flush，或者窗口已到期？"}
    C -->|否| B
    C -->|是| D["向 Convai 发送一次上下文更新"]
    D --> E["DynamicContextUpdateResultReceived"]
```

当对话处于活动状态时，首个已暂存更改会打开批处理窗口。之后每次暂存更改都会重置一个倒计时 `ConvaiCharacter.DynamicContextBatchDelaySeconds` ——默认 0.5 秒。SDK 还会强制一个内部上限，从该窗口中的首个已暂存更改开始计算为 3 秒，因此持续不断的更改流不会无限期推迟发送。调用 `Flush()` 会立即发送待处理批次，绕过倒计时剩余部分。

如果在角色进入对话之前发生调用，它只会在本地暂存——不会启动倒计时。当会话的角色就绪信号到达时，SDK 会立即刷新该暂存批次，因此在 `Awake` 或 `Start` 这段时间内进行的调用会在没有任何额外计时代码的情况下发送：

```csharp
void Start()
{
    // 安全——先在本地暂存，然后在角色准备好后立即刷新
    _character.DynamicContext.SetState("Facility", "Offshore Platform Alpha");
    _character.DynamicContext.SetState("Scenario", "Fire Drill");
    _character.DynamicContext.AddEvent("Session initialized");
}
```

当会话断开时，SDK 会将已跟踪上下文标记为需要完整的规范化重新同步，因此下一次重连时，即使离线期间本地没有任何变化，也会重建相同的上下文。

当多个已暂存更改携带不同的响应模式时，整个批次会采用最强的那个： `MustRespond` 高于 `Auto`——它的优先级高于 `Silent`.

{% hint style="warning" %}
**已在 SDK 4.4.0 中重命名。** `ConvaiContextReactionMode` 已移除。动态上下文和动态视觉上下文现在共享一套响应模式词汇， `ConvaiRespondMode` （命名空间 `Convai.Runtime`): `SyncOnly` 映射为 `Silent`, `ReactImmediately` 映射为 `MustRespond`，以及 `Auto` 保持不变。
{% endhint %}

{% hint style="warning" %}
`Apply()` 是唯一的例外：它不会暂存或排队。若在角色未进入对话时调用，更新会被丢弃。使用 `SetState`, `AddEvent`，或其他已跟踪方法来处理必须一直保留到对话开始的上下文。
{% endhint %}

### 确认与令牌反馈

SDK 发送的每一次动态上下文更新——无论来自跟踪批次、显式 `Flush()`，或原始 `Apply()` 调用——都会通过 `DynamicContextUpdateResultReceived`进行确认，并通过 `ConvaiManager.ActiveManager.Events.OnDynamicContextUpdateResultReceived`送达。使用 `UpdateId`将确认与您发送的更新进行匹配。跟踪批次始终会获得一个 SDK 生成的 ID；只有 `Apply()` 允许您提供自己的 `updateId` 用于关联。

```csharp
using Convai.Domain.DomainEvents.Runtime;
using Convai.Runtime.Components;

ConvaiManager.ActiveManager.Events.OnDynamicContextUpdateResultReceived += result =>
{
    Debug.Log($"{result.Status}: revision {result.ContextRevision}, {result.RemainingTokens} tokens remaining");
};
```

| 属性                                                                       | 类型               | 含义                                              |
| ------------------------------------------------------------------------ | ---------------- | ----------------------------------------------- |
| `状态`                                                                     | `string`         | `"success"` 表示更新已应用；任何其他值都表示它被拒绝。               |
| `消息`                                                                     | `string`         | 随状态一起提供的人类可读详情。                                 |
| `UpdateId`                                                               | `string`         | 与更新发送时分配的 ID 相匹配。                               |
| `ContextRevision`                                                        | `int`            | 后端为该角色上下文维护的修订计数器。                              |
| `TokenCount`, `StaticTokenCount`, `RuntimeTokenCount`, `RemainingTokens` | `int`            | 此更新之后，该角色上下文窗口的令牌计数。                            |
| `请求的 LLM 运行策略`, `实际 LLM 运行策略`                                            | `string`         | 请求的响应模式与后端实际接受的响应模式之间的差异。                       |
| `降级原因`                                                                   | `string`         | 解释了为什么 `实际 LLM 运行策略` 不同于 `请求的 LLM 运行策略`，在确实如此时。 |
| `Interrupted`                                                            | `bool`           | 此更新是否中断了一个正在进行中的角色轮次。                           |
| `LlmTriggered`                                                           | `bool`           | 此更新是否触发了新的角色轮次。                                 |
| `PromptRebuild`, `PromptRebuildStatus`                                   | `bool`, `string` | 后端是否重建了角色提示以包含此次更新，以及结果如何。                      |

同一事件还会携带操作特定字段—— `ActionConfigUpdated`, `ActionsCount`, `当前注意对象`以及相关属性——当更新包含操作配置补丁时。请参见 [在运行时更新角色动作](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/character-actions/update-actions-at-runtime.md) 了解该确认流程。

### 下一步

{% content-ref url="/pages/aad74412c9dc416577acffb5b0f1b8be3c9ad61d" %}
[动态上下文快速入门](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/dynamic-context-quick-start.md)
{% endcontent-ref %}

{% content-ref url="/pages/743bcc7de155496cbbf02a52a5577aee99f28aab" %}
[Relay 组件参考](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/relay-component-reference.md)
{% endcontent-ref %}

{% content-ref url="/pages/bb1aef3496a2be08987c770aa7b8072e7d8c5cd6" %}
[同步行为与时序](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/sync-behavior-and-timing.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/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/how-dynamic-context-works.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.
