【AgentScope Java新手村系列】(17)长期记忆系统

第十七章 长期记忆系统:从 LongTermMemory 到 workspace/MEMORY.md + 中间件驱动

1.x 的 io.agentscope.core.memory.LongTermMemory 及其子类(LongTermMemoryBase / Mem0LongTermMemory 等)在 2.0.0-RC2 中被标记为 @Deprecated(forRemoval = true)

原因:硬编码"知识库"接口很难适配不同业务------什么是"长期"?是按主题、按时间、还是按人?2.0 把这件事下沉到 workspace 文件 + 中间件,业务方自己决定写什么、何时写、写到哪。

本章先给出 1.x 旧 API 的最小示例(仅供维护老项目参考),再讲 2.0 推荐的新做法。

17.1 1.x LongTermMemory 旧 API 回顾

java 复制代码
import io.agentscope.core.memory.LongTermMemory;
import io.agentscope.core.memory.LongTermMemoryBase;
import io.agentscope.core.memory.Mem0LongTermMemory;
import io.agentscope.core.ReActAgent;

public class Chapter17_LegacyLongTerm {

    public static void main(String[] args) {
        LongTermMemory memory = new Mem0LongTermMemory(
                "https://api.mem0.ai",
                System.getenv("MEM0_API_KEY"));

        ReActAgent agent = ReActAgent.builder()
                .name("assistant")
                .sysPrompt("你是一个长期记忆助理。")
                .model(model())
                .longTermMemory(memory)        // 1.x
                .build();
    }
}

2.0 仍可编译ReActAgent.longTermMemory(...) 在 RC2 中标记为 @Deprecated(forRemoval = true),编译通过但会告警。

17.2 2.0 推荐的两层记忆结构

workspace 下两份文件,按不同"时间粒度"组织:

yaml 复制代码
workspace/
├── MEMORY.md          # 长期稳定的事实笔记
└── memory/
    ├── 2026-06-05.md  # 当天事件流
    ├── 2026-06-06.md
    └── 2026-06-07.md

17.2.1 MEMORY.md

跨会话、跨天都会用到的"稳定信息"------

markdown 复制代码
# MEMORY.md

## 用户偏好
- 常驻城市:杭州
- 时区:UTC+8
- 语言:中文

## 长期目标
- 2026 年内学完 Rust

17.2.2 memory/YYYY-MM-DD.md

按天追加的"事件流"------

bash 复制代码
# 2026-06-07

## 14:32
- 用户问"今天杭州天气",回答:22~28℃,局部多云,建议带伞

## 15:01
- 用户问"我常驻哪里",agent 从 MEMORY.md 知道"杭州",回答一致

17.3 两个核心中间件

io.agentscope.core.middleware 提供两个跟记忆直接相关的中间件:

中间件 何时触发 副作用
CompactionMiddleware 上下文 token 超过阈值 早期消息压成摘要写进 MEMORY.md
MemoryFlushMiddleware 每轮 call 结束 把"该记的"刷到当日 memory/YYYY-MM-DD.md

17.3.1 CompactionMiddleware

CompactionMiddlewareHarnessAgentbuild() 时根据 MemoryConfig 自动挂载,业务方通过 .memory() 配置即可:

scss 复制代码
HarnessAgent agent = HarnessAgent.builder()
        ...
        .memory(MemoryConfig.builder()
                .compactionEnabled(true)          // 超过 token 阈值自动压缩
                .build())
        .build();

CompactionMiddleware 工作流程:

  1. 监听每轮 call 结束
  2. 检查当前上下文 token 数
  3. 超过阈值 → 把"较旧的消息"让 LLM 总结成几行
  4. 摘要写进 MEMORY.md 的"近期摘要"小节
  5. 旧消息从 AgentState 移除

17.3.2 MemoryFlushMiddleware

scss 复制代码
import io.agentscope.harness.agent.middleware.MemoryFlushMiddleware;

HarnessAgent agent = HarnessAgent.builder()
        ...
        .memory(MemoryConfig.builder().build())           // 默认开启每轮结束后冲刷记忆
        .build();

MemoryFlushMiddleware 由 HarnessAgent 根据 MemoryConfig 自动挂载,业务方无需手动构造。工作流程:

  1. 监听每轮 call 结束
  2. 让 LLM 决定"本轮是否有值得长期记的事"(事实、用户偏好变化等)
  3. 如果有 → 追加到当日 memory/2026-06-07.md
  4. 没有 → 跳过,不写

