边缘安全加速平台 EO 会话管理

Agent 运行在按需拉起、随时会被回收、且同一对话的多次请求可能落在不同实例上的环境里。如果只把数据放在进程内存,会遇到三个绕不开的问题:前端一刷新聊天记录就没了;换了个实例,模型接不上上一轮、表现为「失忆」;审批流、人工介入这类要「等一下再回来」的场景,实例回收后状态就丢了。

相关产品
边缘安全加速平台 EO 产品页,含版本规格、价格与购买入口。

context.store 是 Makers Agent 内置的会话管理能力,零配置即可保存与读取对话历史,并为 Claude / OpenAI / LangGraph / DeepAgents Agent 框架提供原生适配。

它的核心特性是跨实例持久化:数据落到平台 Blob 存储,不绑定进程——本次请求写入的数据,在当前请求结束、写入实例被回收、甚至换到另一个实例之后,只要带同一个 conversation_id,新请求仍然读得到。你不用自己管理任何外部数据库或缓存。

注意:说明:

注意:跨实例持久化自 EdgeOne CLI 1.6.26 起支持,请将 CLI 升级到该版本或更高(npm i -g edgeone@latest)。

注意:底层基于平台的 Blob 存储,Node 与 Python 两端 API 完全镜像(JavaScript camelCase、Python snake_case),框架接入章节以 TypeScript 为主,每节附 Python 等价示例。

一、在两种 Runtime 中使用

store 在 agents/ 与 cloud-functions/ 两类目录的代码中指向同一份数据。

目录 使用入口 典型用途
agents/ context.store LLM 对话主路径,追加消息、Checkpointer
cloud-functions/ context.agent.store 对话列表 API、消息查询

二、共通前提:对话身份

所有跨实例持久化都以 conversation_id 为 key。你不用自己解析:平台从请求头 makers-conversation-id 解析后注入 context.conversation_id,并在响应头回显 makers-conversation-id 和 makers-run-id。直接取 context.conversation_id 用即可,不要自己从请求体里另拼一个。对话 ID 作为存储 key 时长度上限 256 字符。

三、框架接入

context.store 同时提供框架原生适配器和通用 API。

framework 推荐用法 入口(TS)
claude-sdk 框架原生 SessionStore context.store.claudeSessionStore()
openai-sdk 框架原生 Session context.store.openaiSession(sessionId)
langgraph 框架原生 Checkpointer + BaseStore context.store.langgraphCheckpointer / .langgraphStore
deepagents 复用 LangGraph Checkpointer + BaseStore context.store.langgraphCheckpointer / .langgraphStore
crewai 暂不支持,使用通用 API 自管 –

四、Claude Agent SDK

claudeSessionStore() 返回 SessionStore 协议实现(append / load / listSessions / delete / listSubkeys)。

// typescriptconst sessionStore = context.store.claudeSessionStore()// pythonsession_store = context.store.claude_session_store()

Claude SDK 的 sessionId 必须是合法 UUID。如果你的 conversation_id 不保证是 UUID(比如 chat-2026-08-12-abc),用 claudeSessionBinding 把它映射成一个稳定的 UUID:本身是 UUID 就原样用,不是就生成一个并持久化映射,同一个 conversation_id 每次都换回同一个 sessionId。

TS 示例:

// agents/chat/index.tsconst conversationId = context.conversation_id ?? ''const sessionStore = context.store.claudeSessionStore()// 把任意业务 ID 映射成稳定的 UUID sessionId(是 UUID 就原样返回,否则生成并持久化)const sessionId = await context.store.claudeSessionBinding(conversationId)// 判断这个 sessionId 之前有没有落过盘:有就 resume 续上,没有就当新会话// dir 不能省——SessionStore 的 blob key 按项目目录派生,丢了 dir 永远读不回const info = await getSessionInfo(sessionId, { dir: process.cwd(), sessionStore })const options: Record<string, any> = { model, systemPrompt, sessionStore }if (info) { options.resume = sessionId // 恢复已有会话} else { options.sessionId = sessionId // 新建会话}const q = query({ prompt: message, options })

Python 示例:

session_store = context.store.claude_session_store()session_id = await context.store.claude_session_binding(conversation_id)# 同样:getSessionInfo 判断 resume/new,options 里带上 session_store 和 resume/session_id

我们提供了示例代码以供参考:Node,Python

五、OpenAI Agents SDK

openaiSession(sessionId, { maxItems }) 返回 OpenAI Agents SDK 的 Session 协议实现(getItems / addItems / popItem / clearSession)。

