Back to Blog

SDK v0.11: Sliding Window Extraction, Procedural Memory & Smarter Recall

neuromem team

SDK v0.11 是自 v0.8 以来最大的一次版本更新。这个版本围绕三个方向做了深度改进:提取质量(滑动窗口提取)、记忆类型(程序性记忆)和召回精度(保留分数 + 双时间线 + 自动去重)。5 个核心特性,+2,400 行代码变更,全部向下兼容。

5

新特性

+2,400 行

代码变更

0.11.4

版本号

100%

向下兼容

滑动窗口提取 (Sliding Window Extraction)

这是 v0.11 最核心的改进。

问题很直观:当用户发送"好的"、"嗯"、"收到"这样的短消息时,逐条送入 LLM 提取几乎不可能产出有意义的记忆。LLM 拿到一条孤立的"好的",什么上下文都没有,自然提取不出任何事实或事件。更糟糕的是,这些无效的 LLM 调用还在消耗 token 预算。

解决方案是 extraction_mode='window'。开启后,SDK 不再逐条提取,而是将消息缓冲到累积字符数达到 window_char_threshold(默认 500 字符)时再统一提取。缓冲区内的所有消息作为完整上下文一起送入 LLM,提取质量显著提升。

滑动窗口提取流程

用户发送短消息

内容 < 500 字符

消息进入缓冲区

等待更多上下文积累

累积达到阈值

≥ 500 字符或会话结束

合并提取

完整上下文 → 更准确的 facts/episodes

使用方式非常简单,只需要在初始化时指定两个参数:

async with NeuroMemory(
    ...,
    extraction_mode="window",     # 开启滑动窗口
    window_char_threshold=500,    # 累积到 500 字符再提取
) as nm:
    await nm.ingest(user_id="alice", role="user", content="好的")
    # → 缓冲,不触发提取

    await nm.ingest(user_id="alice", role="assistant", content="还有什么需要帮忙的吗?")
    # → 缓冲,继续等待

    await nm.ingest(user_id="alice", role="user", content="对了,我下周要去北京出差,帮我查一下天气")
    # → 累积达到阈值,三条消息一起提取

效果对比一目了然:

逐条提取 vs 窗口提取

场景逐条提取窗口提取
"好的" + "收到" + "下周去北京出差"提取 0 条有效记忆提取 1 条: "下周计划去北京出差"
三轮闲聊后提到工作变动仅提取最后一条完整上下文,关联前后信息
LLM 调用次数(10 条短消息)10 次2-3 次

窗口提取不仅提升了质量,还显著降低了 LLM 调用次数——对于以短消息为主的对话场景,调用量可以减少 70-80%。

程序性记忆 (Procedural Memory)

v0.11 新增了第五种记忆类型:程序性记忆(Procedural)

之前的四种类型——事实(Fact)、事件(Episode)、特征(Trait)、文档(Document)——覆盖了"用户是什么样的人"和"发生过什么事",但缺少对"用户怎么做事"的建模。当用户描述"每天早上先看邮件,然后开站会,再 review PR",这是一个多步骤的工作流程,不应该被拆成三条孤立的事实。

程序性记忆专门捕获这类多步骤流程。提取时 LLM 会识别对话中的流程描述,生成结构化的 procedure_steps,每个步骤包含顺序、描述和可选的条件。

记忆类型分布(新增 Procedural)

五种记忆类型各司其职

程序性记忆还支持按 Space 级别的召回排除开关。有些 Agent(比如纯问答型)可能不需要在 recall 结果中看到工作流步骤,可以在 Space 设置中关闭 procedural 类型的召回。

保留分数 (Retention Score)

recall 的打分逻辑新增了一个因子:保留分数(Retention Score)

核心思路是:被反复访问的记忆更可能是重要的,应该在排序中获得加分。同时,刚存入不久的新记忆不应该因为访问次数少就被埋没。

计算公式:

  • retention_boost = log2(1 + access_count) * 0.05,上限 +0.25
  • 7 天新记忆保护:创建 7 天内的记忆,retention_boost 最低为 0.02

保留分数随访问次数变化

access_count → retention_boost (上限 0.25)

这个设计参考了认知科学中的间隔重复效应:你越频繁地回忆一件事,它在记忆中就越牢固。retention_boost 的对数增长曲线确保前几次访问带来的增益最大,后续趋于平缓,不会无限放大高频记忆的优势。

双时间线查询 (Dual Timeline)

recall 现在支持两套时间线过滤参数:

  • created_after / created_before:按记忆的创建时间过滤
  • event_after / event_before:按事件的实际发生时间过滤

两套时间线参数

参数过滤依据使用场景
created_after / created_before记忆创建时间最近一周存入的所有记忆
event_after / event_before事件发生时间上个月发生的所有事件

为什么需要两套?因为记忆的创建时间和事件的发生时间经常不一致。用户可能周一开了一个重要会议,但直到周三才在对话中提到。如果只按 created_at 查询,用"上周一的会议"这种自然语言查询就无法正确定位。

event_after / event_before 解决了这个问题。它们过滤的是记忆中记录的事件时间戳(event_date 字段),让时间相关的查询更加精准。

自动去重 (Memory Deduplication)

长期运行的 Agent 不可避免地会积累重复记忆。用户反复提到"我住在北京",每次都会创建一条新的事实记忆。v0.11 新增了两个 API 来解决这个问题:

  • find_duplicates():检测重复记忆。使用 content_hash 精确匹配和向量相似度近似匹配两种策略
  • merge_duplicates():合并重复记忆。保留最新版本的内容,合并 access_count,删除旧条目

去重可以手动调用,也可以在 digest 流程中自动触发。合并策略遵循"保留最新、累加权重"的原则,确保不丢失任何有价值的访问统计信息。

总结

SDK v0.11 的五个特性分别解决了记忆系统中的五个实际痛点:

  1. 滑动窗口提取 — 短消息不再产生垃圾记忆,LLM 调用量减少 70%+
  2. 程序性记忆 — 捕获"怎么做",补全记忆类型的最后一块拼图
  3. 保留分数 — 高频访问的记忆排序更靠前,新记忆受 7 天保护
  4. 双时间线 — 按事件发生时间查询,而不仅仅是记录时间
  5. 自动去重 — 清理重复记忆,保持记忆库整洁

所有特性均向下兼容,升级到 v0.11 不需要修改任何现有代码。新特性通过参数开启,不影响默认行为。

完整的 API 变更请参考 SDK Changelog