注意 :每日事件文件不保证一定生成 。LLM 可能判断当前对话太短、没有新信息,从而跳过写入。另外 MemoryFlush 是异步回调,进程如果在 flush 完成前退出,文件也来不及创建。实际使用中建议关注 MEMORY.md(CompactionMiddleware 写入,更稳定),memory/*.md 作为辅助查看。

17.3.3 完整配置

scss 复制代码
HarnessAgent agent = HarnessAgent.builder()
        .name("assistant")
        .sysPrompt("...")
        .model(model())
        .workspace(Path.of("./workspace"))
        .memory(MemoryConfig.builder().build())     // 默认开启 MemoryFlushMiddleware
        .build();                                   // CompactionMiddleware 按 CompactionConfig 挂载

17.4 主 agent 自动读 / 写 MEMORY.md

主 agent 在每轮推理时会自动MEMORY.md 顶部几行作为"已知背景"。如果你想"agent 决定更新 MEMORY.md"------给它 @Tool

less 复制代码
@Tool(name = "update_long_term_memory", description = "更新长期记忆")  // 注册为 agent 可调工具
public String updateMemory(
        @ToolParam(name = "section") String section,                   // 比如 "用户偏好"
        @ToolParam(name = "content") String content) {                // 比如 "默认中文回答"

    Path memFile = Path.of("./workspace/MEMORY.md");                  // 记忆文件固定路径
    String existing = memFile.toFile().exists()
            ? Files.readString(memFile)                               // 已有记忆 → 追加
            : "# MEMORY.md\n\n";                                      // 新文件 → 创建
    String updated = existing + "\n## " + section + "\n- " + content + "\n";
    Files.writeString(memFile, updated);                              // 写回文件
    return "memory updated";
}

把工具通过 Toolkit 注册进 agent,LLM 看到用户说"以后请默认中文回答"会自己调这个工具写进 MEMORY.md,下次对话时立刻生效。

17.5 完整可运行示例

这个例子在演示什么?

一个 agent 同时运行三种记忆机制,各管各的时间粒度:

  • CompactionMiddleware.compaction() 配置):上下文太长时自动压缩旧消息为摘要,写进 MEMORY.md
  • MemoryFlushMiddleware.memory().flushEnabled(true) 配置):每轮对话结束自动提取重要信息,写进 memory/YYYY-MM-DD.md
  • UpdateMemoryTool@Tool 工具):agent 在对话中主动决定"这件事值得长期记住",自己调工具写 MEMORY.md

三者互补:Compaction 管上下文压缩(被动)、MemoryFlush 管日常记录(自动)、UpdateMemoryTool 管即时决策(主动)。

scss 复制代码
public class Chapter17_MemoryStack {

    public static void main(String[] args) {
        DashScopeChatModel model = DashScopeChatModel.builder()
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                .modelName("qwen-plus")
                .build();

        // 1. 手工更新记忆的工具(agent 主动决策"记住这个")
        Toolkit toolkit = new Toolkit();
        toolkit.registerTool(new UpdateMemoryTool());

        // 2. agent:三层记忆机制一起跑
        HarnessAgent agent = HarnessAgent.builder()
                .name("assistant")
                .sysPrompt("你是一个有长期记忆的助理。用户告诉你的事如果需要长期记住,调用 update_long_term_memory 工具写下来。")
                .model(model)
                .workspace(Path.of("./workspace"))
                .toolkit(toolkit)
                .compaction(CompactionConfig.builder().build())  // 上下文太长时自动压缩
                .memory(MemoryConfig.builder().build())          // 默认 flush=每轮结束自动冲刷
                .build();

        // 3. 多轮对话(同一个 session,演示跨轮记忆)
        RuntimeContext ctx = RuntimeContext.builder()
                .sessionId("user-9527-2026-06-07")
                .userId("9527")
                .build();

        // Round 1:agent 记住用户基本信息
        agent.call(List.of(new UserMessage("user", "我叫小李,住在杭州。")), ctx).block();
        // Round 2:agent 应该能从记忆里调出信息
        agent.call(List.of(new UserMessage("user", "我叫什么?住在哪?")), ctx).block();
        // Round 3:agent 主动调 update_long_term_memory 写偏好
        agent.call(List.of(new UserMessage("user", "以后请用中文回答。")), ctx).block();
    }
}

跑完你会看到:

  • workspace/MEMORY.md --- CompactionMiddleware 写的摘要 + UpdateMemoryTool 写的用户偏好(@ToolParam 缺了这里会是 null)
  • workspace/memory/ --- 不一定生成。MemoryFlushMiddleware 的写入取决于 LLM 判断"是否值得记"以及异步回调是否执行完毕
  • Round 2 时 agent 能从上下文记忆里知道"小李 / 杭州"
  • Round 3 时 agent 主动调工具把"中文回答"偏好写进 MEMORY.md

17.6 最小迁移清单(1.x LongTermMemory → 2.0 文件记忆)

1.x 用法 2.0 等价
LongTermMemory.retrieve(query) 业务方手写 @Tool 调自家向量库;或 subagent 用 grep_files
LongTermMemory.record(messages) MemoryFlushMiddleware + update_long_term_memory 工具
LongTermMemoryBase 子类 业务方自己实现 @Tool
agent.longTermMemory(memory) workspace/MEMORY.md + memory/*.md + 中间件

17.7 本章小结

  • 1.x LongTermMemory 在 2.0 中被标记为弃用,未来会移除。
  • 2.0 用 MEMORY.md + memory/YYYY-MM-DD.md 两层文件做长期记忆。
  • CompactionMiddleware 自动压缩 + MemoryFlushMiddleware 自动写入。
  • 业务方可以手写 @Tool 让 agent 主动更新 MEMORY.md
相关推荐
wei1986211 小时前
.net添加web引用和添加服务引用有什么区别?
java·前端·.net
Full Stack Developme1 小时前
正则表达式的使用教程
java·数据库·正则表达式
SeeYa-J2 小时前
Sprint 1-2:创建第一个 Spring Boot Module(user-service)
java·spring boot·sprint
云絮.3 小时前
数据库事务
java·开发语言·数据库
格子软件3 小时前
2026年GEO优化系统源码级状态机与多模型调度拆解
java·前端·vue.js·人工智能·vue·geo
Full Stack Developme3 小时前
Java 漏斗算法 及应用场景
java·开发语言·算法
从此以后自律3 小时前
Spring 全家桶
java·后端·spring
偏爱自由 !3 小时前
一(0.1):配置git
java·git·intellij-idea