// openaiSession 会以 conversation id 为依据自动恢复会话// typescriptconst session = context.store.openaiSession(context.conversation_id)// pythonsession = context.store.openai_session(context.conversation_id)

断点续跑(审批流 / human-in-the-loop)要额外用 store.state。 上面的 openaiSession 只持久化对话上下文,**不持久化中断时的 RunState**。所以当工具需要人工批准、run() 在中途中断时,要自己把 RunState 序列化存进 context.store.state,下一轮读回、还原、再 run():

// agents/hitl/index.tsimport { RunState, run } from '@openai/agents'const RUN_STATE_KEY = 'openai.run-state'// —— 首轮:跑起来,如果中断就把 RunState 存进 state ——const result = await run(agent, message, { signal })if (result.state.getInterruptions().length > 0) { await context.store.state.set(RUN_STATE_KEY, result.state.toString()) // 关键:存 RunState return json({ status: 'awaiting_approval' })}await context.store.state.delete(RUN_STATE_KEY) // 没中断,跑完了,清掉// —— 下一轮:带批准/拒绝进来,读回 RunState 还原、续跑 ——const stored = await context.store.state.get<string>(RUN_STATE_KEY)const state = await RunState.fromString(agent, stored) // 还原中断态const [approval] = state.getInterruptions()body.approved ? state.approve(approval) : state.reject(approval)const resumed = await run(agent, state, { signal }) // 从断点续跑

我们提供了示例代码以供参考:Node,Python

六、LangGraph

langgraphCheckpointer 实现 BaseCheckpointSaver(getTuple / list / put / putWrites),langgraphStore 实现 BaseStore(get / put / search / listNamespaces / batch),BaseStore的 search 暂未支持按语义检索。

// typescriptconst checkpointer = context.store.langgraphCheckpointerconst store = context.store.langgraphStore// pythoncheckpointer = context.store.langgraph_checkpointerstore = context.store.langgraph_store

七、DeepAgents

DeepAgents 基于 LangGraph 构建,直接复用 langgraphCheckpointer + langgraphStore 两个适配器。

// typescriptconst checkpointer = context.store.langgraphCheckpointerconst store = context.store.langgraphStore// pythoncheckpointer = context.store.langgraph_checkpointerstore = context.store.langgraph_store

八、CrewAI

CrewAI 自带 Memory 强依赖向量库(默认 LanceDB + embedder),context.store暂时未建向量层,对话历史用context.store 通 API 自管即可,多角色编排、任务链、工具调用等核心能力完全不受影响。

如果需要跨实例恢复会话,每轮结束把框架的会话状态序列化成 JSON 存进去,下一轮开始先读回来还原。只要状态能表达成可 JSON 序列化的对象,就能实现同样的跨实例多轮记忆和断点续跑。典型落地是审批流(human-in-the-loop)——工具调用需人工批准时把运行状态存进 state、返回「等待审批」,下次带同一个 conversation_id(批准或拒绝)读回状态、从断点接着跑,跑完删掉;序列化状态始终只留在服务端。

十、方法总览

注意:说明:

注意:方法均挂在 context.store 上。Node 端使用对象解构入参(camelCase),Python 端使用关键字参数(snake_case),语义完全一致。

Node 方法 Python 方法 描述
appendMessage append_message 追加一条消息;对话不存在时自动创建
getMessages get_messages 拉消息列表,支持游标分页
updateMessage update_message 覆盖式更新指定消息
deleteMessage delete_message 删除单条消息
clearMessages clear_messages 清空消息但保留对话元信息
getConversation get_conversation 取对话元信息
listConversations list_conversations 列对话,按 lastMessageAt 倒序
updateConversation update_conversation 更新对话元信息,浅合并
deleteConversation delete_conversation 删除整个对话(不可恢复)
toAnthropicMessages to_anthropic_messages 把消息列表转成 Anthropic Messages 格式
toOpenAIInput to_openai_input 把消息列表转成 OpenAI Chat Completions 格式

十一、appendMessage / append_message

向指定对话追加一条消息,对话不存在时自动创建,并按需建立用户索引。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 业务侧对话 ID,长度 ≤ 256 字节
role 'user' | 'assistant' | 'system' | 'tool' Yes 消息角色
content string | string[] | object Yes 消息正文,支持纯文本 / 字符串数组 / 多模态 dict,序列化后 ≤ 50MB
metadata Record<string, any> No 业务自定义字段(token 数、tool_call、来源标签等)
userId / user_id string No 关联用户,传入后写入用户索引,便于按用户列对话

