> 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/web-plugins/convai-web-sdk/dynamic-context.md).

# 动态上下文

### 连接时的动态上下文

通过连接时传入初始上下文 `dynamicInfo`:

```ts
client.connect({
  apiKey: '...',
  characterId: '...',
  dynamicInfo: '玩家：Aria，等级：5，当前位置：Eldenmere 森林',
});
```

默认情况下，此上下文是可变的，并且可以在会话期间被替换。若要将其锁定为静态系统提示：

```ts
{
  dynamicInfo: '游戏规则：已禁用 PvP。经济：通胀模式。',
  keepInContext: true, // 在整个会话中作为固定提示保留
}
```

***

### `updateContext`

会话中途更新上下文的主要方法。支持追加、替换和重置模式。

```ts
client.updateContext(options: ContextUpdateOptions)
```

#### 选项

| 字段                         | 类型                                 | 默认值        | 描述                                            |
| -------------------------- | ---------------------------------- | ---------- | --------------------------------------------- |
| `text`                     | `string`                           | —          | 要注入的上下文文本。除非 `mode` 为 `"reset"`               |
| `mode`                     | `"append" \| "replace" \| "reset"` | `"append"` | 如何应用上下文                                       |
| `run_llm`                  | `"true" \| "false" \| "auto"`      | `"auto"`   | 是否触发机器人回复                                     |
| `current_attention_object` | `string`                           | —          | 机器人应聚焦的对象（必须匹配 `actionConfig.objects[].name`) |

#### 模式

**`append`** — 追加到现有的临时上下文：

```ts
client.updateContext({
  text: '用户刚捡起了魔法剑。',
  mode: 'append',
  run_llm: 'false', // 静默更新，不触发机器人回复
});
```

**`replace`** — 替换整个临时上下文：

```ts
client.updateContext({
  text: '游戏状态：10 级，已触发 Boss 战。',
  mode: 'replace',
  run_llm: 'auto', // 让服务器决定是否回复
});
```

**`reset`** — 完全清除临时上下文：

```ts
client.updateContext({ mode: 'reset' });
```

#### 触发机器人回复

```ts
// 始终回复
client.updateContext({
  text: '出现了新的挑战者。',
  run_llm: 'true',
});

// 永不回复（静默上下文更新）
client.updateContext({
  text: '用户生命值：10/100',
  run_llm: 'false',
});

// 由服务器决定（默认）
client.updateContext({
  text: '天气已变为暴风雨。',
  run_llm: 'auto',
});
```

#### 通过以下方式监控令牌使用情况 `serverResponse`

服务器会在每次 `context-update`:

```ts
client.on('serverResponse', (response) => {
  if (response.event_type === 'context-update' && response.status === 'success') {
    const { remaining_tokens, max_tokens } = response.extras ?? {};
    console.log(`上下文令牌：${max_tokens - remaining_tokens} / ${max_tokens}`);
  }
});
```

***

### `updateDynamicInfo`

更简单的 `updateContext` —— 总是追加，并且永远不会触发机器人回复。

```ts
client.updateDynamicInfo('玩家生命值降至 30%。');
```

等同于：

```ts
client.updateContext({ text: '...', mode: 'append', run_llm: 'false' });
```

用于 `updateContext` 完全控制； `updateDynamicInfo` 用于快速静默更新。

***

### 叙事设计模板键

模板键可为每个会话定制叙事设计图。章节目标和说话标签可以包含占位符，例如 `{player_name}` 或 `{quest_item}` （单花括号）；当章节被评估时，Convai 会用你的值替换它们，因此一个叙事图可以服务于多个个性化会话。

#### 控制台设置（前提条件）

在角色的叙事设计图引用这些键之前，模板键不会生效。在控制台中：

1. **在角色上** 启用叙事设计
2. **创建一个章节** 其 **目标** （并且可选地 **说话标签**）包含你的占位符：

   > 向客人问好。请说出这句完整的欢迎语："{StartMessage}"。角色扮演上下文：{Situation}。

   占位符只会解析 **在** 章节目标和说话标签中—— `{placeholder}` 写入角色基础提示、问候语或触发消息中的内容会按字面原样播出。
