实现一个简单的短期记忆DEMO

实现一个简单的短期记忆DEMO

短期记忆让应用能在同一段会话中接续上下文。实现的核心很直接:按会话标识保存消息,每轮请求时取出对应历史交给模型。但当会话越来越长,系统还需要决定带哪些内容、如何控制长度,以及怎样压缩较早的对话。

一、短期记忆在程序里是什么

短期记忆可以理解为一条会话线程的持续状态。应用用 threadId 标识会话,把用户消息和模型回复按先后顺序保存;下一轮再取回同一线程的历史,让模型获得这段对话的背景。

要区分两件事:数据库里保存的完整历史,和本轮实际发送给模型的上下文。历史可以长期保存在本地;每次请求只需选取适合当前问题、且不超过预算的部分。

数据库记录的基本结构

messages 表保存每条消息的线程 ID、角色、正文和时间;按 thread_id 与消息顺序查询。不同线程的数据不会混在一起。

messages
├─ id          消息顺序
├─ thread_id   会话标识
├─ role        user / assistant
├─ content     消息正文
└─ created_at  保存时间

Demo 还提供切换线程、列出线程、查看当前历史、清空当前线程等命令。模型调用成功后,应用在同一事务中保存本轮的用户问题和助手回答,避免只保存半轮对话。

本节 Demo 的代码位于 Gitee 仓库的 Page7 目录:https://gitee.com/toxplore/ai/tree/master/Page7。程序默认将数据库保存在 data/short-term-memory.db。

二、Demo 完整流程图

下面的流程图对应 Page7 中的 Java 程序:从启动配置和 SQLite 初始化,到每轮问答、命令处理,以及 thread 之间的隔离与恢复。

1. 启动:准备会话消息存储

图较宽时可横向滚动查看。

短期记忆 Demo 启动流程读取 API 配置,确定 threadId,打开 SQLite 并准备会话消息表,随后进入交互循环。 读取 API 配置main()Key、模型、Base URL 确定当前 threadIdinitialThreadId(args)默认 demo-1 或命令行指定 打开 SQLite 数据库ThreadMemoryStore(…)连接本地 .db 文件 准备会话消息表CREATE TABLE / INDEX按 threadId 查询消息顺序 初始化完成后进入 main() 的交互循环

SQLite 在这个 Demo 中负责持久保存会话消息。模型不直接访问数据库;Java 应用负责按 threadId 读取和写入。

2. 普通问答:读取历史、请求模型、保存本轮

短期记忆 Demo 问答流程处理用户输入和命令,读取当前线程历史,调用模型;成功后事务保存问答,失败则报告异常。 收到用户问题main() · scanner.nextLine() 是否为命令?否则作为普通问题 读取当前线程历史store.loadMessages(threadId)SELECT … ORDER BY id 组装本次请求上下文history + 当前 user 消息当前问题先在内存中加入 发送上下文给大模型callModel(…, history)API messages = 历史 + 新问题 API 调用成功?2xx 事务保存用户问题和模型回答store.saveTurn(threadId, …) 展示模型回答回到等待下一条输入 抛出 API 异常saveTurn 尚未执行本轮消息未落库 继续交互 是:进入命令处理 执行对应命令switchThread()printMemory() / printThreads()clearThread() / quit 除 /quit 外回到输入循环

注意:Demo 在模型成功返回后才调用 saveTurn()。如果 API 报错,本轮用户问题不会写入 SQLite;此前成功的轮次仍保留。

3. Thread 命令:切换、查看或清空会话

/thread <会话ID>

switchThread() 验证 ID,并读取历史条数;main 更新当前 ID。实际历史会在下一次普通提问时重新加载。

/memory

printMemory() 调用 loadMessages(),打印当前 thread 的消息。

/threads

printThreads() 调用 listThreads(),列出已有消息的 thread ID。

/reset

clearThread(threadId) 只删除当前 thread 的消息。

/quit

main() 结束交互循环;SQLite 中已保存的会话仍保留。

4. 记忆隔离与持久化

短期记忆的线程隔离与持久化不同 threadId 的消息保存在同一 SQLite 表中,本次请求只加载当前线程,重启后可恢复。 threadId = demo-1一组有序对话消息 threadId = demo-2另一组独立消息 SQLite messages 表thread_id · role · content按 thread_id + id 索引 本次请求只取当前线程WHERE thread_id = ?程序重启后可按 ID 恢复 新会话 ID 不会自动读取别的 thread

三、会话变长后会遇到什么问题