返回值

新消息的 messageId / message_id(形如 msg_xxx)。

TS 示例:

const messageId = await context.store.appendMessage({ conversationId: context.conversation_id, role: 'user', content: context.request.body.message, userId: context.request.body.userId, metadata: { source: 'web' },})

Python 示例:

message_id = await context.store.append_message( conversation_id=context.conversation_id, role="user", content=context.request.body["message"], user_id=context.request.body.get("user_id"), metadata={"source": "web"},)

十二、getMessages / get_messages

拉取指定对话的消息列表,支持游标分页。对话不存在不抛异常,返回空列表,默认按时间正序(order='asc',最早优先),方便直接拼 prompt。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID
limit number No 单页条数,默认 20,范围 [1, 100]
order 'asc' | 'desc' No 排序方向,默认 'asc'(最早优先)
after string No 游标,取该 messageId 之后的消息;与 before 互斥
before string No 游标,取该 messageId 之前的消息;与 after 互斥

返回值

list[Message] —— 消息数组,对话不存在返回 []。

TS 示例:

const messages = await context.store.getMessages({ conversationId: context.conversation_id, limit: 50,})const reply = await openai.chat.completions.create({ model: 'gpt-4o', messages: context.store.toOpenAIInput(messages),})

Python 示例:

messages = await context.store.get_messages( conversation_id=context.conversation_id, limit=50,)reply = await openai_client.chat.completions.create( model="gpt-4o", messages=context.store.to_openai_input(messages),)

十三、updateMessage / update_message

覆盖式更新一条消息,仅传入的字段会被覆盖,未传字段保持原值;updatedAt / updated_at 自动刷新。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID
messageId / message_id string Yes 目标消息 ID
content string | string[] | object No 新正文;不传则保持原值
metadata Record<string, any> No 整体覆盖 metadata(非合并);不传则保持原值

返回值

Message —— 更新后的完整消息对象。

TS 示例:

const updated = await context.store.updateMessage({ conversationId: context.conversation_id, messageId: 'msg_abc123', content: 'corrected answer', metadata: { edited: true },})

Python 示例:

updated = await context.store.update_message( conversation_id=context.conversation_id, message_id="msg_abc123", content="corrected answer", metadata={"edited": True},)

十四、deleteMessage / delete_message

删除单条消息。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID
messageId / message_id string Yes 目标消息 ID

TS 示例:

await context.store.deleteMessage({ conversationId: context.conversation_id, messageId: 'msg_abc123',})

Python 示例:

await context.store.delete_message( conversation_id=context.conversation_id, message_id="msg_abc123",)

十五、clearMessages / clear_messages

清空对话中所有消息,但保留 ConversationMeta,彻底删除请用 deleteConversation。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID

TS 示例:

await context.store.clearMessages({ conversationId: context.conversation_id })

Python 示例:

await context.store.clear_messages(conversation_id=context.conversation_id)

十六、getConversation / get_conversation

获取对话元信息。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID

返回值

ConversationMeta —— 包含 conversationId / createdAt / lastMessageAt / messageCount / metadata 等字段。

TS 示例:

const meta = await context.store.getConversation({ conversationId: context.conversation_id,})console.log(meta.messageCount, meta.metadata?.title)

Python 示例:

meta = await context.store.get_conversation( conversation_id=context.conversation_id,)print(meta.message_count, (meta.metadata or {}).get("title"))

十七、listConversations / list_conversations

列对话,按 lastMessageAt 排序,支持游标分页与按用户过滤。

参数

Parameter Type Required Description
limit number No 单页条数,默认 20,范围 [1, 100]
order 'asc' | 'desc' No 排序方向,默认 'desc'(最新优先)
after string No 游标,传上一页返回的 nextCursor
before string No 游标,传上一页返回的 previousCursor
userId / user_id string No 仅列该用户名下的对话(命中用户索引)

返回值

ListConversationsResult —— { items, nextCursor, previousCursor }。

TS 示例:

const { items, nextCursor } = await context.store.listConversations({ userId: 'u_123', limit: 20,})

Python 示例:

result = await context.store.list_conversations(user_id="u_123", limit=20)items, next_cursor = result.items, result.next_cursor

十八、updateConversation / update_conversation

浅合并 metadata:同 key 覆盖、不同 key 保留。

参数

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID
metadata Record<string, any> Yes 待合并字段;value 为 null / None 表示删除该 key

返回值

ConversationMeta —— 合并后的对话元信息。

