> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.so/llms.txt
> Use this file to discover all available pages before exploring further.

# 消息

> SDK 需要每条聊天消息提供哪些信息，以及为什么 ID 必须稳定且唯一。

SDK 在每次广告请求中都基于你喂入的对话来构建上下文。**消息是会话期间唯一会变化的上下文信号**，因此把消息处理对——是决定广告相关性、以及广告缓存能否正常工作的最关键一步。

## 消息字段

所有 SDK 都接受同样四个字段（各平台的类型名略有不同——详见 [SDK 页面](/overview)）：

| 字段          | 类型                   | 必填 | 含义                                             |
| ----------- | -------------------- | -- | ---------------------------------------------- |
| `id`        | string               | 是  | 该消息的稳定、唯一标识。SDK 会按此 id 建立广告缓存并路由事件。            |
| `role`      | `user` 或 `assistant` | 是  | 谁发送了这条消息。SDK 只会在 `user` 消息时触发预加载，但两种角色都会用于上下文。 |
| `content`   | string               | 是  | 该消息的可见文本。                                      |
| `createdAt` | 时间戳                  | 是  | 消息创建时间。                                        |

`content` 应当是消息的**可见文本**——即用户在屏幕上看到的内容。请不要对它做预处理（去 Markdown、做摘要等）——服务端使用的就是用户看到的同一段文本。

## 稳定 ID

`id` 是 SDK 中所有其他机制的连接键：

* 广告缓存按每条助手消息的 `id` 存储返回的广告（具体配对规则见 [展示广告](/concepts/displaying-ads)）。
* 广告组件（例如 `<InlineAd messageId="..." />`）通过同一个 `id` 查找对应的广告。
* 广告生命周期事件（`filled`、`no-fill`、`viewed`、`clicked` ……）会携带 `messageId`，让你知道它对应的是哪个广告位。

<Warning>
  **`id` 必须跨重渲染保持稳定。** 如果你为同一条逻辑消息在不同的渲染过程中赋予不同的 `id`（例如，每次 React 组件挂载都生成一个新的 UUID），SDK 就无法把预加载到的广告与本应展示它的广告位关联起来——广告会静默地不出现。请直接复用你后端已有的 message id；如果没有，在消息创建时生成一次并持久化到该消息的整个生命周期。
</Warning>

## 把每一条消息都传入，两种角色都要

请把 **用户** 和 **助手** 消息都通过 `addMessage` 喂入 SDK。SDK 使用完整的对话上下文（而不仅是最后一条消息）来寻找相关广告。漏掉助手消息——或者反过来只传助手消息——都会让服务端看到的上下文产生偏差，从而降低相关性。

**助手消息尤为重要。** Kontext 的广告是基于紧接其前的助手回复生成的——会延续其语气与声线，使广告读起来像对话的自然延续，而不是一段生硬的广告插入。如果 SDK 始终看不到你的助手消息，广告就没有可以参照的口吻，过渡会显得突兀。

即使你**不想**让某一轮出现广告，这条规则也成立。如果你希望在抑制广告的同时保留完整上下文（例如只在每 N 轮展示一次广告、或针对免费试用用户），请照常传入消息，并设置 `AddMessageOptions(trackOnly: true)`——见 [节奏控制](/concepts/pacing)。

## 何时调用 `addMessage`

请在消息**最终确定时**对每条消息调用**一次** `addMessage`：

* **用户消息** —— 在用户提交之后、把消息转发给你的 LLM 后端之前调用。
* **助手消息** —— 在助手回复**完整结束流式输出之后**调用，而不是每个 token 调用一次。

SDK 对预加载做了约 10ms 的防抖，并且一旦有新的 `addMessage` 到来，就会取消任何进行中的 `/preload`。连续的快速调用——用户消息紧接着助手回复、一次性恢复多条历史消息，或任何其他突发场景——都会被合并为针对最新状态的单次请求。

## 从后端恢复对话

当用户重新打开一个对话时，请按顺序对每条历史消息调用一次 `addMessage`。同样的防抖会把它们合并为针对最新一条用户消息的单次 preload——不会出现"每恢复一条消息就发一次 preload"的情况。

如果你的后端保存了原始 message id，请直接复用。如果只能重新生成（因为旧 id 没有持久化），请注意 SDK 会把它视为一段全新的对话——上下文不受影响，但服务端原本基于旧 id 缓存的任何广告都将无法再被使用。

## 常见错误

* **每次渲染都重新生成 `id`。** 请为每条消息使用稳定的 id。在组件渲染体里生成的 UUID 无法跨重渲染存活，会导致广告查找失败。
* **漏掉助手消息。** 两种角色都要走 `addMessage`。漏掉 `assistant` 会削弱上下文定向。
* **以"这条消息不需要广告"为由跳过。** 请改用 `trackOnly: true`——见 [节奏控制](/concepts/pacing)。

## 下一步

<CardGroup cols={2}>
  <Card title="广告生命周期事件" icon="bell" href="/concepts/events">
    按 `messageId` 流回的 `filled`、`no-fill` 与渲染期事件。
  </Card>

  <Card title="节奏控制" icon="eye-slash" href="/concepts/pacing">
    在抑制某一轮广告的同时保留完整上下文。
  </Card>

  <Card title="会话生命周期" icon="circle-play" href="/concepts/session">
    `addMessage` 在更大范围的会话生命周期中的位置。
  </Card>
</CardGroup>
