多会话
Agent 允许用户拥有多个相互独立的会话,不同会话间的上下文互不影响:在会话 A 里告诉 Agent 的信息,会话 B 中不可见。
在 OpenAgentKernel(OAK)中,会话是内置能力:每个会话对应一个 session,对话记录自动持久化,跨请求、跨实例都能恢复。新建和续接会话不需要改任何代码;要给用户提供「会话列表 / 切换 / 删除」界面(类似常见 AI 对话产品的左侧边栏),则需要在模板里补三个路由。
本文按实际操作顺序展开:理解会话机制 → 拉代码 → 添加会话路由 → 部署 → 验证。
前置条件
- 已在 CloudBase 控制台创建 Agent,选择官方
cloudbase-agent模板(即 OpenAgentKernel 项目) - 本地已安装 CloudBase CLI 并完成
tcb login - Node.js ≥ 20
开箱即用:新建与续接会话
对话统一走 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 详情页的「本地开发」页按指引拉取代码:

也可以直接用 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。
第四步:验证
在控制台「接入 & 调试」页按下面顺序发消息,验证会话的记忆与隔离:
- 发一条带信息量的消息,例如「我养了一只猫叫大橘」,Agent 确认;
- 同一会话里追问「我的猫叫什么?」——Agent 应答出「大橘」,说明续接生效;
- 新开一个会话问同样的问题——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 不管理标题。常见做法是自建会话索引表:用户发第一条消息时,把消息前若干个字(或让模型生成一句摘要)作为标题写入索引表,列表页从索引表读取。