上一阶段我们搞定了“富媒体卡片”,让 AI 的回复不再枯燥。但在测试过程中,我遇到了一个让人抓狂的问题:只要浏览器刷新,之前的几十条聊天记录和筛选过的房源就全部消失了

为了解决这个问题,也是为了满足任务书 4.1 节“对话历史 ≥50 条” 的硬性指标,这一阶段我投入了大量精力重构了状态管理模块。目标很明确:实现“刷新不丢历史”,并且在架构上确立“后端为数据权威”的原则,前端仅作为展示层和快照提交者。

1. 架构抉择:前端存储还是后端存储?

在动手写代码前,我思考了两种方案:

  1. 纯前端方案(LocalStorage):实现简单,但数据仅限当前浏览器,换台电脑就没了,且不利于后端做多轮约束推理(RAG)。

  2. 后端权威方案(REST API):数据由服务端统一管理,支持跨设备,且方便未来做数据分析。

我最终选择了方案 2。但为了照顾开发体验(后端接口没好时也能跑),我设计了一个“双重模式”:如果配置了后端地址,就走 HTTP 协议;如果没有配置,就自动降级回写本机文件。

2. 核心实现:persistence.py 的双重模式设计

这是本阶段最复杂的部分。我编写了 frontend/app_lib/persistence.py,核心逻辑在于 load_session 和 save_session 函数。

设计亮点:

  • 无感切换:通过 resolve_persistence_api_base() 检查环境变量。如果存在 PERSISTENCE_API_BASE,则调用 _save_session_api 发送 HTTP PUT 请求;否则调用 _save_session_local 写入本地 .persist/ 目录。

  • 原子写入(防崩溃):在写入本地文件时,我没有直接覆盖原文件,而是先写入临时文件(mkstemp),写完后再原子性地替换(os.replace)。这防止了程序在写入中途崩溃导致 JSON 文件损坏。

  • 懒加载与防抖:页面加载时通过 hydrate_session_state 拉取一次历史;平时只在状态变更时打标(mark_session_dirty),仅在页面卸载前(flush_session_persistence)写入一次,减少 API 调用频率。

# 简化逻辑示意
def save_session(session_key: str, state_subset: Mapping[str, Any]) -> None:
    base = resolve_persistence_api_base()
    if base: # 走后端
        _save_session_api(session_key, state_subset, base)
    else: # 降级成本地文件
        _save_session_local(session_key, state_subset)

3. 会话标识与生命周期管理

3.1 基于 URL 的会话 ID
我不想引入复杂的登录系统,所以采用了 URL 查询参数来管理会话。

  • 逻辑:检查 URL 中是否有 sk=(Session Key)。

  • 实现ensure_session_key() 函数。如果 URL 没有 key,就生成一个 UUID 并写入 URL(触发重定向)。这样,用户分享链接给朋友,朋友打开就是一模一样的看房记录。

3.2 FIFO 策略与 50+ 条约束
任务书要求支持 50 条以上的历史。为了防止前端内存溢出,我在 append_message 中强制实施了 FIFO(先进先出) 策略。

# frontend/app_lib/state.py
def append_message(role: str, content: str, **meta: Any) -> None:
    msg: dict[str, Any] = {"role": role, "content": content, **meta}
    st.session_state.messages.append(msg)
    # 超过上限时,丢弃最老的记录
    if len(st.session_state.messages) > MAX_CHAT_HISTORY:
        st.session_state.messages = st.session_state.messages[-MAX_CHAT_HISTORY:]
    mark_session_dirty()

4. HTTP 契约与安全性

为了让后端同学能顺利对接,我严格定义了 RESTful 接口契约,并写在了代码注释中:

方法路径说明
GET/api/chat/sessions/{sk}获取会话快照(JSON),包含 messagespreferences_snapshot 等
PUT同上提交最新快照
DELETE同上清除会话
  • 鉴权:支持 Authorization: Bearer <token>,Token 从 secrets.toml 或环境变量读取。

  • 数据结构:前端发送的 JSON 包含 version(版本号)、saved_at(时间戳)和 payload(实际数据)。这种封装方便后端未来做格式迁移。

5. UI 交互反馈

在侧边栏(Sidebar)中,我增加了一个状态提示,让用户知道当前数据是存在“云端”还是“本地”。

# frontend/app.py
def persistence_mode_caption() -> str:
    base = resolve_persistence_api_base()
    if base:
        return "会话快照通过后端 API 持久化...(由服务端入库)"
    return "未配置 API:会话暂存本机...(便于开发)"

同时,我增加了“清除会话存档”的按钮。点击时,不仅会清空前端的 session_state,还会同步调用 DELETE 接口或删除本地文件,确保数据彻底擦除,避免刷新后又“诈尸”恢复。

6. 总结与展望

现在,我们的智能体终于有了“长期记忆”。

  • 架构上:确立了“后端为数据源(Source of Truth)”的架构,前端只是视图层。

  • 体验上:用户刷新页面不再丢失几十条对话,且支持跨标签页共享状态(只要 URL 的 sk 相同)。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