3. **创建一个命名触发器** 指向该章节，并带有一个 **触发消息** （必填字段——例如“有位客人走进旅馆大门。现在向他们问好。”）。客户端通过以下方式触发它 `sendTriggerMessage('<trigger name>')` —— 名称必须完全匹配（区分大小写）。触发消息才会使机器人在触发时回复；带有目标但触发消息为空的触发器（仅可通过 REST API 实现）会静默更改章节 *静默地* 并且服务器会确认 *“触发已处理，但未生成上下文”*.
4. 还可以设置图的 **起始章节** —— 它会在会话开始时自动进入，因此在连接时，配置中传入的键会在任何触发器触发之前应用到它。

为了获得确定性的测试输出，请将占位符放在章节的说话标签中——说话标签内容会经过相同的替换，并按原文播出。

有 **一张键映射** ，可通过两种方式设置：

#### 在会话开始时预设： `narrativeTemplateKeys` config

作为 `narrative_template_keys` 发送于 `/connect` 请求中。

```ts
const client = new ConvaiClient({
  apiKey: 'your-api-key',
  characterId: 'your-character-id',
  narrativeTemplateKeys: {
    player_name: 'Alex',
    location: 'Engineering Deck',
    quest_item: 'oxygen generator',
  },
});
```

如果章节目标是 `向 {player_name} 问好。请他们修理 {quest_item}。`，则机器人会收到 `向 Alex 问好。请他们修理氧气发生器。`

#### 会话中更新： `updateTemplateKeys()`

在活动会话中替换相同的键映射。在触发进入使用新值的章节之前调用它：

```ts
client.updateTemplateKeys({
  player_name: 'Alex',
  location: 'Medical Bay',        // 玩家已移动
  quest_item: 'med kit',
});
client.sendTriggerMessage('NextObjective');
```

**这是完全替换，而不是合并** —— 传入图仍然需要的每个键，或者被省略的键将解析为空字符串。

#### 规则

* 需要一个已启用 **叙事设计**的角色。没有它， `narrativeTemplateKeys` 会被忽略，并且 `updateTemplateKeys()` 会以 `status: "error"` — `"Narrative design service not available"` （监听 `serverResponse` 用于 `update-template-keys` ack）。
* 占位符语法是在一个单词外包裹单花括号： `{player_name}`。键区分大小写，且必须与图中的占位符名称完全匹配。
* 替换发生在章节目标/说话标签被 *评估*时，而不是在键发送时——键只需要在触发器触发前准备好即可。
* 没有匹配键的占位符会解析为 **空字符串** （不会保留为字面文本）。如果角色 *说出* 一个字面 `{placeholder}`，说明文本根本没有经过叙事设计——请检查它是否位于章节目标或说话标签中，而不是基础提示或问候语中。
* **`trigger_name` 和 `trigger_message` 是保留字段。** 在每个命名触发器中，服务器都会把它们注入键映射（覆盖你的键），因此不要把这些名称用于你自己的键——但你 *可以* 在目标中 `{trigger_name}` / `{trigger_message}` 自由引用。

#### 故障排除

| 症状                                             | 原因                                                    |
| ---------------------------------------------- | ----------------------------------------------------- |
| 确认： `"Narrative design service not available"` | 角色未启用叙事设计                                             |
| 确认： `“触发已处理，但未生成上下文”`                          | 角色上不存在该触发器名称，或者该触发器的触发消息为空，且其目标章节没有说话标签（静默章节更改）       |
| 机器人说出字面 `{placeholder}`                        | 占位符位于叙事设计之外（基础提示、问候语、触发消息）——请把它移到章节目标或说话标签中           |
| 机器人说出空值                                        | 键映射中缺少该键——请记住 `updateTemplateKeys()` 会替换整个映射，并且键区分大小写 |

***

### 文件上传

在活动会话中使用 `uploadFile`直接向角色发送文件。角色会将文件作为对话上下文的一部分接收，并可以对其内容作出回应。

```ts
await client.uploadFile(file, {
  onProgress: (pct) => console.log(`${pct}% 已上传`),
});
```

#### React

```tsx
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
  const file = e.target.files?.[0];
  if (!file) return;

  await client.uploadFile(file, {
    onProgress: (pct) => setProgress(pct),
  });
};
```

#### 原生 JS

