Skip to content

会话存储

GodeX 实现了 OpenAI Responses API 的 previous_response_id 机制,允许调用方将多个请求串联成一次对话,而无需手动回放消息历史。为了实现这一点,每个生成的响应必须与其请求快照一起持久化,以便后续请求可以沿着父指针链遍历并重建完整的对话上下文。ResponseSessionStore 是实现这一功能的存储接口,提供两种实现:用于测试和单进程部署的内存存储,以及用于生产持久化的 SQLite 存储后端。

存储边界被刻意保持狭窄——它持久化的是 API 形态的快照(而非特定于提供商的聊天消息),并将链条遍历委托给共享的 resolveResponseSessionChain 函数。这使存储层无需了解特定于提供商的协议细节。

概览

组件文件用途
ResponseSessionStoretypes.ts:99-120存储接口
MemoryResponseSessionStorememory.ts:19-66基于 Map 的内存存储
SQLiteResponseSessionStoresqlite.ts:36-149启用 WAL 模式的 SQLite 存储
assertCanSaveSessionsave-policy.ts:13-40防止意外覆盖
StoredResponseSessiontypes.ts:23-38持久化的轮次数据结构
createResponseSessionStoresession-store-factory.ts:8-16基于配置的工厂函数

存储类型

StoredResponseSession

核心持久化类型(types.ts:23-38):

字段类型描述
idResponseId响应 ID,被后续请求用作 previous_response_id
previous_response_idResponseId?父指针,形成轮次的链表结构
conversation_idConversationId?为未来 Conversation API 兼容性预留
created_atnumberUnix 时间戳
completed_atnumber?生成完成时的时间戳
statusResponseStatus"completed""incomplete"
requestStoredResponseRequestSnapshot原始请求的快照
responseStoredResponseSnapshot已生成响应的快照
metadataRecord<string, unknown>?可选的用户自定义元数据

StoredResponseRequestSnapshot

原始请求的最小子集(types.ts:46-56),用于历史重建:inputinstructionsmodeltoolstool_choiceparallel_tool_callsreasoningtexttruncation

StoredResponseSnapshot

用于历史和诊断的响应数据(types.ts:59-66):idoutputoutput_textusageerrorincomplete_details

ResponseSessionStore 接口

方法描述
get(responseId)按 ID 返回一个已存储的响应,或 null
save(session, options?)持久化响应快照;检查保存策略
resolveChain(prevId, options?)遍历父指针构建 ResponseSessionSnapshot
delete(responseId)按 ID 删除一个响应
close()释放资源(数据库连接等)

MemoryResponseSessionStore

内存实现(memory.ts:19-66)使用 Map<ResponseId, StoredResponseSession>

  • 读写时克隆:每次 get()save() 调用都经过 cloneStoredResponseSessionsnapshot-clone.ts:4-7),该函数使用 structuredClone 防止调用方通过持有的引用修改已持久化的状态。
  • 构造函数预填充:接受一个可选的初始会话数组用于测试初始化(memory.ts:22-25)。
  • 清空方法:暴露 clear() 用于测试清理(memory.ts:63-65)。

SQLiteResponseSessionStore

SQLite 实现(sqlite.ts:36-149)使用 bun:sqlite 进行持久化:

数据库 Schema

Schema 通过 migrateResponseSessionSchemasqlite-schema.ts:3-23)自动迁移,并在 previous_response_idconversation_id 上创建索引以加速链条查询。

行映射

sqlite-row-mapper.ts 模块处理双向转换:

函数方向
sessionToSQLiteParamsStoredResponseSession -> SQLiteResponseSessionParams(JSON 编码快照)
sqliteRowToSessionSQLiteResponseSessionRow -> StoredResponseSession(JSON 解析快照)

JSON 列将 request_jsonresponse_jsonmetadata_json 存储为序列化字符串(sqlite-row-mapper.ts:32-34)。

保存操作

save 方法(sqlite.ts:64-105)使用 INSERT ... ON CONFLICT(id) DO UPDATE(upsert),但仅在保存策略通过之后执行。它会检查现有记录并调用 assertCanSaveSession

同步读取

所有读取操作都是同步的(getSync),因为 bun:sqlite 是同步的。getresolveChain 方法将同步调用包装在 async 中以满足 ResponseSessionStore 接口(sqlite.ts:60-62)。

保存策略

assertCanSaveSessionsave-policy.ts:13-40)执行两项保护检查:

保护条件检查内容错误码
父指针不匹配expected_previous_response_id与会话的 previous_response_id 不一致SESSION_CONFLICT
覆盖防护已存在记录且 overwrite 不为 trueSESSION_CONFLICT

两者都会抛出带有 SESSION_CONFLICT 错误码的 SessionErrorcodes.ts:43)。

存储工厂

createResponseSessionStoresession-store-factory.ts:8-16)读取 SessionConfig 来选择后端:

config.backend创建的存储
"sqlite"使用 config.sqlite.path 或默认路径的 SQLiteResponseSessionStore
其他值MemoryResponseSessionStore

存储选择流程

交叉引用

  • 链条解析 -- resolveChain 如何遍历父指针并构建 input_items

参考文献