TS 示例:

await context.store.updateConversation({ conversationId: context.conversation_id, metadata: { title: '产品咨询', tag: null }, // 设标题,删 tag})

Python 示例:

await context.store.update_conversation( conversation_id=context.conversation_id, metadata={"title": "产品咨询", "tag": None},)

十九、deleteConversation / delete_conversation

删除整个对话,同步清理消息索引、会话元信息和全局会话索引,不可恢复。

参数:

Parameter Type Required Description
conversationId / conversation_id string Yes 对话 ID

TS 示例:

await context.store.deleteConversation({ conversationId: context.conversation_id,})

Python 示例:

await context.store.delete_conversation( conversation_id=context.conversation_id,)

20、toAnthropicMessages / to_anthropic_messages

把 getMessages 返回的消息列表转成 Anthropic Messages API 的 messages 字段格式。

参数

Parameter Type Required Description
messages Message[] Yes getMessages / get_messages 的返回结果

返回值

Array<{ role: string; content: unknown }>—— 可直接作为 anthropic.messages.create({ messages }) 的入参。

TS 示例:

const history = await context.store.getMessages({ conversationId: context.conversation_id,})const resp = await anthropic.messages.create({ model: 'claude-sonnet-4', max_tokens: 1024, messages: context.store.toAnthropicMessages(history),})

Python 示例:

history = await context.store.get_messages( conversation_id=context.conversation_id,)resp = await anthropic_client.messages.create( model="claude-sonnet-4", max_tokens=1024, messages=context.store.to_anthropic_messages(history),)

21、toOpenAIInput / to_openai_input

把 getMessages 返回的消息列表转成 OpenAI Chat Completions API 的 messages 字段格式,保留 role ∈ user / assistant / system / tool,content 透传。

参数

Parameter Type Required Description
messages Message[] Yes getMessages / get_messages 的返回结果

返回值

Array<{ role: string, content: any }> —— 可直接作为 openai.chat.completions.create({ messages }) 的入参。

TS 示例:

const history = await context.store.getMessages({ conversationId: context.conversation_id,})const resp = await openai.chat.completions.create({ model: 'gpt-4o', messages: context.store.toOpenAIInput(history),})

Python 示例:

history = await context.store.get_messages( conversation_id=context.conversation_id,)resp = await openai_client.chat.completions.create( model="gpt-4o", messages=context.store.to_openai_input(history),)

22、state — 通用状态存储

前面的方法都是围绕「消息」的。context.store.state 是另一类能力:一个按对话隔离的 JSON 键值存储,存什么由你决定。适合任何框架,尤其是没有原生适配器的框架(如 CrewAI)、以及要存业务自定义状态(步骤计数、临时变量、序列化后的框架运行状态)的场景。

conversationId 自动绑定当前对话,方法上不用再传,写入按 conversationId 命名空间隔离。和消息历史、会话上下文一样,state 里的值也是跨实例持久化的——换实例读同一个 conversationId 仍在。

Node 方法 Python 方法 描述
state.get(key) state.get(key) 读取;key 不存在返回 null / None
state.set(key, value) state.set(key, value) 写入;value 必须 JSON 可序列化
state.delete(key) state.delete(key) 删除该 key

三个方法均为异步。

参数与约束

项 说明
key 字符串,长度 1–256
value 必须 JSON 可序列化:null / 字符串 / 布尔 / 有限数字 / 普通对象 / 数组。函数、循环引用、NaN / Infinity 会被拒绝

TS 示例:

// 请求开始:读回上一轮状态(没有则 null)const saved = await context.store.state.get<MyState>('session')// … 跑一轮对话 …// 请求结束:把最新状态存回去await context.store.state.set('session', latestState)

Python 示例:

saved = await context.store.state.get("session")# … 跑一轮 …await context.store.state.set("session", latest_state)

通用做法:用其他框架实现多轮记忆 / 断点续跑。 没有原生适配器时,思路是每轮结束把框架的会话状态序列化成 JSON 存进 state,下一轮开始先读回来还原。只要状态能表达成可 JSON 序列化的对象,就能实现跨进程的多轮记忆和断点续跑,不依赖任何框架特定的适配器。一个典型落地是审批流(human-in-the-loop):工具调用需要人工批准时,把框架序列化出的运行状态存进 state,返回「等待审批」;下一次带同一个 conversationId 进来(批准或拒绝),读回状态、从断点接着跑,跑完删掉。序列化的状态始终只留在服务端,前端只发消息和「批准/拒绝」的决定。

23、数据结构

interface Message { messageId: string // msg_xxx(appendMessage 自动生成) role: 'user' | 'assistant' | 'system' | 'tool' content: any // string / array / object(多模态) createdAt: number // ms 时间戳 metadata?: Record<string, any> // 自定义(token 数、tool_call 等) updatedAt?: number // 仅 updateMessage 后存在}interface ConversationMeta { conversationId: string createdAt: number // ms 时间戳 lastMessageAt: number // ms 时间戳 messageCount: number metadata?: Record<string, any> // 业务自定义(标题、用户、标签等)}interface ListConversationsResult { items: ConversationMeta[] nextCursor?: string // 下一页游标,传给 after 参数 previousCursor?: string // 上一页游标,传给 before 参数}

注意:说明:

注意:Python 端字段为 message_id / conversation_id / created_at / last_message_at / message_count / next_cursor / previous_cursor,整体形态一致。

24、限制与配额

项 默认 超限行为
conversation_id 长度 ≤ 256 字符 抛 MemoryValidationError
单条 content 大小 ≤ 50MB(序列化后) 抛 MemoryValidationError
单对话最大消息数 10000 抛 MemoryQuotaExceededError
limit 上限 100 抛 MemoryValidationError
limit 下限 1 抛 MemoryValidationError

单条 content 超大(长文档、图片原始数据)建议放对象存储,传 URL 进消息。

25、对话摘要

getMessages 单次最多取 100 条,长对话下推荐做法是保留最近 N 条原文 + 早期内容压成一段摘要,每次请求时从 Store 拼装回 prompt。Store 本身不调 LLM、不生成摘要,只提供 ConversationMeta.metadata 这个 JSON 容器。

26、职责切分

谁负责 做什么
Store 提供 metadata JSON 字段 + 浅合并写入。约定两个 key:summary(摘要文本)、summarizedUntil(已摘要到哪条 messageId)
业务侧 决定何时摘要、调便宜模型生成摘要、调 updateConversation 写回

注意:说明:

注意:summary / summarizedUntil 是约定 key 而非 schema,Store 只把它当任意 JSON 存。你可以换名字,只要业务侧前后保持一致。

27、实现示例

可以使用便宜模型做摘要——压缩 + 保关键事实不需要主模型那种推理能力。prompt 中四条要求都不能省,否则会出现摘要发散、寒暄混入、语言乱跳、越摘越长等问题。

async function summarizeWithLLM(previousSummary, newMessages) { const transcript = newMessages .filter(m => m.role === 'user' || m.role === 'assistant') .map(m => `${m.role}: ${ typeof m.content === 'string' ? m.content : JSON.stringify(m.content) }`) .join('\\n') const prompt = previousSummary ? `下面是一段对话的【已有摘要】和【新增对话】,请合并成一段新摘要。要求:- 保留:关键事实、用户偏好、未完成的任务、重要的上下文设定- 删除:寒暄、确认收到、重复内容、工具调用细节- 用与最近对话相同的语言书写- 控制在 500 字内【已有摘要】${previousSummary}【新增对话】${transcript}【新摘要】` : `请把下面的对话总结成一段摘要。要求:- 保留:关键事实、用户偏好、未完成的任务、重要的上下文设定- 删除:寒暄、确认收到、重复内容、工具调用细节- 用与对话相同的语言书写- 控制在 500 字内【对话】${transcript}【摘要】` const { text } = await generateText({ model: openai('gpt-4o-mini'), prompt, maxTokens: 800, }) return text.trim()}

28、消息存储和会话上下文存储的区别

context.store 上有两类互相独立、不能互相替代的持久化,多数对话应用两个都要:

存哪种 管什么效果 怎么存
消息历史 前端刷新后聊天记录还在、能重新渲染,侧边栏能列历史会话 通用 API appendMessage / getMessages,是纯读写、不调模型的数据
会话上下文 模型记得上一轮说了什么(多轮连续对话)、中断能续跑 交给框架原生适配器(见「跨实例恢复会话」),框架自己按原生格式读写

两个最常见的误区:

·只写了消息历史、没接会话上下文 → /history 里看得到记录,但模型每轮都失忆,因为上下文没喂回给它。

·只接了会话上下文、没写消息历史 → 模型记得住,但前端一刷新聊天记录空白,因为框架的 session 格式不是给前端渲染用的。

一句话:消息历史管「刷新后看得见」,会话上下文管「模型记得住」,各管一摊,都是跨实例持久化的。

赞 (0)
上一篇 4天前
下一篇 4天前