跳到主要内容

多会话

Agent 允许用户拥有多个相互独立的会话,不同会话间的上下文互不影响:在会话 A 里告诉 Agent 的信息,会话 B 中不可见。

在 OpenAgentKernel(OAK)中,会话是内置能力:每个会话对应一个 session,对话记录自动持久化,跨请求、跨实例都能恢复。新建和续接会话不需要改任何代码;要给用户提供「会话列表 / 切换 / 删除」界面(类似常见 AI 对话产品的左侧边栏),则需要在模板里补三个路由。

本文按实际操作顺序展开:理解会话机制 → 拉代码 → 添加会话路由 → 部署 → 验证。

前置条件

开箱即用:新建与续接会话

对话统一走 session/prompt 一个入口,规则只有一条:请求里不传 sessionId 就是新会话,传了就是续接

  • 新会话建立时,服务端会在事件流开头把会话 ID(conversationId)回给前端,把它存下来;
  • 之后每次发消息带上这个 ID,Agent 就能接着之前的上下文聊。

小程序端发消息:

const res = await wx.cloud.extend.AI.bot.sendMessage({
data: {
botId: "agt-xxx",
jsonrpc: "2.0",
id: 1,
method: "session/prompt",
params: {
sessionId: currentConversationId || undefined, // 不传 = 新建会话
prompt: [{ type: "text", text: message }],
},
},
});

for await (const ev of res.eventStream) {
// 新会话的首个事件会携带 conversationId,存下来用于续接
// 之后按流式事件渲染回复
}
「新建会话」按钮是前端动作

用户点「新建会话」时,前端只需要把当前持有的 sessionId 清空,让用户的下一条消息自然创建新会话。不要在点按钮时就单独创建一个空会话——没有任何消息的空会话无法续接。

第一步:拉取 Agent 代码

会话列表、拉取历史、删除会话这三个动作,OAK SDK 都提供了方法,但模板默认没有暴露对应路由,需要拉代码补上。

在控制台 Agent 详情页的「本地开发」页按指引拉取代码:

Agent 本地开发页

也可以直接用 CLI 拉取,<函数名> 即 Agent 对应的云函数名:

tcb fn code download <函数名> ./agent-code -e <环境 ID>

第二步:添加会话管理路由

三个动作对应的 SDK 调用:

动作SDK 调用
列出会话agent.sessions.list()
拉取历史session.getHistory({ limit, before })
删除会话agent.sessions.delete(sessionId)

在服务入口(src/index.ts)处理 /acp 请求的位置,按 JSON-RPC 的 method 分发。先做一件安全上必须的事——用网关注入的可信身份覆盖客户端传的 userId,避免用户越权访问他人会话:

import { gunzipSync } from "node:zlib";

function callerFromContext(req) {
const raw = req.headers["x-cloudbase-context"];
if (!raw) return null;
try {
return JSON.parse(gunzipSync(Buffer.from(raw, "base64")).toString("utf8"));
} catch {
return null;
}
}

// 分发前:
const caller = callerFromContext(req);
if (caller?.userId) params.userId = String(caller.userId);

然后逐个补路由。列出当前用户的会话:

case "session/list": {
const all = await agent.sessions.list({});
const mine = all
.filter((s) => s.userId === params.userId)
.sort((a, b) => b.updatedAt - a.updatedAt)
.slice(0, params.limit ?? 20);
return json(res, 200, { jsonrpc: "2.0", id, result: { sessions: mine } });
}

拉取某个会话的历史消息:

case "session/load": {
const session = await agent.resumeSession(params.sessionId);
const history = await session.getHistory({ limit: 50 });
return json(res, 200, { jsonrpc: "2.0", id, result: { history } });
}

删除会话:

case "session/delete": {
await agent.sessions.delete(params.sessionId);
return json(res, 200, { jsonrpc: "2.0", id, result: { deleted: params.sessionId } });
}

会话标题、置顶这类展示属性,以及会话量较大时按用户高效分页,建议自建一张会话索引表(userId / conversationId / title / 时间戳),在会话建立时写入一行,列表页直接查这张表。

第三步:部署回云端

cd ./agent-code
tcb fn code update <函数名> --dir . -e <环境 ID>

命令会提示确认,选择 Update with current config

第四步:验证

在控制台「接入 & 调试」页按下面顺序发消息,验证会话的记忆与隔离:

  1. 发一条带信息量的消息,例如「我养了一只猫叫大橘」,Agent 确认;
  2. 同一会话里追问「我的猫叫什么?」——Agent 应答出「大橘」,说明续接生效
  3. 新开一个会话问同样的问题——Agent 应表示不知道,说明会话之间是隔离的

再从客户端验证新增的三个路由(复用上文 sendMessage 入口,换 method 即可):

  • session/list:返回的列表中应包含刚才产生的会话,且只有当前用户自己的;
  • session/load:能取回上面「大橘」对话的完整消息;
  • session/delete:删除后再调 session/list,该会话不再出现。

渲染历史消息

getHistory() 返回的每条消息形如:

{
id: "...",
role: "user" | "assistant",
status: "done",
createdAt: 1756350000000,
parts: [
{ type: "text", text: "..." },
// 还可能出现 thinking / tool_call / tool_result
],
}

工具调用与结果已按对配好、内部协议消息已被过滤,前端按 parts 逐段渲染即可。

常见问题

为什么「新建会话」不能先建好空会话

会话的持久化以第一条消息为起点,没有任何消息的空会话缺少可恢复的上下文,续接会失败。所以「新建会话」在 UI 上是纯前端动作:清空当前 sessionId,等用户发第一条消息时才真正建立会话。

想清空聊天记录,但保留 Agent 的记忆

调用 session.clearHistory():它只清空前端展示的消息索引,不影响会话上下文,Agent 仍然记得之前聊过的内容。

会话标题从哪来

OAK 的 session 不管理标题。常见做法是自建会话索引表:用户发第一条消息时,把消息前若干个字(或让模型生成一句摘要)作为标题写入索引表,列表页从索引表读取。