问题为什么会发生可能的影响解决方案
上下文窗口有限每轮把全部历史重新发送,消息会不断累积。请求可能超出模型限制;即使没超限,也会增加输入 token、成本和响应时间。用模型对应的 tokenizer 估算 token;预算中先扣除系统提示、当前问题、工具定义、回答空间和余量。超出阈值时压缩较早消息,只发送“摘要 + 最近几轮原文 + 当前问题”。
历史噪声变多已结束的话题、重复内容和临时细节仍在历史中。模型可能被无关背景分散注意力。围绕当前任务挑选上下文:保留任务目标、已确认决定、约束、待办和近期对话;不相关的旧话题不放进本轮请求,需要时再从完整历史查回。
简单截断会丢重点只保留最近几轮,可能删掉更早但仍有效的决定、偏好或任务目标。模型忘记约束,或重复询问已经确认的信息。截断前先把较早消息整理成结构化摘要,保留目标、决定、偏好和未完成事项;完整原文继续存储,并记录摘要覆盖到哪条消息,便于回查。
摘要可能失真模型归纳时可能遗漏细节、混淆时间顺序,或把推测写成事实。后续回答依赖错误或过时的摘要。采用增量摘要(旧摘要 + 新一段历史),要求区分讨论、决定和完成;只记有依据的内容,冲突标为待确认。校验摘要格式并保留原文和消息位置。
线程和隐私管理线程标识使用错误会读错会话;历史也可能包含敏感信息。上下文串线,或不必要地保留、发送敏感内容。查询始终按已验证的 threadId 限定,并检查用户是否有权访问该线程;发送模型前按策略过滤敏感信息,支持清理或删除过期会话。

四、怎样从历史中挑出有用上下文

筛选要围绕当前问题和当前任务来做,而不是把历史越多越好。一个可解释、容易调试的第一版,可以把上下文拆成三部分:

任务摘要

目标、已确认决定、约束、进度、待办,以及仍有效的用户偏好。

近期原文

最近几轮完整消息,保留当前话题的细节、措辞和问答衔接。

再加上系统提示和当前问题,组成发给模型的本轮上下文。完整历史仍留在 SQLite,需要时可以按线程回查。

  1. 先保留系统提示、当前问题和最近几轮对话。
  2. 加入仍有效的目标、决定、约束、进度和待办。
  3. 若仍超预算,再压缩较早的消息;重复、无关或已被新决定替代的内容可省略。
  4. 新信息与旧信息冲突时,以用户明确更新的内容为准;无法判断时标记待确认。

五、长度控制与摘要如何配合

先计算本轮可用预算

输入可用预算
  = 模型上下文上限
  - 预留的回答空间
  - 安全余量

历史可用预算
  = 输入可用预算
  - 系统提示、工具定义、当前问题及其他输入

实际 token 数应使用对应模型或服务提供的统计能力估算。字符数不能准确替代 token 数;API 返回的用量适合事后校准,发送前仍要做预算控制。

超过阈值时压缩较早消息

常见实现是直接调用大模型做摘要,或采用增量摘要:将旧摘要 + 新发生的一段较早消息交给模型,让它生成更新后的摘要。程序负责触发时机、选择消息范围、保存摘要和校验长度;模型负责理解并归纳内容。

触发条件:历史 token 估算超过阈值
      ↓
选取较早的一段消息
      ↓
旧摘要 + 这段消息 → 摘要模型
      ↓
保存新摘要与已覆盖的消息位置
      ↓
本轮上下文 = 摘要 + 最近消息 + 当前问题

摘要保留什么

字段要记录的内容示例
当前目标仍在进行的任务实现 Java 短期记忆 Demo
已确认明确决定或已经采用的方案SQLite 按 threadId 保存消息
进度与待办已完成事项、下一步和未解决问题基础会话已完成;待实现上下文裁剪与摘要
偏好与事实仍有效的用户要求和必要的具体信息函数需要注释;百炼额度错误码

摘要更新时应要求模型只记录消息明确支持的内容,区分“讨论过、已决定、已完成”,保留必要的数字、路径和错误码;冲突标为待确认。保存时可以记录摘要覆盖到哪条消息,避免重复摘要或漏处理消息。

摘要不是原始记录的替代品。完整消息保留在数据库,摘要只是给模型快速提供背景。摘要可能有损;若任务需要精确措辞、时间线或证据,应从原始历史回查。

总结

短期记忆让应用能够按会话保存并恢复交互历史。每轮请求时,应用取出当前线程的历史,与用户的新问题一起组成上下文交给模型;模型返回后,应用再保存本轮问答。随着历史增长,系统需要在 token 预算内筛选上下文:保留当前任务相关信息和近期原文,把较早对话压缩为可校验的摘要,同时保留完整历史供后续回查。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注