v0.5-01 让桌面 AI 助手记得住:预算组装、递归摘要、长期记忆与评估基线

我们做的是一个本地优先、单人开发的桌面 AI 助手:模型跑在本机(Ollama)或自己指定的 OpenAI 兼容接口上,全部数据留在本地磁盘。备份、分享、自动更新、OCR 这些「数据随身走」的能力做好之后,真正每天用下来,另一个问题越来越刺眼:

助手是一条金鱼。

每开一个新会话都要重新自我介绍:「我是后端开发,主要用 Java 和 Node」「我说话喜欢直接一点」「我最近在准备晋升答辩」......聊到第 30 轮,前文的关键决定被按条数硬截断丢掉,助手开始问「你刚才说的那个方案是什么」。

具体拆开是五个痛点:

  1. 长对话按条数硬截断:保留最近 N 条,前面的直接蒸发。第 5 轮定的技术选型,第 25 轮就引不到了。
  2. 超出的历史没有任何沉淀:截断就是删除,模型连「我们之前聊过这个」的认知都没有。
  3. 跨会话零记忆:个人信息、偏好、重要决定,换会话全部重讲。
  4. 记忆质量没法验证:就算做了记忆功能,改了 prompt、换了嵌入模型,效果是变好还是变坏?拍脑袋。
  5. 记忆一旦做成就成黑盒:它记了我什么?会不会记错?能不能删?用户没有控制权。

这一轮改造的主题因此定为「记得住」------但不是简单加个记忆功能,而是一整套生命周期:上下文按 token 预算组装、超预算的旧历史递归压缩成摘要、跨会话长期记忆全链路、记忆管理面板把控制权交给用户,外加遗忘策略和评估基线两条质量防线。

21 个提交,101 个文件,三次数据库迁移,493 个单测全绿,golden set 评估 11/12。这篇是总览,B02~B10 逐点拆到底。

六件事一览

模块 解决的问题
token 预算组装 历史不再按条数截断,按模型上下文长度精确装配
递归摘要压缩 超预算的旧对话折叠成摘要块注入 system,信息不蒸发
长期记忆全链路 提取 → 去重 → 向量存储 → 跨会话召回注入
记忆管理面板 可见、可删、可清空、可按助手关闭------隐私闭环
遗忘策略 低重要性 + 长期未召回的记忆软归档,记忆库不无限膨胀
评估与反馈 golden set + LLM-as-judge 回归基线;消息级 👍/👎

前四件是主线,后两件原本可能顺延,最后全部落地------因为它们恰恰是「敢不敢真用」的分水岭:没有遗忘策略,记忆库半年后就成垃圾场;没有评估基线,调参就是玄学。

记忆生命周期总图

这套设计的核心是一条完整的记忆生命周期,横跨「回合前、回合后、长期」三个时间尺度:

text 复制代码
【回合前】新消息到达
  └─ buildTurnMessages 按 token 预算组装
       ├─ system:人设 + 摘要块(512 预留)+ 记忆块(512 预留)
       ├─ recall:问题嵌入 → Top-3 → L2 ≤ 1.05 过滤 → 【长期记忆】区块
       └─ 历史:从新到旧连续装载,装不下为止

【回合后】回答完成(后台静默,失败只 warn)
  └─ post-turn-jobs 统一入口
       ├─ 递归压缩:历史占比 >70% 时折叠最旧的 50% 之外 → 摘要
       ├─ 摘要情景记忆化(importance 0.6 event)
       ├─ 记忆提取:强约束 JSON + zod 校验 + ≤5 条
       │    └─ 去重写入:L2 ≤ 0.35 合并(更新内容 + importance 取大)
       └─ 衰减归档:30 天 + importance <0.4 + 未召回 → archived(7 天间隔)

【长期】用户视角
  └─ 记忆面板:列表/筛选/编辑/删除/清空 + 助手级开关

三个时间尺度共用一个原则:记忆是增强,不是依赖 。回合后每一步独立 try/catch,提取失败、嵌入失败、归档失败都只 console.warn,绝不影响已经发给用户的回答。

三个关键数字

这套设计的所有决策都可以用三个数字串起来:

0.35(≈余弦 0.94)------去重阈值。写入前查最近邻,距离小于 0.35 视为同一记忆:内容更新为最新表述、importance 取大。「我不吃香菜」和「点餐不要香菜」不会变成两条记忆。详见 B05。

