SuperAPI AI 工具集

ai-persistence/server

智能体与模型

查看项目

更新于 10-16 09:42

技能介绍

服务端聊天持久化(Server Chat Persistence)

本能力建立在 ai-persistence 之上,包名为 @tanstack/ai-persistence。核心 API 为 withPersistence(persistence),它是一个 ChatMiddleware,用于把聊天状态写入后端,包括 messages、runs、interrupts(可选 metadata)。它不会修改 chunk 流,也不替代交付层的持久化(delivery durability)。

一、用途 用于让服务端成为聊天记录的权威来源:保存完整模型消息记录、运行状态与生命周期、待处理/已解决的工具审批与等待(human-in-the-loop 中断),并支持客户端按 threadId 重建历史、在中断后携带 resume 批继续执行。

二、主要能力

  1. 状态存储分四类 store:messages(完整模型消息记录加载/保存,withPersistence 必需)、runs(运行状态、时间、用量、错误,可选,但中断持久化需要)、interrupts(待处理/已解决的工具审批与等待,可选,依赖 runs)、metadata(应用自有命名空间键值,可选)。
  2. 命名形状:ChatTranscriptPersistence(最低要求)、ChatPersistence(全部四个 store)。工厂应标注为其中之一,不要用裸 AIPersistence——该无参类型是全部可选的集合,withPersistence 会因其 stores.messages 可能为 undefined 而拒绝。
  3. 消息按 id 合并:空 messages 时加载已存线程并继续;非空时按 id 合并。最后一个在存储中已存在的传入 id 作为 cutoff,其后已存消息被丢弃;若传入 id 均不在存储中,则保留全部已存消息;同 id 以传入为准;新 id 与无 id 消息追加。保存时 saveThread 用合并后的列表替换线程。合并发生在中间件,而非 store。
  4. 写入时机:onStart 写入待处理回合快照(尽力而为,失败不中止);中断边界写入新中断、run → interrupted、消息快照(非尽力而为);onFinish 先写规范记录,再 run → completed,并提交 resume(非尽力而为);流式快照为可选,需 snapshotStreaming: true 且受 snapshotIntervalMs 节流(默认 1000ms);onError 写 run → failed,resume 保持 pending;onAbort 有条件写入,resume 保持 pending。
  5. onAbort 的条件写入:用户点停止与关闭标签页产生相同连接关闭,因此不能仅凭 abort 推断意图。当显式取消(info.cancelRequested === true,或通过 wasCancelRequested(runs, runId) 发现持久取消请求,二者来自 @tanstack/ai,配合 requestRunCancel/RUN_CANCEL_REASON),或运行本身不可 detach(无 sandbox/journal 可重连)时,写入 'aborted'(终态,带 finishedAt);当为可 detach 运行上的普通断连(其他中间件如 @tanstack/ai-sandbox 提供了 DetachableRunCapability)时,不写入任何内容,记录保持 'running',后续 attach 可接管。标注 detachedSince 的是 detaching 中间件,而非 withPersistence。
  6. 注意:chat 的 onAbort 与 generation 的 onAbort(withGenerationPersistence)故意不对称——generation 任务没有 journal 也没有 agent 循环可重连,因此其 onAbort 总是无条件写 'aborted'。不要“修正”这种不对称。不要构建假设断连总会终结运行的客户端或后端:可 detach 的运行通常不会,为仍为 'running' 的记录编造 finishedAt 会破坏接管。判断终态请用 isTerminalRunStatus(status)(来自 @tanstack/ai-persistence),不要手写 'completed' | 'failed' | 'aborted'。流式快照默认关闭(以 finish 为准),仅当部分输出持久化值得额外写入时开启。resume 仅在成功边界(中断或 finish)的 onConfig 中被提交;失败的运行会保留中断为 pending,以便同一 resume 批重试。
  7. 中断/恢复流程:中间件记录 pending 中断并门控新输入——若存在 pending,请求必须携带匹配的 resume 批,否则 onConfig 抛错。有效 resume 时,中间件构建 resumeToolState 并清除 config.resume,避免引擎从客户端历史重复重建(服务端持有记录)。成功边界时,中断标记为 resolved/cancelled。
  8. 为客户端 hydrate(reconstructChat):服务端权威客户端按 threadId 加载历史(常见为 GET)。返回 { messages, activeRun, interrupts, page? }。messages 为该窗口的 UI 消息;activeRun 为仍在生成时的 { runId }(runs.findActiveRun);interrupts 为待重新提示的 human-in-the-loop 状态;page 为 GET 带有效 limit 时的 { truncated, cursor }。分页为可选;无 limit 返回完整记录且可省略 page。reconstructChat 从查询读取 limit 和 before。activeRun 与 interrupts 不分页。若不配置 authorize,任何猜到 ?threadId= 的人都能拿到记录(多用户生产环境必需)。
  9. 生成类活动:withGenerationPersistence(persistence) 跟踪非聊天活动(image、audio、TTS、video、transcription)的运行记录。不要在聊天 run store 上伪造 threadId = requestId,应使用生成 helper。

