#Next.js#React#TypeScript

React AI 对话状态契约:消息、运行与持久化如何分层

多轮 AI 对话不能只靠一个 messages 数组。本文设计浏览器临时状态、服务端会话事实、生成运行记录与幂等协议,避免串话、重复消息和刷新丢失。

本文要点

  • 主题聚焦:Next.js、React、TypeScript
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年1月31日·更新于 2026年7月20日

许多 AI 对话原型只有一个 messages 数组:提交时追加用户文本,流式到达时不断改最后一项。单窗口演示足够,但一旦出现刷新、多标签页、重新生成、工具执行或慢请求并发,数组就无法回答关键问题:哪条消息已经持久化,哪次生成正在写它,哪个版本可作为下一轮上下文,以及晚到事件是否仍然有效。

生产设计应先划分状态所有权,再选 React 状态库。服务端数据库保存可审计的事实;浏览器保存输入框、滚动位置、展开面板等短暂交互;URL 只保存可分享的会话标识和视图参数。不要因为 conversationId 出现在 URL 中,就相信访问者拥有该会话。每次读取、追加消息和启动生成都必须在服务端根据当前身份重新鉴权。

会话、消息和运行是三个对象

conversation 表示对话容器,至少包含所有者、标题、版本、创建时间和保留策略。message 表示不可变的语义记录,包含 messageId、角色、内容版本、状态和顺序。run 表示一次模型执行,包含 runId、输入消息集合、模型配置、开始时间、结束原因及目标 assistant 消息。

把运行单独建模后,“重新生成”就不必覆盖旧消息:它创建新的 run 和候选消息版本;用户选择某个版本继续对话时,服务端明确记录活动分支。工具调用也可作为 run 下的结构化步骤,而不是伪装成一段字符串。这类状态机思路与 Agent 运行时状态机 相通,但前端只接收展示所需的安全字段。

消息顺序不要依赖客户端时间。可由服务端分配单调递增的 sequence,或使用明确的父消息关系。时间戳用于展示和审计,不用于解决并发。否则两个标签页同时发送时,时钟偏差和提交顺序会让上下文分叉得不可预测。

乐观界面也要有幂等身份

用户按下发送后,React 可以立即插入一条 pending 消息,但请求必须带客户端生成的 clientMessageId。服务端以“用户 + 会话 + clientMessageId”为唯一键:第一次创建,网络重试时返回同一结果。没有该约束,移动网络重传或用户双击会产生两条内容相同但实际独立的消息,并触发两次模型费用。

服务端响应返回正式 messageId、runId 和会话新版本。客户端 reducer 用 clientMessageId 对账,而不是按数组最后一项猜测。每个流事件同时携带 runId 与递增事件序号;若事件属于旧 run,或者序号不大于已应用序号,就忽略它。这样迟到的数据不会污染重新生成后的新回答。

并发更新可使用会话版本做乐观锁。客户端提交它看到的 expectedVersion;服务端在事务中确认版本、追加消息并递增版本。发生冲突时返回可识别的冲突码,客户端拉取增量并让用户确认,而不是静默把两个分支拼在一起。React 的本地状态管理选型可参考 React 状态管理,但无论使用 reducer、Context 还是外部 store,都不能替代服务端并发约束。

把服务器快照与界面草稿分开

服务端渲染得到的初始会话快照适合作为已确认数据。输入草稿、正在上传的附件、选中的模型和自动滚动开关属于本地状态,不应每次按键都写数据库。若需要跨设备草稿,应单独建模并设置过期时间,不能混入正式消息。

切换会话时给对话工作区设置稳定的 React key,可以有意识地重置输入框和临时选择;需要保留的全局偏好则放到更高层。React 对组件位置与 key 的状态保留规则必须主动设计,否则从 A 会话切到 B 会话后,A 的草稿或错误提示可能残留。与此同时,长消息列表应以消息 ID 为 key,绝不能用数组下标。

浏览器缓存不是最终事实。刷新或重新联网后,客户端用最后确认的事件序号或消息版本请求增量;服务端返回缺失记录和当前活动 run。若 run 仍在执行,客户端重新订阅;若已结束,则以持久化结果校正本地临时文本。关于多轮上下文裁剪、摘要与事实记忆,可继续阅读 多轮对话的状态管理。

隐私、删除与可观测性也是状态契约

服务端要区分“从当前界面隐藏”“用户请求删除”“进入合规保留期”三种动作。删除会话时还要处理附件、向量索引、运行追踪和缓存,不能只删消息行。客户端退出账号后应清除本地草稿、查询缓存和跨标签页广播内容,避免下一位使用同一设备的人看到残留。

日志记录 ID、状态转移、延迟和错误码即可,不要默认记录完整提示词。指标应观察重复提交去重率、版本冲突率、孤儿 run、刷新恢复成功率和跨会话事件丢弃量。测试则应模拟双击发送、两个标签页并发、流式中刷新、重新生成后旧事件到达、权限被撤销和删除后重连。状态模型能在这些场景保持一致,才算真正支持多轮对话,而不只是让消息列表看起来连续。

Related

相关推荐