1.05(≈余弦 0.45)------召回阈值。这个数不是拍的,是 golden set 实测校准出来的:初值 0.78 全量零召回,sqlite-vec 直查发现小嵌入模型下短中文问句与目标记忆的余弦普遍只有 0.5~0.65。这是整套记忆功能最重要的方法论时刻,详见 B07。

512------两个预留席位。摘要块和记忆块各预留 512 token,作为一等公民进入预算体系,而不是「能塞多少塞多少」。详见 B02。

隐私闭环:记忆的控制权在谁手里

做记忆功能最容易被质疑的是「你在偷偷记我什么」。我们的回答是把控制权全部交出去:

  • 可见:记忆面板列出每一条记忆的内容、类型、重要性、来源会话、最近召回时间;
  • 可删:单条删除、一键清空(二次确认);
  • 可关 :每个助手独立的 memory_enabled 开关,关掉后既不提取也不召回;
  • 可恢复:遗忘是软归档(archived)不是物理删除,面板可切换查看、可恢复。

删除权永远留给用户------这条一致性要求贯穿了记忆面板与遗忘策略的全部设计。

测试与评估:两条质量防线

质量保障分两层:

常规门禁:493 个单测(shared 16 / config 14 / database 41 / ai 38 / core 236 / web 113 / desktop 35)+ 集成测试 + Desktop E2E 4/4 全绿。记忆提取、去重、召回、衰减、递归压缩、预算组装全部有独立覆盖。

评估基线 :pnpm eval:memory 手跑 golden set------12 条种子记忆 + 12 个跨会话召回用例,关键词命中率 + LLM 评委 0-2 分。它不跑在 CI 里(依赖真实 Ollama 模型,结果需要人工解读),定位为「发版前手跑的回归基线」。

正是这条基线挖出了「直觉阈值零召回」的真实缺陷------如果只跑单测,0.78 的阈值会被带进发布,记忆功能形同虚设。评估工具自身的有效性验证、排查过程中的「三层洋葱」,都在 B10 复盘里细说。

Non-Goals:明确不做什么

  • 多设备记忆同步、跨助手共享记忆空间------云端不在宪法内;
  • 知识图谱 / GraphRAG------留给未来的知识层演进,架构已预留可替换;
  • 程序性记忆(「怎么写周报」的流程)------归后续的技能系统;
  • 记忆自动微调 / 强化学习------超出本地单机范围。

一句话总结

这轮改造用 21 个提交、三次加法迁移和一条「提取 → 去重 → 召回 → 注入 → 遗忘」的记忆生命周期,把该应用从「每次对话从零开始」变成跨会话记得住、且记忆控制权完全在用户手里 的助手。而 golden set 评估基线挖出的阈值缺陷再次验证:本地模型的参数没有拍脑袋的空间,可重复执行的评估才是调参的度量衡。

下一篇 B02 讲预算体系的根基:context-budget.ts 的 token 估算------没有真实 tokenizer 时怎么按字符计价、CJK 与非 CJK 为什么分别定价、输出预留为什么用上次真实 completion 校准。

相关推荐
leo在掘金18 分钟前
只挂了11小时,5000台开发机被塞进SSH公钥
后端·架构
五萝卜38 分钟前
webshell流量分析靶场WP
安全
2501_9339232538 分钟前
redis:string类型常见命令
数据库·redis·缓存
HelloWorld0011 小时前
告警不是心疼 token,是根因没找对:把 400 条告警压成 1 条根因的归因管道实战
架构
byte轻骑兵2 小时前
时序数据库选型指南:Apache IoTDB 社区版实践与踩坑
数据库·人工智能·apache·时序数据库·iotdb
时间的拾荒人3 小时前
Redis 哨兵(Sentinel)详解:原理、选举机制与实战部署
redis·面试·架构·sentinel
黄连木笛3 小时前
Redis Insight 汉化教程:Windows / macOS / Linux 设置中文界面(附繁體、教程汉化、关闭自动更新)
数据库·redis·缓存
nice_lcj5203 小时前
MySQL 存储引擎入门:InnoDB 和 MyISAM 到底怎么选
java·数据库·mysql·面试·innodb·存储引擎·myisam
装不满的克莱因瓶3 小时前
设计模式之策略模式核心概念和应用场景
java·后端·设计模式·架构·系统架构·策略模式