```ts
document.querySelector('#file-input').addEventListener('change', async (e) => {
  const file = (e.target as HTMLInputElement).files?.[0];
  if (!file) return;

  await client.uploadFile(file, {
    onProgress: (pct) => progressBar.style.width = `${pct}%`,
  });
});
```

#### 选项

| 字段           | 类型                      | 默认值             | 描述                     |
| ------------ | ----------------------- | --------------- | ---------------------- |
| `主题`         | `string`                | `"file-upload"` | 上传通道的路由标识符             |
| `onProgress` | `(pct: number) => void` | —               | 在上传进度中调用，参数为 0–100 的整数 |

#### 支持的格式

| 格式   | MIME 类型      |
| ---- | ------------ |
| JPEG | `image/jpeg` |
| PNG  | `image/png`  |
| GIF  | `image/gif`  |
| WebP | `image/webp` |

最大文件大小： **10 MB**。文件以原始二进制形式发送——不是 base64 编码。

#### 错误处理

`uploadFile` 是异步的，失败时会抛出异常——务必将其包裹在 try/catch 中：

```ts
try {
  await client.uploadFile(file, { onProgress: setProgress });
} catch (err) {
  // 常见原因：
  // - 未连接（传输未就绪）
  // - 文件类型不受支持（仅 JPEG、PNG、GIF、WebP）
  // - 文件超过 10 MB
  // - 传输中发生网络错误
  console.error('上传失败：', err);
}
```

在调用前先验证类型和大小，这样可以给用户一个快速的本地错误，而不是等待传输失败：

```ts
const SUPPORTED = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];

if (!SUPPORTED.includes(file.type)) throw new Error(`不支持的类型：${file.type}`);
if (file.size > 10 * 1024 * 1024) throw new Error('文件超过 10 MB');

await client.uploadFile(file, { onProgress: setProgress });
```

#### 注意

* 仅在使用 WebRTC 传输（`transport: "livekit"`，默认值）连接时可用。WebSocket 传输不支持。
* 在未连接时调用该方法会抛出异常。请先检查 `client.state.isConnected` 。

***

### 场景元数据

用于 `updateSceneMetadata` 用于描述环境变化。这 **不会** 添加新的动作目标——它只给机器人提供叙事上下文。

```ts
client.updateSceneMetadata([
  { name: 'fog', description: '一层浓雾已经弥漫开来，能见度很低。' },
  { name: 'ambience', description: '远处雷声和风声。' },
]);
```

如果机器人需要 *作用于* 场景中的对象，它们必须在 `actionConfig.objects` 中于连接时声明。参见 Actions。

***

### 会话管理

#### 重置对话

`resetSession()` 会清除消息历史并开启一个新的对话线程。角色会忘记当前交流，但会保留任何长期记忆（如果 `endUserId` 已设置）。

```ts
await client.disconnect();
client.resetSession();
await client.connect();
```

#### 空闲超时

服务器会在可配置的超时时间后断开空闲会话。使用 `resetIdleTimer()` 在任何用户交互时保持会话存活。

```ts
// 在点击、按键或任何用户活动时调用
document.addEventListener('click', () => {
  if (client.state.isConnected) {
    client.resetIdleTimer();
  }
});

// 在断开连接前监听警告
client.on('idleWarning', ({ remainingSeconds }) => {
  showBanner(`会话将在 ${remainingSeconds}s 后过期——点击继续`);
});
```

***

### 长期记忆

当 `endUserId` 提供时，角色会跨会话构建持久记忆。通过以下方式访问它们 `client.memoryManager`.

```ts
const client = useConvaiClient({
  apiKey: '...',
  characterId: '...',
  endUserId: 'user-uuid or any unique userid', // 启用记忆
});

// 连接后：
const memory = client.memoryManager;

if (memory) {
  // 列出记忆
  const { memories, total_count } = await memory.listMemories({ page: 1, pageSize: 20 });

  // 手动添加一条记忆
  await memory.addMemories(['用户是一名偏好 TypeScript 的软件工程师。']);

  // 删除特定记忆
  await memory.deleteMemory(memories[0].id);
}
```

完整参考请参阅 Memory API。


---

# 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/web-plugins/convai-web-sdk/dynamic-context.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.