三、使用方式 基本用法(服务端 POST): import { chat, chatParamsFromRequest, toServerSentEventsResponse, } from '@tanstack/ai' import { openaiText } from '@tanstack/ai-openai' import { withPersistence } from '@tanstack/ai-persistence' // Your adapter — see ai-persistence/stores. import { persistence } from './persistence'

export async function POST(request: Request) { const params = await chatParamsFromRequest(request) const stream = chat({ adapter: openaiText('gpt-5.5'), messages: params.messages, threadId: params.threadId, runId: params.runId, ...(params.resume ? { resume: params.resume } : {}), middleware: [withPersistence(persistence)], }) return toServerSentEventsResponse(stream) } 始终从客户端传入 threadId 与 runId(通过 chatParamsFromRequest / body helpers)。当客户端解决待处理中断时转发 resume。

开发与测试:使用 @tanstack/ai-persistence 的 memoryPersistence(),它是实现全部四个 store 的进程内即插即用后端。

开启流式快照: import { withPersistence } from '@tanstack/ai-persistence' import { persistence } from './persistence'

withPersistence(persistence, { snapshotStreaming: true, snapshotIntervalMs: 1000, // default })

hydrate 线程: import { reconstructChat } from '@tanstack/ai-persistence' import { persistence } from './persistence' import { sessionUserId, userOwnsThread } from './auth'

export async function GET(request: Request) { return reconstructChat(persistence, request, { // Multi-user: required in production authorize: async (threadId, req) => { const userId = await sessionUserId(req) return userOwnsThread(userId, threadId) }, }) }

四、适用场景 需要服务端权威聊天记录、可恢复的 human-in-the-loop 中断与审批、可 detach 的长运行接管、以及按 threadId 重建客户端历史的多用户聊天应用;也覆盖非聊天的生成类活动运行记录。

五、限制与常见错误 CRITICAL:不要在 saveThread 内部做合并——按 id 合并是 withPersistence 的职责,saveThread 必须替换它收到的合并列表。 HIGH:遗漏 threadId / runId——持久化键与 resume 需要稳定 id,使用 chatParamsFromRequest。 HIGH:只提供 interrupts 而无 runs——interrupts 依赖 runs,withPersistence 否则会抛错。 HIGH:把工厂标注为裸 AIPersistence——其默认为稀疏全可选集合,withPersistence 与 reconstructChat 会拒绝该值,应返回 ChatPersistence(或 ChatTranscriptPersistence)。 MEDIUM:期望 withPersistence 重连断开的流——那属于交付层持久化(可恢复流),不是状态持久化。 其他:不配置 authorize 会暴露记录;不要假设断连总会终结运行;不要手工列举终态。

六、相互参照 ai-persistence(层次与推荐栈)、ai-persistence/stores(实现 store 接口)、ai-core/client-persistence(@tanstack/ai,浏览器端一半)、ai-core/locks(多实例协调)。

关于定价、收费、平台支持范围等事实,资料暂未提供。